diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegate.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegate.kt index c51cdfe8..1dba2c7f 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegate.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegate.kt @@ -4,6 +4,7 @@ import hep.dataforge.meta.* import hep.dataforge.names.Name import hep.dataforge.names.NameToken import hep.dataforge.names.asName +import hep.dataforge.names.toName import hep.dataforge.values.Value import kotlin.jvm.JvmName import kotlin.properties.ReadOnlyProperty @@ -35,81 +36,79 @@ class VisualObjectDelegate( } class VisualObjectDelegateWrapper( + val obj: VisualObject, val key: Name?, val default: T, val inherited: Boolean, val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) }, val read: (MetaItem<*>?) -> T? -) : ReadWriteProperty { +) : ReadWriteProperty { //private var cachedName: Name? = null - override fun getValue(thisRef: VisualObject, property: KProperty<*>): T { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { val name = key ?: property.name.asName() - return if (inherited) { - read(thisRef.getProperty(name)) - } else { - read(thisRef.config[name]) - } ?: default + return read(obj.getProperty(name,inherited))?:default } - override fun setValue(thisRef: VisualObject, property: KProperty<*>, value: T) { + override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { val name = key ?: property.name.asName() - thisRef.config[name] = value + obj.config[name] = value } } fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.value } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.value } fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string } fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean } fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number } fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double } fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int } fun VisualObject.node(key: String? = null, inherited: Boolean = true) = - VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it.node } + VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it.node } fun VisualObject.item(key: String? = null, inherited: Boolean = true) = - VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it } + VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it } //fun Configurable.spec(spec: Specification, key: String? = null) = ChildConfigDelegate(key) { spec.wrap(this) } @JvmName("safeString") fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string } @JvmName("safeBoolean") fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean } @JvmName("safeNumber") fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number } @JvmName("safeDouble") fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double } @JvmName("safeInt") fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) = - VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } + VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int } inline fun > VisualObject.enum(default: E, key: String? = null, inherited: Boolean = false) = VisualObjectDelegateWrapper( + this, key?.let { NameToken(it).asName() }, default, inherited @@ -121,11 +120,11 @@ fun VisualObject.merge( key: String? = null, transformer: (Sequence>) -> T ): ReadOnlyProperty { - return object : ReadOnlyProperty { - override fun getValue(thisRef: VisualObject, property: KProperty<*>): T { - val name = key?.asName() ?: property.name.asName() + return object : ReadOnlyProperty { + override fun getValue(thisRef: Any?, property: KProperty<*>): T { + val name = key?.toName() ?: property.name.asName() val sequence = sequence> { - var thisObj: VisualObject? = thisRef + var thisObj: VisualObject? = this@merge while (thisObj != null) { thisObj.config[name]?.let { yield(it) } thisObj = thisObj.parent diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/PolyLine.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/PolyLine.kt new file mode 100644 index 00000000..eb8b4699 --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/PolyLine.kt @@ -0,0 +1,26 @@ +@file:UseSerializers(Point3DSerializer::class) + +package hep.dataforge.vis.spatial + +import hep.dataforge.io.ConfigSerializer +import hep.dataforge.meta.Config +import hep.dataforge.vis.common.AbstractVisualObject +import hep.dataforge.vis.common.number +import kotlinx.serialization.Serializable +import kotlinx.serialization.UseSerializers + +@Serializable +class PolyLine(var points: List) : AbstractVisualObject(), VisualObject3D { + @Serializable(ConfigSerializer::class) + override var properties: Config? = null + + override var position: Point3D? = null + override var rotation: Point3D? = null + override var scale: Point3D? = null + + //var lineType by string() + var thickness by number(1.0, key = "material.thickness") +} + +fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) = + PolyLine(points.toList()).apply(action).also { set(name, it) } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/Materials.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/Materials.kt index e6c72174..8e575169 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/Materials.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/Materials.kt @@ -4,6 +4,7 @@ import hep.dataforge.meta.* import hep.dataforge.values.ValueType import hep.dataforge.vis.common.Colors import hep.dataforge.vis.spatial.Material3D +import info.laht.threekt.materials.LineBasicMaterial import info.laht.threekt.materials.Material import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.materials.MeshPhongMaterial @@ -13,8 +14,37 @@ import info.laht.threekt.math.Color object Materials { val DEFAULT_COLOR = Color(Colors.darkgreen) val DEFAULT = MeshPhongMaterial().apply { - this.color.set(DEFAULT_COLOR) + color.set(DEFAULT_COLOR) } + val DEFAULT_LINE_COLOR = Color(Colors.black) + val DEFAULT_LINE = LineBasicMaterial().apply { + color.set(DEFAULT_LINE_COLOR) + } + + + private val materialCache = HashMap() + private val lineMaterialCache = HashMap() + + fun getMaterial(meta: Meta): Material = materialCache.getOrPut(meta) { + MeshBasicMaterial().apply { + color = meta["color"]?.color() ?: DEFAULT_COLOR + opacity = meta["opacity"]?.double ?: 1.0 + transparent = meta["transparent"].boolean ?: (opacity < 1.0) + //node["specularColor"]?.let { specular = it.color() } + //side = 2 + } + } + + fun getLineMaterial(meta: Meta): Material = lineMaterialCache.getOrPut(meta) { + LineBasicMaterial().apply { + color = meta["color"]?.color() ?: DEFAULT_LINE_COLOR + opacity = meta["opacity"].double ?: 1.0 + transparent = meta["transparent"].boolean ?: (opacity < 1.0) + linewidth = meta["thickness"].double ?: 1.0 + } + } + + } /** @@ -41,26 +71,26 @@ fun MetaItem<*>.color(): Color { } } -private val materialCache = HashMap() - /** * Infer Three material based on meta item */ fun Meta?.jsMaterial(): Material { return if (this == null) { Materials.DEFAULT - } else - //TODO add more options for material - return materialCache.getOrPut(this) { - MeshBasicMaterial().apply { - color = get("color")?.color() ?: Materials.DEFAULT_COLOR - opacity = get("opacity")?.double ?: 1.0 - transparent = get("transparent").boolean ?: (opacity < 1.0) - //node["specularColor"]?.let { specular = it.color() } - //side = 2 - } - } + } else { + Materials.getMaterial(this) + } } -fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial() +fun Meta?.jsLineMaterial(): Material { + return if (this == null) { + Materials.DEFAULT_LINE + } else{ + Materials.getLineMaterial(this) + } +} + + +fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial() +fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial() diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/MeshThreeFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/MeshThreeFactory.kt new file mode 100644 index 00000000..2b0c5d21 --- /dev/null +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/MeshThreeFactory.kt @@ -0,0 +1,99 @@ +package hep.dataforge.vis.spatial.three + +import hep.dataforge.meta.boolean +import hep.dataforge.meta.node +import hep.dataforge.names.asName +import hep.dataforge.names.plus +import hep.dataforge.names.startsWith +import hep.dataforge.vis.spatial.Material3D +import hep.dataforge.vis.spatial.VisualObject3D +import hep.dataforge.vis.spatial.layer +import hep.dataforge.vis.spatial.material +import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.geometries.EdgesGeometry +import info.laht.threekt.geometries.WireframeGeometry +import info.laht.threekt.objects.LineSegments +import info.laht.threekt.objects.Mesh +import kotlin.reflect.KClass + +/** + * Basic geometry-based factory + */ +abstract class MeshThreeFactory( + override val type: KClass +) : ThreeFactory { + /** + * Build a geometry for an object + */ + abstract fun buildGeometry(obj: T): BufferGeometry + + private fun Mesh.applyEdges(obj: T) { + children.find { it.name == "edges" }?.let { remove(it) } + //inherited edges definition, enabled by default + if (obj.getProperty(EDGES_ENABLED_KEY).boolean != false) { + val material = obj.getProperty(EDGES_MATERIAL_KEY).node.jsLineMaterial() + add( + LineSegments( + EdgesGeometry(geometry as BufferGeometry), + material + ) + ) + } + } + + private fun Mesh.applyWireFrame(obj: T) { + children.find { it.name == "wireframe" }?.let { remove(it) } + //inherited wireframe definition, disabled by default + if (obj.getProperty(WIREFRAME_ENABLED_KEY).boolean == true) { + val material = obj.getProperty(WIREFRAME_MATERIAL_KEY).node.jsLineMaterial() + add( + LineSegments( + WireframeGeometry(geometry as BufferGeometry), + material + ) + ) + } + } + + override fun invoke(obj: T): Mesh { + //TODO add caching for geometries using templates + val geometry = buildGeometry(obj) + + //JS sometimes tries to pass Geometry as BufferGeometry + @Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected") + + val mesh = Mesh(geometry, obj.material.jsMaterial()).apply { + matrixAutoUpdate = false + applyEdges(obj) + applyWireFrame(obj) + //set position for mesh + updatePosition(obj) + + layers.enable(obj.layer) + children.forEach { + it.layers.enable(obj.layer) + } + } + + //add listener to object properties + obj.onPropertyChange(this) { name, _, _ -> + when { + name.startsWith(VisualObject3D.GEOMETRY_KEY) -> mesh.geometry = buildGeometry(obj) + name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) + name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj) + else -> mesh.updateProperty(obj, name) + } + } + return mesh + } + + companion object { + val EDGES_KEY = "edges".asName() + val WIREFRAME_KEY = "wireframe".asName() + val ENABLED_KEY = "enabled".asName() + val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY + val EDGES_MATERIAL_KEY = EDGES_KEY + Material3D.MATERIAL_KEY + val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY + val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + Material3D.MATERIAL_KEY + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt index c190f048..94e71f5e 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt @@ -1,22 +1,14 @@ package hep.dataforge.vis.spatial.three -import hep.dataforge.meta.boolean -import hep.dataforge.meta.node import hep.dataforge.names.Name -import hep.dataforge.names.asName -import hep.dataforge.names.plus import hep.dataforge.names.startsWith import hep.dataforge.provider.Type import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY -import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.Object3D -import info.laht.threekt.geometries.EdgesGeometry -import info.laht.threekt.geometries.WireframeGeometry -import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.Mesh import kotlin.reflect.KClass @@ -46,30 +38,6 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) { updateMatrix() } -internal fun Mesh.updateFrom(obj: T) { - matrixAutoUpdate = false - - //inherited edges definition, enabled by default - if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) { - val material = obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT - add(LineSegments(EdgesGeometry(geometry as BufferGeometry), material)) - } - - //inherited wireframe definition, disabled by default - if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) { - val material = obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT - add(LineSegments(WireframeGeometry(geometry as BufferGeometry), material)) - } - - //set position for mesh - updatePosition(obj) - - layers.enable(obj.layer) - children.forEach { - it.layers.enable(obj.layer) - } -} - /** * Unsafe invocation of a factory */ @@ -82,53 +50,6 @@ operator fun ThreeFactory.invoke(obj: Any): Object3D { } } -/** - * Basic geometry-based factory - */ -abstract class MeshThreeFactory(override val type: KClass) : ThreeFactory { - /** - * Build a geometry for an object - */ - abstract fun buildGeometry(obj: T): BufferGeometry - - - override fun invoke(obj: T): Mesh { - //create mesh from geometry - return buildMesh(obj) { buildGeometry(it) } - } - - companion object { - val EDGES_KEY = "edges".asName() - val WIREFRAME_KEY = "wireframe".asName() - val ENABLED_KEY = "enabled".asName() - val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY - val EDGES_MATERIAL_KEY = EDGES_KEY + MATERIAL_KEY - val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY - val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + MATERIAL_KEY - - fun buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh { - //TODO add caching for geometries using templates - val geometry = geometryBuilder(obj) - - //JS sometimes tries to pass Geometry as BufferGeometry - @Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected") - - val mesh = Mesh(geometry, obj.material.jsMaterial()) - - mesh.updateFrom(obj) - - //add listener to object properties - obj.onPropertyChange(this) { name, _, _ -> - mesh.updateProperty(obj, name) - if (name.startsWith(GEOMETRY_KEY)) { - mesh.geometry = geometryBuilder(obj) - } - } - return mesh - } - } -} - fun Object3D.updateProperty(source: VisualObject, propertyName: Name) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) { //updated material diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeLineFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeLineFactory.kt new file mode 100644 index 00000000..cb109e8b --- /dev/null +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeLineFactory.kt @@ -0,0 +1,32 @@ +package hep.dataforge.vis.spatial.three + +import hep.dataforge.vis.spatial.PolyLine +import hep.dataforge.vis.spatial.layer +import hep.dataforge.vis.spatial.material +import info.laht.threekt.core.Geometry +import info.laht.threekt.core.Object3D +import info.laht.threekt.objects.LineSegments +import kotlin.reflect.KClass + +object ThreeLineFactory : ThreeFactory { + override val type: KClass get() = PolyLine::class + + override fun invoke(obj: PolyLine): Object3D { + val geometry = Geometry().apply { + vertices = obj.points.toTypedArray() + } + + val material = obj.material.jsLineMaterial() + return LineSegments(geometry, material).apply { + + updatePosition(obj) + layers.enable(obj.layer) + + //add listener to object properties + obj.onPropertyChange(this) { propertyName, _, _ -> + updateProperty(obj, propertyName) + } + } + } + +} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt index d39653f4..be1f7803 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt @@ -33,6 +33,7 @@ class ThreePlugin : AbstractPlugin() { objectFactories[Convex::class] = ThreeConvexFactory objectFactories[Sphere::class] = ThreeSphereFactory objectFactories[ConeSegment::class] = ThreeCylinderFactory + objectFactories[PolyLine::class] = ThreeLineFactory } private fun findObjectFactory(type: KClass): ThreeFactory<*>? { diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt index e682cdc7..3925c7fb 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/objects/LineSegments.kt @@ -28,7 +28,10 @@ package info.laht.threekt.objects import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry import info.laht.threekt.core.Object3D import info.laht.threekt.materials.Material -open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D \ No newline at end of file +open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D { + constructor(geometry: Geometry, material: Material) +} \ No newline at end of file diff --git a/spatial-js-demo/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt b/spatial-js-demo/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt index de2d6d0e..a35bf4c9 100644 --- a/spatial-js-demo/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt +++ b/spatial-js-demo/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt @@ -130,6 +130,21 @@ private class ThreeDemoApp : ApplicationBase() { } } } + 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 + } + } + } }