diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt index 1f42d87c..780a6874 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt @@ -1,7 +1,5 @@ package hep.dataforge.vis.common -import kotlin.math.max - /** * Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt */ @@ -177,11 +175,6 @@ object Colors { const val yellow = 0xFFFF00 const val yellowgreen = 0x9ACD32 - fun rgbToString(rgb: Int): String { - val string = rgb.toString(16).padStart(6, '0') - return "#" + string.substring(max(0, string.length - 6)) - } - fun rgbToString(red: UByte, green: UByte, blue: UByte): String { fun colorToString(color: UByte): String{ return color.toString(16).padStart(2,'0') diff --git a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/GDMLTransformer.kt b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/GDMLTransformer.kt index 6b11338c..698c5ada 100644 --- a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/GDMLTransformer.kt +++ b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/GDMLTransformer.kt @@ -5,7 +5,6 @@ import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.buildMeta import hep.dataforge.names.Name import hep.dataforge.names.toName -import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.applyStyle import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY @@ -53,7 +52,7 @@ class GDMLTransformer(val root: GDML) { val styleName = "material[${material.name}]" obj.useStyle(styleName){ - COLOR_KEY to Colors.rgbToString(random.nextInt(0, Int.MAX_VALUE)) + COLOR_KEY to random.nextInt(0, Int.MAX_VALUE) "gdml.material" put material.name } diff --git a/dataforge-vis-spatial/build.gradle.kts b/dataforge-vis-spatial/build.gradle.kts index 67352978..203cd2b7 100644 --- a/dataforge-vis-spatial/build.gradle.kts +++ b/dataforge-vis-spatial/build.gradle.kts @@ -22,6 +22,7 @@ kotlin { jvmMain { dependencies { api("org.fxyz3d:fxyz3d:0.5.2") + api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}") implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") } } diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt index 645c0cf5..68913c55 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Material3D.kt @@ -3,7 +3,6 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.* import hep.dataforge.names.asName import hep.dataforge.names.plus -import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY @@ -20,6 +19,7 @@ class Material3D(override val config: Config) : Specific { val MATERIAL_KEY = "material".asName() val COLOR_KEY = MATERIAL_KEY + "color" + val SPECULAR_COLOR = MATERIAL_KEY + "specularColor" val OPACITY_KEY = MATERIAL_KEY + "opacity" } @@ -29,9 +29,18 @@ fun VisualObject.color(rgb: String) { setProperty(COLOR_KEY, rgb) } -fun VisualObject.color(rgb: Int) = color(Colors.rgbToString(rgb)) +fun VisualObject.color(rgb: Int) { + setProperty(COLOR_KEY, rgb) +} -fun VisualObject.color(r: UByte, g: UByte, b: UByte) = color(Colors.rgbToString(r, g, b)) +fun VisualObject.color(r: UByte, g: UByte, b: UByte) = setProperty( + COLOR_KEY, + buildMeta { + "red" put r.toInt() + "green" put g.toInt() + "blue" put b.toInt() + } +) var VisualObject.color: String? get() = getProperty(COLOR_KEY).string diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt index f762a48a..26a9e820 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Sphere.kt @@ -26,7 +26,16 @@ class Sphere( override var scale: Point3D? = null override fun toGeometry(geometryBuilder: GeometryBuilder) { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. + TODO("not implemented") +// val segments = this.detail ?: 8 +// require(segments >= 4) { "The detail for sphere must be >= 4" } +// val phiStep = phi / segments +// val thetaStep = theta / segments +// for (i in 1 until segments - 1) { +// for (j in 0 until segments - 1) { +// val point1 = Point3D() +// } +// } } } diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt index 4b4309d8..4ac3d4a8 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt @@ -11,6 +11,7 @@ import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.materials.MeshPhongMaterial import info.laht.threekt.math.Color import info.laht.threekt.objects.Mesh +import kotlin.math.max object ThreeMaterials { @@ -46,6 +47,11 @@ object ThreeMaterials { } } + fun rgbToString(rgb: Int): String { + val string = rgb.toString(16).padStart(6, '0') + return "#" + string.substring(max(0, string.length - 6)) + } + } /** @@ -53,11 +59,11 @@ object ThreeMaterials { */ fun MetaItem<*>.color(): Color { return when (this) { - is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) { - Color(this.value.string) - } else { + is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) { val int = value.number.toInt() Color(int) + } else { + Color(this.value.string) } is MetaItem.NodeItem -> { Color( diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/FXDemoApp.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/FXDemoApp.kt new file mode 100644 index 00000000..489b536c --- /dev/null +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/FXDemoApp.kt @@ -0,0 +1,211 @@ +package hep.dataforge.vis.spatial.demo + +import hep.dataforge.vis.common.Colors +import hep.dataforge.vis.spatial.* +import javafx.stage.Stage +import kotlinx.coroutines.* +import kotlinx.coroutines.javafx.JavaFx +import tornadofx.* +import kotlin.math.PI +import kotlin.math.cos +import kotlin.math.sin +import kotlin.random.Random + +class FXDemoApp : App(FXDemoGrid::class) { + + val view: FXDemoGrid by inject() + + override fun start(stage: Stage) { + super.start(stage) + view.run { + demo("shapes", "Basic shapes") { + box(100.0, 100.0, 100.0) { + z = 110.0 + } + sphere(50.0) { + x = 110 + detail = 16 + } + tube(50, height = 10, innerRadius = 25, angle = PI) { + y = 110 + detail = 16 + rotationX = PI / 4 + } + } + + demo("dynamic", "Dynamic properties") { + val group = group { + box(100, 100, 100) { + z = 110.0 + } + + box(100, 100, 100) { + visible = false + x = 110.0 + //override color for this cube + color(1530) + + GlobalScope.launch(Dispatchers.JavaFx) { + while (isActive) { + delay(500) + visible = !(visible ?: false) + } + } + } + } + + GlobalScope.launch(Dispatchers.JavaFx) { + val random = Random(111) + while (isActive) { + delay(1000) + group.color(random.nextInt(0, Int.MAX_VALUE)) + } + } + } + + demo("rotation", "Rotations") { + box(100, 100, 100) + group { + x = 200 + rotationY = PI / 4 + box(100, 100, 100) { + rotationZ = PI / 4 + color(Colors.red) + } + } + } + + demo("extrude", "extruded shape") { + extrude { + shape { + polygon(8, 50) + } + for (i in 0..100) { + layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i)) + } + color(Colors.teal) + } + } + +// demo("CSG.simple", "CSG operations") { +// composite(CompositeType.UNION) { +// box(100, 100, 100) { +// z = 50 +// } +// sphere(50) +// material { +// color(Colors.lightgreen) +// opacity = 0.3f +// } +// } +// composite(CompositeType.INTERSECT) { +// y = 300 +// box(100, 100, 100) { +// z = 50 +// } +// sphere(50) +// color(Colors.red) +// } +// composite(CompositeType.SUBTRACT) { +// y = -300 +// box(100, 100, 100) { +// z = 50 +// } +// sphere(50) +// color(Colors.blue) +// } +// } + +// demo("CSG.custom", "CSG with manually created object") { +// intersect { +// box(100, 100, 100) +// tube(60, 10) { +// detail = 180 +// } +// } +// } + + demo("lines", "Track / line segments") { + sphere(100) { + color(Colors.blue) + detail = 50 + opacity = 0.4 + } + repeat(20) { + polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) { + thickness = 208.0 + rotationX = it * PI2 / 20 + color(Colors.green) + //rotationY = it * PI2 / 20 + } + } + } + +// demo("dynamicBox", "Dancing boxes") { +// val boxes = (-10..10).flatMap { i -> +// (-10..10).map { j -> +// varBox(10, 10, 0, name = "cell_${i}_${j}") { +// x = i * 10 +// y = j * 10 +// value = 128 +// setProperty(EDGES_ENABLED_KEY, false) +// setProperty(WIREFRAME_ENABLED_KEY, false) +// } +// } +// } +// GlobalScope.launch { +// while (isActive) { +// delay(200) +// boxes.forEach { box -> +// box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255) +// } +// } +// } +// } + + } + } +} + +//class SpatialDemoView : View() { +// private val plugin = Global.plugins.fetch(FX3DPlugin) +// private val canvas = FXCanvas3D(plugin) +// +// override val root: Parent = borderpane { +// center = canvas.root +// } +// +// lateinit var group: VisualGroup3D +// +// init { +// canvas.render { +// group = group { +// box(100, 100, 100) +// box(100, 100, 100) { +// x = 110.0 +// color(Colors.blue) +// } +// } +// } +// +// //var color by group.config.number(1530) +// +// GlobalScope.launch { +// val random = Random(111) +// while (isActive) { +// delay(1000) +// group.color(random.nextInt(0, Int.MAX_VALUE)) +// } +// } +// +//// canvas.apply { +//// angleY = -30.0 +//// angleX = -15.0 +//// } +// } +//} + + +fun main() { + launch() +} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/FXDemoGrid.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/FXDemoGrid.kt new file mode 100644 index 00000000..e6b23039 --- /dev/null +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/FXDemoGrid.kt @@ -0,0 +1,58 @@ +package hep.dataforge.vis.spatial.demo + +import hep.dataforge.context.Global +import hep.dataforge.meta.Meta +import hep.dataforge.meta.buildMeta +import hep.dataforge.names.Name +import hep.dataforge.names.toName +import hep.dataforge.output.OutputManager +import hep.dataforge.output.Renderer +import hep.dataforge.vis.common.VisualObject +import hep.dataforge.vis.spatial.VisualGroup3D +import hep.dataforge.vis.spatial.fx.FX3DPlugin +import hep.dataforge.vis.spatial.fx.FXCanvas3D +import hep.dataforge.vis.spatial.render +import javafx.collections.FXCollections +import javafx.scene.Parent +import javafx.scene.control.Tab +import tornadofx.* +import kotlin.reflect.KClass + +class FXDemoGrid : View(), OutputManager { + private val outputs = FXCollections.observableHashMap() + + override val root: Parent = borderpane { + center = tabpane { + tabs.bind(outputs) { key: Name, value: FXCanvas3D -> + Tab(key.toString(), value.root) + } + } + } + + private val fx3d = Global.plugins.fetch(FX3DPlugin) + + @Suppress("UNCHECKED_CAST") + override fun get(type: KClass, name: Name, stage: Name, meta: Meta): Renderer { + return outputs.getOrPut(name) { + if (type != VisualObject::class) kotlin.error("Supports only DisplayObject") + val customMeta = buildMeta(meta) { + "minSize" put 500 + "axis" put { + "size" put 500 + } + } + val output = FXCanvas3D(fx3d, customMeta) + + output + } as Renderer + } + +} + +fun FXDemoGrid.demo(name: String, title: String = name, block: VisualGroup3D.() -> Unit) { + val meta = buildMeta { + "title" put title + } + val output = get(VisualObject::class, name.toName(), meta = meta) + output.render(action = block) +} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/SpatialDemoApp.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/SpatialDemoApp.kt deleted file mode 100644 index 0a9fc4b7..00000000 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/demo/SpatialDemoApp.kt +++ /dev/null @@ -1,59 +0,0 @@ -package hep.dataforge.vis.spatial.demo - -import hep.dataforge.context.Global -import hep.dataforge.meta.number -import hep.dataforge.vis.spatial.* -import hep.dataforge.vis.spatial.fx.Canvas3D -import hep.dataforge.vis.spatial.fx.FX3DPlugin -import javafx.scene.Parent -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.delay -import kotlinx.coroutines.isActive -import kotlinx.coroutines.launch -import tornadofx.* -import kotlin.random.Random - -class SpatialDemoApp: App(SpatialDemoView::class) - -class SpatialDemoView: View(){ - private val plugin = Global.plugins.fetch(FX3DPlugin) - private val canvas = Canvas3D(plugin) - - override val root: Parent = borderpane { - center = canvas.root - } - - lateinit var group: VisualGroup3D - - init { - canvas.render { - box(100,100,100) - group = group { - box(100,100,100) - box(100,100,100) { - x = 110.0 - } - } - } - - var color by group.config.number(1530) - - GlobalScope.launch { - val random = Random(111) - while (isActive) { - delay(1000) - color = random.nextInt(0, Int.MAX_VALUE) - } - } - - canvas.apply { - angleY = -30.0 - angleX = -15.0 - } - } -} - - -fun main() { - launch() -} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/DisplayObjectFXBinding.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/DisplayObjectFXBinding.kt deleted file mode 100644 index b0d66ed8..00000000 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/DisplayObjectFXBinding.kt +++ /dev/null @@ -1,42 +0,0 @@ -package hep.dataforge.vis.spatial.fx - -import hep.dataforge.meta.* -import hep.dataforge.names.Name -import hep.dataforge.names.toName -import hep.dataforge.vis.common.VisualObject -import javafx.beans.binding.ObjectBinding -import tornadofx.* - -/** - * A caching binding collection for [VisualObject] properties - */ -class DisplayObjectFXBinding(val obj: VisualObject) { - private val binndings = HashMap?>>() - - init { - obj.onPropertyChange(this) { name, _, _ -> - binndings[name]?.invalidate() - } - } - - operator fun get(key: Name): ObjectBinding?> { - return binndings.getOrPut(key) { - object : ObjectBinding?>() { - override fun computeValue(): MetaItem<*>? = obj.getProperty(key) - } - } - } - - operator fun get(key: String) = get(key.toName()) -} - -fun ObjectBinding?>.value() = this.objectBinding { it.value } -fun ObjectBinding?>.string() = this.stringBinding { it.string } -fun ObjectBinding?>.number() = this.objectBinding { it.number } -fun ObjectBinding?>.double() = this.objectBinding { it.double } -fun ObjectBinding?>.float() = this.objectBinding { it.number?.toFloat() } -fun ObjectBinding?>.int() = this.objectBinding { it.int } -fun ObjectBinding?>.long() = this.objectBinding { it.long } -fun ObjectBinding?>.node() = this.objectBinding { it.node } - -fun ObjectBinding?>.transform(transform: (MetaItem<*>) -> T) = this.objectBinding { it?.let(transform) } diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FX3DPlugin.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FX3DPlugin.kt index 1a1aa430..40f2a435 100644 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FX3DPlugin.kt +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FX3DPlugin.kt @@ -12,6 +12,8 @@ import javafx.scene.shape.Shape3D import javafx.scene.transform.Rotate import org.fxyz3d.shapes.composites.PolyLine3D import org.fxyz3d.shapes.primitives.CuboidMesh +import org.fxyz3d.shapes.primitives.SpheroidMesh +import kotlin.math.PI import kotlin.reflect.KClass class FX3DPlugin : AbstractPlugin() { @@ -33,13 +35,27 @@ class FX3DPlugin : AbstractPlugin() { as FX3DFactory? } - fun buildNode(obj: VisualObject3D): Node? { - val binding = DisplayObjectFXBinding(obj) + fun buildNode(obj: VisualObject3D): Node { + val binding = VisualObjectFXBinding(obj) return when (obj) { is Proxy -> proxyFactory(obj, binding) - is VisualGroup3D -> Group(obj.filterIsInstance().map { buildNode(it) }) + is VisualGroup3D -> { + Group(obj.children.mapNotNull { (token, obj) -> + (obj as? VisualObject3D)?.let { + buildNode(it).apply { + properties["name"] = token.toString() + } + } + }) + } is Composite -> compositeFactory(obj, binding) is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble()) + is Sphere -> if (obj.phi == PI2 && obj.theta == PI.toFloat()) { + //use sphere for orb + SpheroidMesh(obj.detail ?: 16, obj.radius.toDouble(), obj.radius.toDouble()) + } else { + FXShapeFactory(obj, binding) + } is PolyLine -> PolyLine3D( obj.points.map { it.point }, obj.thickness.toFloat(), @@ -55,23 +71,23 @@ class FX3DPlugin : AbstractPlugin() { } } }.apply { - translateXProperty().bind(binding[VisualObject3D.xPos].float()) - translateYProperty().bind(binding[VisualObject3D.yPos].float()) - translateZProperty().bind(binding[VisualObject3D.zPos].float()) - scaleXProperty().bind(binding[VisualObject3D.xScale].float()) - scaleYProperty().bind(binding[VisualObject3D.yScale].float()) - scaleZProperty().bind(binding[VisualObject3D.zScale].float()) + translateXProperty().bind(binding[VisualObject3D.xPos].float(obj.x.toFloat())) + translateYProperty().bind(binding[VisualObject3D.yPos].float(obj.y.toFloat())) + translateZProperty().bind(binding[VisualObject3D.zPos].float(obj.z.toFloat())) + scaleXProperty().bind(binding[VisualObject3D.xScale].float(obj.scaleX.toFloat())) + scaleYProperty().bind(binding[VisualObject3D.yScale].float(obj.scaleY.toFloat())) + scaleZProperty().bind(binding[VisualObject3D.zScale].float(obj.scaleZ.toFloat())) val rotateX = Rotate(0.0, Rotate.X_AXIS).apply { - angleProperty().bind(binding[VisualObject3D.xRotation].float()) + angleProperty().bind(binding[VisualObject3D.xRotation].float(obj.rotationX.toFloat())) } val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply { - angleProperty().bind(binding[VisualObject3D.yRotation].float()) + angleProperty().bind(binding[VisualObject3D.yRotation].float(obj.rotationY.toFloat())) } val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply { - angleProperty().bind(binding[VisualObject3D.zRotation].float()) + angleProperty().bind(binding[VisualObject3D.zRotation].float(obj.rotationZ.toFloat())) } when (obj.rotationOrder) { @@ -84,7 +100,9 @@ class FX3DPlugin : AbstractPlugin() { } if (this is Shape3D) { - materialProperty().bind(binding[Material3D.MATERIAL_KEY].transform { it.material() }) + materialProperty().bind(binding[Material3D.MATERIAL_KEY].transform { + it.material() + }) } } } @@ -104,7 +122,7 @@ interface FX3DFactory { val type: KClass - operator fun invoke(obj: T, binding: DisplayObjectFXBinding): Node + operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node companion object { const val TYPE = "fx3DFactory" diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/Canvas3D.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXCanvas3D.kt similarity index 70% rename from dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/Canvas3D.kt rename to dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXCanvas3D.kt index fd6cbc40..7c7663e0 100644 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/Canvas3D.kt +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXCanvas3D.kt @@ -2,15 +2,13 @@ package hep.dataforge.vis.spatial.fx import hep.dataforge.context.Context import hep.dataforge.context.ContextAware -import hep.dataforge.meta.EmptyMeta -import hep.dataforge.meta.Meta +import hep.dataforge.meta.* import hep.dataforge.output.Renderer import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.World.CAMERA_FAR_CLIP import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_DISTANCE import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_X_ANGLE import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Y_ANGLE -import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Z_ANGLE import hep.dataforge.vis.spatial.World.CAMERA_NEAR_CLIP import javafx.event.EventHandler import javafx.scene.* @@ -20,19 +18,22 @@ import javafx.scene.input.MouseEvent import javafx.scene.input.ScrollEvent import javafx.scene.paint.Color import org.fxyz3d.scene.Axes -import org.fxyz3d.scene.CubeWorld import org.fxyz3d.utils.CameraTransformer import tornadofx.* -class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) : +class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) : Fragment(), Renderer, ContextAware { override val context: Context get() = plugin.context - val world = CubeWorld(true) + + val world = Group() + val axes = Axes().also { - it.setHeight(AXIS_LENGTH) - it.setRadius(LINE_WIDTH) + it.setHeight(meta["axis.size"].double ?: AXIS_LENGTH) + it.setRadius(meta["axis.width"].double ?: LINE_WIDTH) + it.isVisible = meta["axis.visible"].boolean ?: (meta["axis"] != null) world.add(it) + } private val camera = PerspectiveCamera().apply { @@ -41,43 +42,32 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) : translateZ = CAMERA_INITIAL_DISTANCE } - private val cameraShift = CameraTransformer().apply { - val cameraFlip = CameraTransformer() - cameraFlip.children.add(camera) - cameraFlip.setRotateZ(180.0) - children.add(cameraFlip) + val cameraTransform = CameraTransformer().also { + it.add(camera) } - val translationXProperty get() = cameraShift.t.xProperty() + val translationXProperty get() = cameraTransform.t.xProperty() var translateX by translationXProperty - val translationYProperty get() = cameraShift.t.yProperty() + val translationYProperty get() = cameraTransform.t.yProperty() var translateY by translationYProperty - val translationZProperty get() = cameraShift.t.zProperty() + val translationZProperty get() = cameraTransform.t.zProperty() var translateZ by translationZProperty - private val cameraRotation = CameraTransformer().apply { - children.add(cameraShift) - ry.angle = CAMERA_INITIAL_Y_ANGLE - rx.angle = CAMERA_INITIAL_X_ANGLE - rz.angle = CAMERA_INITIAL_Z_ANGLE - } - - val rotationXProperty get() = cameraRotation.rx.angleProperty() + val rotationXProperty get() = cameraTransform.rx.angleProperty() var angleX by rotationXProperty - val rotationYProperty get() = cameraRotation.ry.angleProperty() + val rotationYProperty get() = cameraTransform.ry.angleProperty() var angleY by rotationYProperty - val rotationZProperty get() = cameraRotation.rz.angleProperty() + val rotationZProperty get() = cameraTransform.rz.angleProperty() var angleZ by rotationZProperty - override val root = borderpane { center = SubScene( - Group(world, cameraRotation).apply { DepthTest.ENABLE }, + Group(world, cameraTransform).apply { DepthTest.ENABLE }, 1024.0, 768.0, true, SceneAntialiasing.BALANCED - ).also {scene-> + ).also { scene -> scene.fill = Color.GREY scene.camera = camera id = "canvas" @@ -92,11 +82,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) : if (event.isControlDown) { when (event.code) { KeyCode.Z -> { - cameraShift.t.x = 0.0 - cameraShift.t.y = 0.0 + translateX = 0.0 + translateY = 0.0 camera.translateZ = CAMERA_INITIAL_DISTANCE - cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE - cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE + angleY = CAMERA_INITIAL_Y_ANGLE + angleX = CAMERA_INITIAL_X_ANGLE } KeyCode.X -> axes.isVisible = !axes.isVisible // KeyCode.S -> snapshot() @@ -153,13 +143,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) : } if (me.isPrimaryButtonDown) { - cameraRotation.ry.angle = - cameraRotation.ry.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED - cameraRotation.rx.angle = - cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED + angleY += mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED + angleX += mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED } else if (me.isSecondaryButtonDown) { - cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED - cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED + translateX -= mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED + translateY -= mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED } } scene.onScroll = EventHandler { event -> @@ -171,11 +159,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) : override fun render(obj: VisualObject3D, meta: Meta) { val node = plugin.buildNode(obj) ?: kotlin.error("Can't render FX node for object $obj") - world.add(node) + world.children.add(node) } companion object { - private const val AXIS_LENGTH = 2000.0 + private const val AXIS_LENGTH = 400.0 private const val CONTROL_MULTIPLIER = 0.1 private const val SHIFT_MULTIPLIER = 10.0 private const val MOUSE_SPEED = 0.1 diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXCompositeFactory.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXCompositeFactory.kt index e5efbf78..1b11f2c0 100644 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXCompositeFactory.kt +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXCompositeFactory.kt @@ -14,7 +14,7 @@ class FXCompositeFactory(val plugin: FX3DPlugin) : override val type: KClass get() = Composite::class - override fun invoke(obj: Composite, binding: DisplayObjectFXBinding): Node { + override fun invoke(obj: Composite, binding: VisualObjectFXBinding): Node { val first = plugin.buildNode(obj.first) as? MeshView ?: error("Can't build node") val second = plugin.buildNode(obj.second) as? MeshView ?: error("Can't build node") val firstCSG = MeshUtils.mesh2CSG(first) diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXConvexFactory.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXConvexFactory.kt index 386cdf31..aeeb2cac 100644 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXConvexFactory.kt +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXConvexFactory.kt @@ -11,7 +11,7 @@ import kotlin.reflect.KClass object FXConvexFactory : FX3DFactory { override val type: KClass get() = Convex::class - override fun invoke(obj: Convex, binding: DisplayObjectFXBinding): Node { + override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node { val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage()) return hull.toNode() } diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXMaterials.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXMaterials.kt index 174c3559..2237a46b 100644 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXMaterials.kt +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXMaterials.kt @@ -12,7 +12,7 @@ import javafx.scene.paint.PhongMaterial object FXMaterials { val RED = PhongMaterial().apply { diffuseColor = Color.DARKRED - specularColor = Color.RED + specularColor = Color.WHITE } val WHITE = PhongMaterial().apply { @@ -22,7 +22,7 @@ object FXMaterials { val GREY = PhongMaterial().apply { diffuseColor = Color.DARKGREY - specularColor = Color.GREY + specularColor = Color.WHITE } val BLUE = PhongMaterial(Color.BLUE) @@ -34,14 +34,14 @@ object FXMaterials { */ fun MetaItem<*>.color(opacity: Double = 1.0): Color { return when (this) { - is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) { - Color.web(this.value.string) - } else { + is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) { val int = value.number.toInt() val red = int and 0x00ff0000 shr 16 val green = int and 0x0000ff00 shr 8 val blue = int and 0x000000ff Color.rgb(red, green, blue) + } else { + Color.web(this.value.string) } is MetaItem.NodeItem -> { Color.rgb( @@ -63,9 +63,8 @@ fun MetaItem<*>?.material(): Material { is MetaItem.ValueItem -> PhongMaterial(color()) is MetaItem.NodeItem -> PhongMaterial().apply { val opacity = node["opacity"].double ?: 1.0 - (node["color"] ?: this@material).let { diffuseColor = it.color(opacity) } - node["specularColor"]?.let { specularColor = it.color(opacity) } - + diffuseColor = node["color"]?.color(opacity) ?: Color.DARKGREY + specularColor = node["specularColor"]?.color(opacity) ?: Color.WHITE } } } diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXProxyFactory.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXProxyFactory.kt index d42362b2..7a7c11da 100644 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXProxyFactory.kt +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXProxyFactory.kt @@ -1,15 +1,50 @@ package hep.dataforge.vis.spatial.fx +import hep.dataforge.names.Name +import hep.dataforge.names.isEmpty +import hep.dataforge.names.startsWith +import hep.dataforge.names.toName +import hep.dataforge.vis.common.VisualObject +import hep.dataforge.vis.spatial.Material3D import hep.dataforge.vis.spatial.Proxy +import javafx.scene.Group import javafx.scene.Node +import javafx.scene.shape.Shape3D import kotlin.reflect.KClass -class FXProxyFactory(val plugin: FX3DPlugin) : - FX3DFactory { +class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory { override val type: KClass get() = Proxy::class - override fun invoke(obj: Proxy, binding: DisplayObjectFXBinding): Node { - TODO("not implemented") //To change body of created functions use File | Settings | File Templates. - } + override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node { + val template = obj.prototype + val node = plugin.buildNode(template) + obj.onPropertyChange(this) { name, _, _ -> + if (name.first()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) { + val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'") + val propertyName = name.cutFirst() + val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found") + val child = node.findChild(childName) ?: error("Object child with name '$childName' not found") + child.updateProperty(proxyChild, propertyName) + } + } + return node + } +} + +private fun Node.findChild(name: Name): Node? { + return if (name.isEmpty()) { + this + } else { + (this as? Group) + ?.children + ?.find { it.properties["name"] as String == name.first()?.toString() } + ?.findChild(name.cutFirst()) + } +} + +private fun Node.updateProperty(obj: VisualObject, propertyName: Name) { + if (propertyName.startsWith(Material3D.MATERIAL_KEY)) { + (this as? Shape3D)?.let { it.material = obj.getProperty(Material3D.MATERIAL_KEY).material() } + } } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXShapeFactory.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXShapeFactory.kt index 93868b54..cd851fe6 100644 --- a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXShapeFactory.kt +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/FXShapeFactory.kt @@ -13,7 +13,7 @@ import kotlin.reflect.KClass object FXShapeFactory : FX3DFactory { override val type: KClass get() = Shape::class - override fun invoke(obj: Shape, binding: DisplayObjectFXBinding): MeshView { + override fun invoke(obj: Shape, binding: VisualObjectFXBinding): MeshView { val mesh = FXGeometryBuilder().apply { obj.toGeometry(this) }.build() return MeshView(mesh) } diff --git a/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/VisualObjectFXBinding.kt b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/VisualObjectFXBinding.kt new file mode 100644 index 00000000..bc772892 --- /dev/null +++ b/dataforge-vis-spatial/src/jvmMain/kotlin/hep/dataforge/vis/spatial/fx/VisualObjectFXBinding.kt @@ -0,0 +1,54 @@ +package hep.dataforge.vis.spatial.fx + +import hep.dataforge.meta.* +import hep.dataforge.names.Name +import hep.dataforge.names.isEmpty +import hep.dataforge.names.toName +import hep.dataforge.vis.common.VisualObject +import javafx.beans.binding.ObjectBinding +import tornadofx.* + +/** + * A caching binding collection for [VisualObject] properties + */ +class VisualObjectFXBinding(val obj: VisualObject) { + private val bindings = HashMap?>>() + + init { + obj.onPropertyChange(this) { name, _, _ -> + var currentName = name + while(!currentName.isEmpty()) { + //recursively update all upper level bindings + bindings[currentName]?.invalidate() + currentName = currentName.cutLast() + } + } + } + + operator fun get(key: Name): ObjectBinding?> { + return bindings.getOrPut(key) { + object : ObjectBinding?>() { + override fun computeValue(): MetaItem<*>? = obj.getProperty(key) + } + } + } + + operator fun get(key: String) = get(key.toName()) +} + +fun ObjectBinding?>.value() = objectBinding { it.value } +fun ObjectBinding?>.string() = stringBinding { it.string } +fun ObjectBinding?>.number() = objectBinding { it.number } +fun ObjectBinding?>.double() = objectBinding { it.double } +fun ObjectBinding?>.float() = objectBinding { it.float } +fun ObjectBinding?>.int() = objectBinding { it.int } +fun ObjectBinding?>.long() = objectBinding { it.long } +fun ObjectBinding?>.node() = objectBinding { it.node } + +fun ObjectBinding?>.string(default: String) = stringBinding { it.string ?: default } +fun ObjectBinding?>.double(default: Double) = objectBinding { it.double ?: default } +fun ObjectBinding?>.float(default: Float) = objectBinding { it.float ?: default } +fun ObjectBinding?>.int(default: Int) = objectBinding { it.int ?: default } +fun ObjectBinding?>.long(default: Long) = objectBinding { it.long ?:default } + +fun ObjectBinding?>.transform(transform: (MetaItem<*>) -> T) = objectBinding { it?.let(transform) }