diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt index 94749b45..ceecd768 100644 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GDMLTransformer.kt @@ -7,6 +7,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.asName import hep.dataforge.names.plus import hep.dataforge.names.toName +import hep.dataforge.vision.MutableVisionGroup import hep.dataforge.vision.set import hep.dataforge.vision.solid.* import hep.dataforge.vision.solid.SolidMaterial.Companion.MATERIAL_COLOR_KEY @@ -19,7 +20,7 @@ import kotlin.random.Random private val solidsName = "solids".asName() private val volumesName = "volumes".asName() -class GDMLTransformer(val root: GDML) { +class GDMLTransformer internal constructor(val root: GDML) { //private val materialCache = HashMap() private val random = Random(222) @@ -38,25 +39,35 @@ class GDMLTransformer(val root: GDML) { /** * A special group for local templates */ - internal val proto by lazy { SolidGroup() } + private val proto by lazy { SolidGroup() } - internal val solids by lazy { + private val solids by lazy { proto.group(solidsName) { config["edges.enabled"] = false } } - internal val volumes by lazy { - proto.group(volumesName) + private val referenceStore = HashMap>() + + private fun proxySolid(group: SolidGroup, solid: GDMLSolid, name: String): Proxy { + val templateName = solidsName + name + if (proto[templateName] == null) { + solids.addSolid(solid, name) + } + val ref = group.ref(templateName, name) + referenceStore.getOrPut(templateName) { ArrayList() }.add(ref) + return ref } -// fun proxySolid(group: SolidGroup, solid: GDMLSolid, name: String): Proxy { -// val fullName = solidsName + name -// if (proto[fullName] == null) { -// solids.addSolid(this, solid, name) -// } -// return group.ref(fullName, name) -// } + private fun proxyVolume(group: SolidGroup, physVolume: GDMLPhysVolume, volume: GDMLGroup): Proxy { + val templateName = volumesName + volume.name.asName() + if (proto[templateName] == null) { + proto[templateName] = volume(volume) + } + val ref = group.ref(templateName, physVolume.name ?: "").withPosition(physVolume) + referenceStore.getOrPut(templateName) { ArrayList() }.add(ref) + return ref + } private val styleCache = HashMap() @@ -70,14 +81,14 @@ class GDMLTransformer(val root: GDML) { } } - fun Solid.useStyle(name: String, builder: MetaBuilder.() -> Unit) { + private fun Solid.useStyle(name: String, builder: MetaBuilder.() -> Unit) { styleCache.getOrPut(name.toName()) { Meta(builder) } useStyle(name) } - internal fun configureSolid(obj: Solid, parent: GDMLVolume, solid: GDMLSolid) { + private fun configureSolid(obj: Solid, parent: GDMLVolume, solid: GDMLSolid) { val material = parent.materialref.resolve(root) ?: GDMLElement(parent.materialref.ref) val styleName = "material[${material.name}]" @@ -92,8 +103,244 @@ class GDMLTransformer(val root: GDML) { var onFinish: GDMLTransformer.() -> Unit = {} - internal fun finalize(final: SolidGroup): SolidGroup { + + private fun T.withPosition( + newPos: GDMLPosition? = null, + newRotation: GDMLRotation? = null, + newScale: GDMLScale? = null + ): T = apply { + newPos?.let { + val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit)) + if (position != null || point != World.ZERO) { + position = point + } + } + newRotation?.let { + val point = Point3D(it.x(aUnit), it.y(aUnit), it.z(aUnit)) + if (rotation != null || point != World.ZERO) { + rotation = point + } + //this@withPosition.rotationOrder = RotationOrder.ZXY + } + newScale?.let { + val point = Point3D(it.x, it.y, it.z) + if (scale != null || point != World.ONE) { + scale = point + } + } + //TODO convert units if needed + } + + private fun T.withPosition(physVolume: GDMLPhysVolume): T = withPosition( + physVolume.resolvePosition(root), + physVolume.resolveRotation(root), + physVolume.resolveScale(root) + ) + + @Suppress("NOTHING_TO_INLINE") + private inline operator fun Number.times(d: Double) = toDouble() * d + + @Suppress("NOTHING_TO_INLINE") + private inline operator fun Number.times(f: Float) = toFloat() * f + + private fun SolidGroup.addSolid( + solid: GDMLSolid, + name: String = "" + ): Solid { + //context.solidAdded(solid) + val lScale = solid.lscale(lUnit) + val aScale = solid.ascale() + return when (solid) { + is GDMLBox -> box(solid.x * lScale, solid.y * lScale, solid.z * lScale, name) + is GDMLTube -> tube( + solid.rmax * lScale, + solid.z * lScale, + solid.rmin * lScale, + solid.startphi * aScale, + solid.deltaphi * aScale, + name + ) + is GDMLCone -> cone(solid.rmax1, solid.z, solid.rmax2, name = name) { + require(solid.rmin1 == 0.0) { "Empty cones are not supported" } + require(solid.rmin2 == 0.0) { "Empty cones are not supported" } + startAngle = solid.startphi.toFloat() + angle = solid.deltaphi.toFloat() + } + is GDMLXtru -> extrude(name) { + shape { + solid.vertices.forEach { + point(it.x * lScale, it.y * lScale) + } + } + solid.sections.sortedBy { it.zOrder }.forEach { section -> + layer( + section.zPosition * lScale, + section.xOffset * lScale, + section.yOffset * lScale, + section.scalingFactor + ) + } + } + is GDMLScaledSolid -> { + //Add solid with modified scale + val innerSolid: GDMLSolid = solid.solidref.resolve(root) + ?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined") + + addSolid(innerSolid, name).apply { + scaleX *= solid.scale.x.toFloat() + scaleY *= solid.scale.y.toFloat() + scaleZ = solid.scale.z.toFloat() + } + } + is GDMLSphere -> sphere(solid.rmax * lScale, solid.deltaphi * aScale, solid.deltatheta * aScale, name) { + phiStart = solid.startphi * aScale + thetaStart = solid.starttheta * aScale + } + is GDMLOrb -> sphere(solid.r * lScale, name = name) + is GDMLPolyhedra -> extrude(name) { + //getting the radius of first + require(solid.planes.size > 1) { "The polyhedron geometry requires at least two planes" } + val baseRadius = solid.planes.first().rmax * lScale + shape { + (0..solid.numsides).forEach { + val phi = solid.deltaphi * aScale / solid.numsides * it + solid.startphi * aScale + (baseRadius * cos(phi) to baseRadius * sin(phi)) + } + } + solid.planes.forEach { plane -> + //scaling all radii relative to first layer radius + layer(plane.z * lScale, scale = plane.rmax * lScale / baseRadius) + } + } + is GDMLBoolSolid -> { + val first: GDMLSolid = solid.first.resolve(root) ?: error("") + val second: GDMLSolid = solid.second.resolve(root) ?: error("") + val type: CompositeType = when (solid) { + is GDMLUnion -> CompositeType.UNION + is GDMLSubtraction -> CompositeType.SUBTRACT + is GDMLIntersection -> CompositeType.INTERSECT + } + + return composite(type, name) { + addSolid(first).withPosition( + solid.resolveFirstPosition(root), + solid.resolveFirstRotation(root), + null + ) + + addSolid(second).withPosition( + solid.resolvePosition(root), + solid.resolveRotation(root), + null + ) + + } + } + else -> error("Renderer for $solid not supported yet") + } + } + + private fun SolidGroup.addSolidWithCaching( + solid: GDMLSolid, + name: String = solid.name + ): Solid? { + return when (solidAction(solid)) { + Action.ADD -> { + addSolid(solid, name) + } + Action.PROTOTYPE -> { + proxySolid(this, solid, name) + } + Action.REJECT -> { + //ignore + null + } + } + } + + private fun SolidGroup.addPhysicalVolume( + physVolume: GDMLPhysVolume + ) { + val volume: GDMLGroup = physVolume.volumeref.resolve(root) + ?: error("Volume with ref ${physVolume.volumeref.ref} could not be resolved") + + // a special case for single solid volume + if (volume is GDMLVolume && volume.physVolumes.isEmpty() && volume.placement == null) { + val solid = volume.solidref.resolve(root) + ?: error("Solid with tag ${volume.solidref.ref} for volume ${volume.name} not defined") + addSolidWithCaching(solid, physVolume.name ?: "")?.apply { + configureSolid(this, volume, solid) + withPosition(physVolume) + } + return + } + + when (volumeAction(volume)) { + Action.ADD -> { + val group: SolidGroup = volume(volume) + this[physVolume.name ?: ""] = group.withPosition(physVolume) + } + Action.PROTOTYPE -> { + proxyVolume(this, physVolume, volume) + } + Action.REJECT -> { + //ignore + } + } + } + + private fun SolidGroup.addDivisionVolume( + divisionVolume: GDMLDivisionVolume + ) { + val volume: GDMLGroup = divisionVolume.volumeref.resolve(root) + ?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved") + + //TODO add divisions + set(Name.EMPTY, volume(volume)) + } + + private fun volume( + group: GDMLGroup + ): SolidGroup = SolidGroup().apply { + if (group is GDMLVolume) { + val solid: GDMLSolid = group.solidref.resolve(root) + ?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined") + + addSolidWithCaching(solid)?.apply { + configureSolid(this, group, solid) + } + + when (val vol: GDMLPlacement? = group.placement) { + is GDMLPhysVolume -> addPhysicalVolume(vol) + is GDMLDivisionVolume -> addDivisionVolume(vol) + } + } + + group.physVolumes.forEach { physVolume -> + addPhysicalVolume(physVolume) + } + } + + private fun finalize(final: SolidGroup): SolidGroup { //final.prototypes = proto + final.useStyle("GDML") { + Solid.ROTATION_ORDER_KEY put RotationOrder.ZXY + } + + //inline prototypes +// referenceStore.forEach { (protoName, list) -> +// val proxy = list.singleOrNull() ?: return@forEach +// val parent = proxy.parent as? MutableVisionGroup ?: return@forEach +// val token = parent.children.entries.find { it.value == proxy }?.key ?: error("Inconsistent reference cache") +// val prototype = proto[protoName] as? Solid ?: error("Inconsistent reference cache") +// prototype.parent = null +// parent[token] = prototype +// prototype.updateFrom(proxy) +// +// //FIXME update prototype +// proto[protoName] = null +// } + final.prototypes { proto.children.forEach { (token, item) -> item.parent = null @@ -105,252 +352,19 @@ class GDMLTransformer(val root: GDML) { define(it.key.toString(), it.value) } } - final.rotationOrder = RotationOrder.ZXY onFinish(this@GDMLTransformer) return final } -} - -private fun Solid.withPosition( - lUnit: LUnit, - aUnit: AUnit = AUnit.RADIAN, - newPos: GDMLPosition? = null, - newRotation: GDMLRotation? = null, - newScale: GDMLScale? = null -): Solid = apply { - newPos?.let { - val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit)) - if (position != null || point != World.ZERO) { - position = point - } - } - newRotation?.let { - val point = Point3D(it.x(aUnit), it.y(aUnit), it.z(aUnit)) - if (rotation != null || point != World.ZERO) { - rotation = point - } - //this@withPosition.rotationOrder = RotationOrder.ZXY - } - newScale?.let { - val point = Point3D(it.x, it.y, it.z) - if (scale != null || point != World.ONE) { - scale = point - } - } - //TODO convert units if needed -} - -private fun Solid.withPosition(context: GDMLTransformer, physVolume: GDMLPhysVolume) = withPosition( - context.lUnit, context.aUnit, - physVolume.resolvePosition(context.root), - physVolume.resolveRotation(context.root), - physVolume.resolveScale(context.root) -) - -@Suppress("NOTHING_TO_INLINE") -private inline operator fun Number.times(d: Double) = toDouble() * d - -@Suppress("NOTHING_TO_INLINE") -private inline operator fun Number.times(f: Float) = toFloat() * f - -private fun SolidGroup.addSolid( - context: GDMLTransformer, - solid: GDMLSolid, - name: String = "" -): Solid { - //context.solidAdded(solid) - val lScale = solid.lscale(context.lUnit) - val aScale = solid.ascale() - return when (solid) { - is GDMLBox -> box(solid.x * lScale, solid.y * lScale, solid.z * lScale, name) - is GDMLTube -> tube( - solid.rmax * lScale, - solid.z * lScale, - solid.rmin * lScale, - solid.startphi * aScale, - solid.deltaphi * aScale, - name - ) - is GDMLCone -> cone(solid.rmax1, solid.z, solid.rmax2, name = name) { - require(solid.rmin1 == 0.0) { "Empty cones are not supported" } - require(solid.rmin2 == 0.0) { "Empty cones are not supported" } - startAngle = solid.startphi.toFloat() - angle = solid.deltaphi.toFloat() - } - is GDMLXtru -> extrude(name) { - shape { - solid.vertices.forEach { - point(it.x * lScale, it.y * lScale) - } - } - solid.sections.sortedBy { it.zOrder }.forEach { section -> - layer( - section.zPosition * lScale, - section.xOffset * lScale, - section.yOffset * lScale, - section.scalingFactor - ) - } - } - is GDMLScaledSolid -> { - //Add solid with modified scale - val innerSolid: GDMLSolid = solid.solidref.resolve(context.root) - ?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined") - - addSolid(context, innerSolid, name).apply { - scaleX *= solid.scale.x.toFloat() - scaleY *= solid.scale.y.toFloat() - scaleZ = solid.scale.z.toFloat() - } - } - is GDMLSphere -> sphere(solid.rmax * lScale, solid.deltaphi * aScale, solid.deltatheta * aScale, name) { - phiStart = solid.startphi * aScale - thetaStart = solid.starttheta * aScale - } - is GDMLOrb -> sphere(solid.r * lScale, name = name) - is GDMLPolyhedra -> extrude(name) { - //getting the radius of first - require(solid.planes.size > 1) { "The polyhedron geometry requires at least two planes" } - val baseRadius = solid.planes.first().rmax * lScale - shape { - (0..solid.numsides).forEach { - val phi = solid.deltaphi * aScale / solid.numsides * it + solid.startphi * aScale - (baseRadius * cos(phi) to baseRadius * sin(phi)) - } - } - solid.planes.forEach { plane -> - //scaling all radii relative to first layer radius - layer(plane.z * lScale, scale = plane.rmax * lScale / baseRadius) - } - } - is GDMLBoolSolid -> { - val first: GDMLSolid = solid.first.resolve(context.root) ?: error("") - val second: GDMLSolid = solid.second.resolve(context.root) ?: error("") - val type: CompositeType = when (solid) { - is GDMLUnion -> CompositeType.UNION - is GDMLSubtraction -> CompositeType.SUBTRACT - is GDMLIntersection -> CompositeType.INTERSECT - } - - return composite(type, name) { - addSolid(context, first).withPosition( - context.lUnit, context.aUnit, - solid.resolveFirstPosition(context.root), - solid.resolveFirstRotation(context.root), - null - ) - - addSolid(context, second).withPosition( - context.lUnit, context.aUnit, - solid.resolvePosition(context.root), - solid.resolveRotation(context.root), - null - ) - - } - } - else -> error("Renderer for $solid not supported yet") + val result by lazy { + finalize(volume(root.world)) } } -private fun SolidGroup.addSolidWithCaching( - context: GDMLTransformer, - solid: GDMLSolid, - name: String = solid.name -): Solid? { - return when (context.solidAction(solid)) { - GDMLTransformer.Action.ADD -> { - addSolid(context, solid, name) - } - GDMLTransformer.Action.PROTOTYPE -> { -// context.proxySolid(this, solid, name) - val fullName = solidsName + solid.name.asName() - if (context.proto[fullName] == null) { - context.solids.addSolid(context, solid, solid.name) - } - ref(fullName, name) - } - GDMLTransformer.Action.REJECT -> { - //ignore - null - } - } -} - -private fun SolidGroup.addPhysicalVolume( - context: GDMLTransformer, - physVolume: GDMLPhysVolume -) { - val volume: GDMLGroup = physVolume.volumeref.resolve(context.root) - ?: error("Volume with ref ${physVolume.volumeref.ref} could not be resolved") - - // a special case for single solid volume - if (volume is GDMLVolume && volume.physVolumes.isEmpty() && volume.placement == null) { - val solid = volume.solidref.resolve(context.root) - ?: error("Solid with tag ${volume.solidref.ref} for volume ${volume.name} not defined") - addSolidWithCaching(context, solid, physVolume.name ?: "")?.apply { - context.configureSolid(this, volume, solid) - withPosition(context, physVolume) - } - return - } - - when (context.volumeAction(volume)) { - GDMLTransformer.Action.ADD -> { - val group: SolidGroup = volume(context, volume) - this[physVolume.name ?: ""] = group.withPosition(context, physVolume) - } - GDMLTransformer.Action.PROTOTYPE -> { - val fullName = volumesName + volume.name.asName() - if (context.proto[fullName] == null) { - context.proto[fullName] = volume(context, volume) - } - ref(fullName, physVolume.name ?: "").withPosition(context, physVolume) - } - GDMLTransformer.Action.REJECT -> { - //ignore - } - } -} - -private fun SolidGroup.addDivisionVolume( - context: GDMLTransformer, - divisionVolume: GDMLDivisionVolume -) { - val volume: GDMLGroup = divisionVolume.volumeref.resolve(context.root) - ?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved") - - //TODO add divisions - set(Name.EMPTY, volume(context, volume)) -} - -private fun volume( - context: GDMLTransformer, - group: GDMLGroup -): SolidGroup = SolidGroup().apply { - if (group is GDMLVolume) { - val solid: GDMLSolid = group.solidref.resolve(context.root) - ?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined") - - addSolidWithCaching(context, solid)?.apply { - context.configureSolid(this, group, solid) - } - - when (val vol: GDMLPlacement? = group.placement) { - is GDMLPhysVolume -> addPhysicalVolume(context, vol) - is GDMLDivisionVolume -> addDivisionVolume(context, vol) - } - } - - group.physVolumes.forEach { physVolume -> - addPhysicalVolume(context, physVolume) - } -} fun GDML.toVision(block: GDMLTransformer.() -> Unit = {}): SolidGroup { val context = GDMLTransformer(this).apply(block) - return context.finalize(volume(context, world)) + return context.result } /** diff --git a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt index c138fcc6..dba7e32f 100644 --- a/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/hep/dataforge/vision/gdml/GdmlOptimizer.kt @@ -18,72 +18,72 @@ expect class Counter() { fun incrementAndGet(): Int } -@DFExperimental -private class GdmlOptimizer() : VisionVisitor { - val logger = KotlinLogging.logger("SingleChildReducer") - - private operator fun Point3D?.plus(other: Point3D?): Point3D? = if (this == null && other == null) { - null - } else { - (this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0)) - } - - private fun Vision.updateFrom(other: Vision): Vision { - if (this is Solid && other is Solid) { - position += other.position - rotation += other.rotation - if (this.scale != null || other.scale != null) { - scaleX = scaleX.toDouble() * other.scaleX.toDouble() - scaleY = scaleY.toDouble() * other.scaleY.toDouble() - scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble() - } - other.properties?.sequence()?.forEach { (name, item) -> - if (properties?.getItem(name) == null) { - config[name] = item - } - } - } - return this - } - - private val depthCount = HashMap() - - override suspend fun visit(name: Name, vision: Vision) { - val depth = name.length - depthCount.getOrPut(depth) { Counter() }.incrementAndGet() - } - - override fun skip(name: Name, vision: Vision): Boolean = vision is Proxy.ProxyChild - - override suspend fun visitChildren(name: Name, group: VisionGroup) { - if (name == "volumes".toName()) return - if (group !is MutableVisionGroup) return - - val newChildren = group.children.entries.associate { (visionToken, vision) -> - //Reduce single child groups - if (vision is VisionGroup && vision !is Proxy && vision.children.size == 1) { - val (token, child) = vision.children.entries.first() - child.parent = null - if (token != visionToken) { - child.config["solidName"] = token.toString() - } - visionToken to child.updateFrom(vision) - } else { - visionToken to vision - } - } - if (newChildren != group.children) { - group.removeAll() - newChildren.forEach { (token, child) -> - group[token] = child - } - } - } +private fun Point3D?.safePlus(other: Point3D?): Point3D? = if (this == null && other == null) { + null +} else { + (this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0)) } -@DFExperimental -suspend fun SolidGroup.optimizeGdml(): Job = coroutineScope { - prototypes?.let { - VisionVisitor.visitTree(GdmlOptimizer(), this, it) - } ?: CompletableDeferred(Unit) -} \ No newline at end of file +internal fun Vision.updateFrom(other: Vision): Vision { + if (this is Solid && other is Solid) { + position = position.safePlus(other.position) + rotation = rotation.safePlus(other.rotation) + if (this.scale != null || other.scale != null) { + scaleX = scaleX.toDouble() * other.scaleX.toDouble() + scaleY = scaleY.toDouble() * other.scaleY.toDouble() + scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble() + } + other.properties?.sequence()?.forEach { (name, item) -> + if (properties?.getItem(name) == null) { + config[name] = item + } + } + } + return this +} +// +//@DFExperimental +//private class GdmlOptimizer() : VisionVisitor { +// val logger = KotlinLogging.logger("SingleChildReducer") +// +// private val depthCount = HashMap() +// +// override suspend fun visit(name: Name, vision: Vision) { +// val depth = name.length +// depthCount.getOrPut(depth) { Counter() }.incrementAndGet() +// } +// +// override fun skip(name: Name, vision: Vision): Boolean = vision is Proxy.ProxyChild +// +// override suspend fun visitChildren(name: Name, group: VisionGroup) { +// if (name == "volumes".toName()) return +// if (group !is MutableVisionGroup) return +// +// val newChildren = group.children.entries.associate { (visionToken, vision) -> +// //Reduce single child groups +// if (vision is VisionGroup && vision !is Proxy && vision.children.size == 1) { +// val (token, child) = vision.children.entries.first() +// child.parent = null +// if (token != visionToken) { +// child.config["solidName"] = token.toString() +// } +// visionToken to child.updateFrom(vision) +// } else { +// visionToken to vision +// } +// } +// if (newChildren != group.children) { +// group.removeAll() +// newChildren.forEach { (token, child) -> +// group[token] = child +// } +// } +// } +//} +// +//@DFExperimental +//suspend fun SolidGroup.optimizeGdml(): Job = coroutineScope { +// prototypes?.let { +// VisionVisitor.visitTree(GdmlOptimizer(), this, it) +// } ?: CompletableDeferred(Unit) +//} \ No newline at end of file