diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt index 6cb22e2a..2425e693 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt @@ -81,7 +81,11 @@ abstract class AbstractVisualObject(override val parent: VisualObject?) : Visual } private var _config: Config? = null - override val config: Config get() = _config ?: Config().also { _config = it } + override val config: Config + get() = _config ?: Config().also { config -> + _config = config + config.onChange(this, ::propertyChanged) + } override fun setProperty(name: Name, value: Any?) { config[name] = value @@ -120,6 +124,13 @@ open class VisualGroup(parent: VisualObject?) : AbstractVisual } } + override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) { + super.propertyChanged(name, before, after) + forEach { + it.propertyChanged(name, before, after) + } + } + private data class Listener(val owner: Any?, val callback: (Name?, T?) -> Unit) private val listeners = HashSet>() @@ -164,10 +175,12 @@ open class VisualGroup(parent: VisualObject?) : AbstractVisual * Get named child by name */ operator fun get(name: Name): T? = namedChildren[name] + /** * Get named child by string */ operator fun get(key: String): T? = namedChildren[key] + /** * Get an unnamed child */ diff --git a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt index ff1ef79a..fcaa90df 100644 --- a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt +++ b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt @@ -33,7 +33,7 @@ class RendererDemoView : View() { } } - var color by group.config.number(1530).int + var color by group.config.number(1530) GlobalScope.launch { val random = Random(111) diff --git a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt index fa3d12fc..b92cf102 100644 --- a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt +++ b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/visualGDML.kt @@ -45,10 +45,14 @@ private fun VisualGroup3D.addSolid( val aScale = solid.ascale() return when (solid) { is GDMLBox -> box(solid.x * lScale, solid.y * lScale, solid.z * lScale, name) - is GDMLTube -> cylinder(solid.rmax * lScale, solid.z * lScale, name) { - startAngle = solid.startphi * aScale - angle = solid.deltaphi * aScale - } + is GDMLTube -> tube( + solid.rmax * lScale, + solid.z * lScale, + solid.rmin * lScale, + solid.startphi * aScale, + solid.deltaphi * aScale, + name + ) is GDMLXtru -> extrude(name) { shape { solid.vertices.forEach { @@ -193,8 +197,7 @@ private fun volume( ?: context.templates.addSolid(context, solid, solid.name) { this.material = context.resolveColor(group, material, solid) } - val wrapper = Proxy3D(this,cachedSolid) - add(wrapper) + proxy(cachedSolid, solid.name) } when (val vol = group.placement) { diff --git a/dataforge-vis-spatial-gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt b/dataforge-vis-spatial-gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt index 503a29af..9a61c632 100644 --- a/dataforge-vis-spatial-gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt +++ b/dataforge-vis-spatial-gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt @@ -90,11 +90,11 @@ private class GDMLDemoApp : ApplicationBase() { launch { message("Converting GDML into DF-VIS format") } val visual = gdml.toVisual { lUnit = LUnit.CM -// acceptSolid = { solid -> -// !solid.name.startsWith("ecal") + acceptSolid = { solid -> + !solid.name.startsWith("ecal") // && !solid.name.startsWith("V") // && !solid.name.startsWith("U") -// } + } } launch { message("Rendering") } val output = three.output(canvas) diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt index d0846f36..156995ac 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt @@ -31,7 +31,12 @@ private class ThreeDemoApp : ApplicationBase() { } sphere(50.0) { x = 110 - detail = 200 + detail = 16 + } + tube(50, height = 10, innerRadius = 25, angle = PI) { + y = 110 + detail = 16 + rotationX = PI/4 } } diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeBoxFactory.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeBoxFactory.kt index 3f9bd2d5..3d09e744 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeBoxFactory.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeBoxFactory.kt @@ -3,12 +3,10 @@ package hep.dataforge.vis.spatial.three import hep.dataforge.vis.spatial.Box import hep.dataforge.vis.spatial.detail import info.laht.threekt.geometries.BoxBufferGeometry -import kotlin.math.pow object ThreeBoxFactory : MeshThreeFactory(Box::class) { override fun buildGeometry(obj: Box) = - obj.detail?.let { - val segments = it.toDouble().pow(1.0 / 3.0).toInt() - BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize, segments, segments, segments) + obj.detail?.let { detail -> + BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize, detail, detail, detail) } ?: BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize) } \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt index 390916d6..086bf269 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt @@ -36,6 +36,7 @@ interface ThreeFactory { * Update position, rotation and visibility */ internal fun Object3D.updatePosition(obj: VisualObject3D) { + visible = obj.visible ?: true position.set(obj.x, obj.y, obj.z) setRotationFromEuler(obj.euler) scale.set(obj.scaleX, obj.scaleY, obj.scaleZ) @@ -125,7 +126,7 @@ abstract class MeshThreeFactory(override val type: KClass { three.buildObject3D(obj.template) as Mesh } - val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material) - //val mesh = templateMesh.clone() + //val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material) + val mesh = templateMesh.clone() mesh.updatePosition(obj) return mesh diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeSphereFactory.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeSphereFactory.kt index 074eb021..d81bdcb9 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeSphereFactory.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeSphereFactory.kt @@ -4,20 +4,18 @@ import hep.dataforge.vis.spatial.Sphere import hep.dataforge.vis.spatial.detail import info.laht.threekt.core.BufferGeometry import info.laht.threekt.geometries.SphereBufferGeometry -import kotlin.math.pow object ThreeSphereFactory : MeshThreeFactory(Sphere::class) { override fun buildGeometry(obj: Sphere): BufferGeometry { - return obj.detail?.let { - val segments = it.toDouble().pow(0.5).toInt() + return obj.detail?.let {detail -> SphereBufferGeometry( radius = obj.radius, phiStart = obj.phiStart, phiLength = obj.phi, thetaStart = obj.thetaStart, thetaLength = obj.theta, - widthSegments = segments, - heightSegments = segments + widthSegments = detail, + heightSegments = detail ) }?: SphereBufferGeometry( radius = obj.radius, diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt index 12db537b..0f720a9e 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt @@ -7,9 +7,9 @@ class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize //TODO add helper for color configuration override fun toGeometry(geometryBuilder: GeometryBuilder) { - val dx = xSize.toDouble() / 2 - val dy = ySize.toDouble() / 2 - val dz = zSize.toDouble() / 2 + val dx = xSize.toFloat() / 2 + val dy = ySize.toFloat() / 2 + val dz = zSize.toFloat() / 2 val node1 = Point3D(-dx, -dy, -dz) val node2 = Point3D(dx, -dy, -dz) val node3 = Point3D(dx, dy, -dz) diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Cylinder.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Cylinder.kt index e9471f61..f177c5eb 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Cylinder.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Cylinder.kt @@ -1,17 +1,19 @@ package hep.dataforge.vis.spatial import hep.dataforge.vis.common.VisualObject -import hep.dataforge.vis.common.number import kotlin.math.PI /** * A cylinder or cut cone segment */ -class Cylinder(parent: VisualObject?, var radius: Number, var height: Number) : VisualLeaf3D(parent) { - var upperRadius by number(radius) - var startAngle by number(0.0) - var angle by number(2 * PI) -} +class Cylinder( + parent: VisualObject?, + var radius: Number, + var height: Number, + var upperRadius: Number = radius, + var startAngle: Number = 0f, + var angle: Number = 2 * PI +) : VisualLeaf3D(parent) fun VisualGroup3D.cylinder( r: Number, diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt index c32118d4..34bca242 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt @@ -46,19 +46,6 @@ class Extruded(parent: VisualObject?) : VisualLeaf3D(parent), Shape { //TODO send invalidation signal } - private fun GeometryBuilder.cap(shape: List) { - //FIXME won't work for non-convex shapes - val center = Point3D( - shape.map { it.x.toDouble() }.average(), - shape.map { it.y.toDouble() }.average(), - shape.map { it.z.toDouble() }.average() - ) - for(i in 0 until (shape.size - 1)){ - face(shape[i], shape[i+1], center, null) - } - face(shape.last(), shape.first(),center,null) - } - override fun toGeometry(geometryBuilder: GeometryBuilder) { val shape: Shape2D = shape diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt index 351c8ead..a3c239a5 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt @@ -8,8 +8,8 @@ data class Point2D(val x: Number, val y: Number) : MetaRepr { "y" to y } - companion object{ - fun from(meta: Meta): Point2D{ + companion object { + fun from(meta: Meta): Point2D { return Point2D(meta["x"].number ?: 0, meta["y"].number ?: 0) } } @@ -22,12 +22,12 @@ data class Point3D(val x: Number, val y: Number, val z: Number) : MetaRepr { "z" to z } - companion object{ - fun from(meta: Meta): Point3D{ + companion object { + fun from(meta: Meta): Point3D { return Point3D(meta["x"].number ?: 0, meta["y"].number ?: 0, meta["y"].number ?: 0) } - val zero = Point3D(0,0,0) + val zero = Point3D(0, 0, 0) } } @@ -42,7 +42,7 @@ interface GeometryBuilder { * @param normal optional external normal to the face * @param meta optional additional platform-specific parameters like color or texture index */ - fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta = EmptyMeta) + fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D? = null, meta: Meta = EmptyMeta) fun build(): T } @@ -61,4 +61,17 @@ fun GeometryBuilder<*>.face4( interface Shape : VisualObject3D { fun toGeometry(geometryBuilder: GeometryBuilder) +} + +fun GeometryBuilder.cap(shape: List, normal: Point3D? = null) { + //FIXME won't work for non-convex shapes + val center = Point3D( + shape.map { it.x.toDouble() }.average(), + shape.map { it.y.toDouble() }.average(), + shape.map { it.z.toDouble() }.average() + ) + for (i in 0 until (shape.size - 1)) { + face(shape[i], shape[i + 1], center, normal) + } + face(shape.last(), shape.first(), center, normal) } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Tube.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Tube.kt new file mode 100644 index 00000000..a0cfa6ee --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Tube.kt @@ -0,0 +1,131 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.vis.common.VisualObject +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin + +/** + * Stright tube segment + */ +class Tube( + parent: VisualObject?, + var radius: Float, + var height: Float, + var innerRadius: Float = 0f, + var startAngle: Float = 0f, + var angle: Float = PI2 +) : VisualLeaf3D(parent), Shape { + + init { + require(radius > 0) + require(height > 0) + require(innerRadius >= 0) + require(startAngle >= 0) + require(angle in (0f..(PI2))) + } + + override fun toGeometry(geometryBuilder: GeometryBuilder) { + val segments = detail ?: 8 + require(segments >= 4) { "The number of segments in tube is too small" } + val angleStep = angle / (segments - 1) + + fun shape(r: Float, z: Float): List { + return (0 until segments).map { i -> + Point3D(r * cos(startAngle + angleStep * i), r * sin(startAngle + angleStep * i), z) + } + } + + geometryBuilder.apply { + + //creating shape in x-y plane with z = 0 + val bottomOuterPoints = shape(radius, 0f) + val upperOuterPoints = shape(radius, height) + //outer face + (1 until segments).forEach { + face4(bottomOuterPoints[it - 1], bottomOuterPoints[it], upperOuterPoints[it], upperOuterPoints[it - 1]) + } + + if (angle == PI2) { + face4(bottomOuterPoints.last(), bottomOuterPoints[0], upperOuterPoints[0], upperOuterPoints.last()) + } + if (innerRadius == 0f) { + val zeroBottom = Point3D(0f, 0f, 0f) + val zeroTop = Point3D(0f, 0f, height) + (1 until segments).forEach { + face(bottomOuterPoints[it - 1], zeroBottom, bottomOuterPoints[it]) + face(upperOuterPoints[it - 1], upperOuterPoints[it], zeroTop) + } + if (angle == PI2) { + face(bottomOuterPoints.last(), zeroBottom, bottomOuterPoints[0]) + face(upperOuterPoints.last(), upperOuterPoints[0], zeroTop) + } else { + face4(zeroTop, zeroBottom, bottomOuterPoints[0], upperOuterPoints[0]) + face4(zeroTop, zeroBottom, bottomOuterPoints.last(), upperOuterPoints.last()) + } + } else { + val bottomInnerPoints = shape(innerRadius, 0f) + val upperInnerPoints = shape(innerRadius, height) + //outer face + (1 until segments).forEach { + // inner surface + face4( + bottomInnerPoints[it], + bottomInnerPoints[it - 1], + upperInnerPoints[it - 1], + upperInnerPoints[it] + ) + //bottom cup + face4( + bottomInnerPoints[it - 1], + bottomInnerPoints[it], + bottomOuterPoints[it], + bottomOuterPoints[it - 1] + ) + //upper cup + face4( + upperInnerPoints[it], + upperInnerPoints[it - 1], + upperOuterPoints[it - 1], + upperOuterPoints[it] + ) + } + if (angle == PI2) { + face4(bottomInnerPoints[0], bottomInnerPoints.last(), upperInnerPoints.last(), upperInnerPoints[0]) + face4( + bottomInnerPoints.last(), + bottomInnerPoints[0], + bottomOuterPoints[0], + bottomOuterPoints.last() + ) + face4(upperInnerPoints[0], upperInnerPoints.last(), upperOuterPoints.last(), upperOuterPoints[0]) + } else{ + face4(bottomInnerPoints[0],bottomOuterPoints[0],upperOuterPoints[0],upperInnerPoints[0]) + face4(bottomOuterPoints.last(),bottomInnerPoints.last(),upperInnerPoints.last(),upperOuterPoints.last()) + } + } + } + } +} + +fun VisualGroup3D.tube( + r: Number, + height: Number, + innerRadius: Number = 0f, + startAngle: Number = 0f, + angle: Number = 2 * PI, + name: String? = null, + block: Tube.() -> Unit = {} +): Tube { + val tube = Tube( + this, + r.toFloat(), + height.toFloat(), + innerRadius.toFloat(), + startAngle.toFloat(), + angle.toFloat() + ).apply( + block + ) + return tube.also { set(name, it) } +} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt index 691d1197..7d4ac372 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt @@ -105,10 +105,10 @@ var VisualObject3D.rotationOrder: RotationOrder /** - * Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default + * Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default. Not inherited */ var VisualObject3D.detail: Int? - get() = getProperty(DETAIL_KEY).int + get() = getProperty(DETAIL_KEY,false).int set(value) = setProperty(DETAIL_KEY, value) var VisualObject3D.material: Meta? diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/World.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/World.kt index 639ae56f..0743357d 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/World.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/World.kt @@ -1,5 +1,7 @@ package hep.dataforge.vis.spatial +import kotlin.math.PI + object World { const val CAMERA_INITIAL_DISTANCE = -500.0 const val CAMERA_INITIAL_X_ANGLE = -50.0 @@ -7,4 +9,6 @@ object World { const val CAMERA_INITIAL_Z_ANGLE = -210.0 const val CAMERA_NEAR_CLIP = 0.1 const val CAMERA_FAR_CLIP = 10000.0 -} \ No newline at end of file +} + +const val PI2: Float = 2 * PI.toFloat() \ No newline at end of file