diff --git a/build.gradle.kts b/build.gradle.kts index a21838aa..4555664e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ -val dataforgeVersion by extra("0.1.3-dev-9") +val dataforgeVersion by extra("0.1.3-dev-10") plugins{ val kotlinVersion = "1.3.50-eap-5" diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt deleted file mode 100644 index 0e3c2c58..00000000 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt +++ /dev/null @@ -1,91 +0,0 @@ -package hep.dataforge.vis.common - -import hep.dataforge.meta.Config -import hep.dataforge.meta.Laminate -import hep.dataforge.meta.Meta -import hep.dataforge.names.Name -import hep.dataforge.provider.Provider -import kotlin.collections.set - -/** - * A display group which allows both named and unnamed children - */ -open class VisualGroup( - override val parent: VisualObject? = null, tagRefs: Array = emptyArray() -) : VisualObject, Iterable, Provider { - - private val namedChildren = HashMap() - private val unnamedChildren = ArrayList() - - override val defaultTarget: String get() = VisualObject.TYPE - override val config = Config() - - override val properties: Laminate by lazy { - combineProperties(parent, config, tagRefs) - } - - override fun iterator(): Iterator = (namedChildren.values + unnamedChildren).iterator() - - override fun provideTop(target: String): Map { - return when (target) { - VisualObject.TYPE -> namedChildren - else -> emptyMap() - } - } - - private data class Listener(val owner: Any?, val callback: (Name?, VisualObject?) -> Unit) - - private val listeners = HashSet() - - /** - * Add listener for children change - */ - fun onChildrenChange(owner: Any?, action: (Name?, VisualObject?) -> Unit) { - listeners.add(Listener(owner, action)) - } - - - /** - * Remove children change listener - */ - fun removeChildrenChangeListener(owner: Any?) { - listeners.removeAll { it.owner === owner } - } - - /** - * Add named or unnamed child to the group. If key is [null] the child is considered unnamed. Both key and value are not - * allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed. - */ - operator fun set(name: Name?, child: VisualObject?) { - when { - name != null -> { - if (child == null) { - namedChildren.remove(name) - } else { - namedChildren[name] = child - } - listeners.forEach { it.callback(name, child) } - } - child != null -> unnamedChildren.add(child) - else -> error("Both key and child element are empty") - } - } - - operator fun set(key: String?, child: VisualObject?) = set(key?.asName(), child) - - /** - * Append unnamed child - */ - fun add(child: VisualObject) { - unnamedChildren.add(child) - listeners.forEach { it.callback(null, child) } - } - - /** - * remove unnamed child - */ - fun remove(child: VisualObject) { - unnamedChildren.remove(child) - listeners.forEach { it.callback(null, null) } - } -} \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualLeaf.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualLeaf.kt new file mode 100644 index 00000000..4c450ae5 --- /dev/null +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualLeaf.kt @@ -0,0 +1,29 @@ +package hep.dataforge.vis.common + +import hep.dataforge.meta.* +import hep.dataforge.names.Name + +/** + * Basic [VisualObject] leaf element + */ +open class VisualLeaf( + parent: VisualObject? = null, + meta: Meta = EmptyMeta +) : AbstractVisualObject(parent), Configurable { + + val properties = Styled(meta) + + override val config: Config = properties.style + + override fun setProperty(name: Name, value: Any?) { + config[name] = value + } + + override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { + return if (inherit) { + properties[name] ?: parent?.getProperty(name, inherit) + } else { + properties[name] + } + } +} \ No newline at end of file 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 20e3d653..6cb22e2a 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 @@ -2,10 +2,11 @@ package hep.dataforge.vis.common import hep.dataforge.meta.* import hep.dataforge.names.Name +import hep.dataforge.names.get +import hep.dataforge.provider.Provider import hep.dataforge.provider.Type -import hep.dataforge.vis.common.VisualObject.Companion.META_KEY -import hep.dataforge.vis.common.VisualObject.Companion.TAGS_KEY import hep.dataforge.vis.common.VisualObject.Companion.TYPE +import kotlin.collections.set private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers) private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta) @@ -16,88 +17,190 @@ private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta) @Type(TYPE) interface VisualObject : MetaRepr, Configurable { + val type: String get() = this::class.simpleName ?: TYPE + /** * The parent object of this one. If null, this one is a root. */ val parent: VisualObject? /** - * Individual properties configurator + * Set property for this object */ - override val config: Config + fun setProperty(name: Name, value: Any?) /** - * All properties including inherited ones + * Get property including or excluding parent properties */ - val properties: Laminate + fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>? - override fun toMeta(): Meta = buildMeta { - "type" to this::class - "properties" to properties - } + /** + * Manually trigger property changed event. If [name] is empty, notify that the whole object is changed + */ + fun propertyChanged(name: Name, before: MetaItem<*>? = null, after: MetaItem<*>? = null): Unit + + /** + * Add listener triggering on property change + */ + fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit): Unit + + /** + * Remove change listeners with given owner. + */ + fun removeChangeListener(owner: Any?) companion object { const val TYPE = "visual" - const val DEFAULT_TYPE = "" - //const val TYPE_KEY = "@type" - //const val CHILDREN_KEY = "@children" - const val META_KEY = "@meta" - const val TAGS_KEY = "@tags" + //const val META_KEY = "@meta" + //const val TAGS_KEY = "@tags" } } -/** - * A change listener for [VisualObject] configuration. - */ -fun VisualObject.onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) { - config.onChange(owner, action) - parent?.onChange(owner, action) -} - -/** - * Remove all meta listeners with matching owners - */ -fun VisualObject.removeChangeListener(owner: Any?) { - config.removeListener(owner) - parent?.removeChangeListener(owner) -} +internal data class MetaListener( + val owner: Any? = null, + val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit +) -/** - * Additional meta not relevant to display - */ -val VisualObject.meta: Meta get() = config[META_KEY]?.node ?: EmptyMeta +abstract class AbstractVisualObject(override val parent: VisualObject?) : VisualObject { + private val listeners = HashSet() -val VisualObject.tags: List get() = config[TAGS_KEY].stringList + override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) { + for (l in listeners) { + l.action(name, before, after) + } + } -/** - * Basic [VisualObject] leaf element - */ -open class VisualLeaf( - final override val parent: VisualObject?, - tagRefs: Array -) : VisualObject { - override val config = Config() + override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) { + listeners.add(MetaListener(owner, action)) + } - override val properties: Laminate by lazy { - combineProperties(parent, config, tagRefs) + override fun removeChangeListener(owner: Any?) { + listeners.removeAll { it.owner == owner } + } + + private var _config: Config? = null + override val config: Config get() = _config ?: Config().also { _config = it } + + override fun setProperty(name: Name, value: Any?) { + config[name] = value + } + + override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { + return if (inherit) { + _config?.get(name) ?: parent?.getProperty(name, inherit) + } else { + _config?.get(name) + } + } + + protected open fun MetaBuilder.updateMeta() {} + + override fun toMeta(): Meta = buildMeta { + "type" to type + "properties" to _config + updateMeta() } } -internal fun combineProperties(parent: VisualObject?, config: Config, tagRefs: Array): Laminate { - val list = ArrayList(tagRefs.size + 2) - list += config - list.addAll(tagRefs) - parent?.properties?.let { list.add(it) } - return Laminate(list) +open class VisualGroup(parent: VisualObject?) : AbstractVisualObject(parent), Iterable, Provider { + + protected val namedChildren = HashMap() + protected val unnamedChildren = ArrayList() + + override val defaultTarget: String get() = VisualObject.TYPE + + override fun iterator(): Iterator = (namedChildren.values + unnamedChildren).iterator() + + override fun provideTop(target: String): Map { + return when (target) { + TYPE -> namedChildren + else -> emptyMap() + } + } + + private data class Listener(val owner: Any?, val callback: (Name?, T?) -> Unit) + + private val listeners = HashSet>() + + /** + * Add listener for children change + */ + fun onChildrenChange(owner: Any?, action: (Name?, T?) -> Unit) { + listeners.add(Listener(owner, action)) + } + + + /** + * Remove children change listener + */ + fun removeChildrenChangeListener(owner: Any?) { + listeners.removeAll { it.owner === owner } + } + + /** + * Add named or unnamed child to the group. If key is [null] the child is considered unnamed. Both key and value are not + * allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed. + */ + operator fun set(name: Name?, child: T?) { + when { + name != null -> { + if (child == null) { + namedChildren.remove(name) + } else { + namedChildren[name] = child + } + listeners.forEach { it.callback(name, child) } + } + child != null -> unnamedChildren.add(child) + else -> error("Both key and child element are empty") + } + } + + operator fun set(key: String?, child: T?) = set(key?.asName(), child) + + /** + * 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 + */ + operator fun get(index: Int): T? = unnamedChildren[index] + + /** + * Append unnamed child + */ + fun add(child: T) { + unnamedChildren.add(child) + listeners.forEach { it.callback(null, child) } + } + + /** + * remove unnamed child + */ + fun remove(child: VisualObject) { + unnamedChildren.remove(child) + listeners.forEach { it.callback(null, null) } + } + + protected fun MetaBuilder.updateChildren() { + //adding unnamed children + "children" to unnamedChildren.map { it.toMeta() } + //adding named children + namedChildren.forEach { + "children[${it.key}" to it.value.toMeta() + } + } + + override fun MetaBuilder.updateMeta() { + updateChildren() + } } -///** -// * A group that could contain both named and unnamed children. Unnamed children could be accessed only via -// */ -//interface VisualGroup : DisplayObject, Iterable, Provider { -// override val defaultTarget: String get() = DisplayObject.TARGET -// -// val children -//} + diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegates.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegates.kt index b951e587..748e537b 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegates.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObjectDelegates.kt @@ -23,7 +23,7 @@ class DisplayObjectDelegate( override fun getValue(thisRef: VisualObject, property: KProperty<*>): MetaItem<*>? { val name = key ?: property.name.asName() return if (inherited) { - thisRef.properties[name] + thisRef.getProperty(name) } else { thisRef.config[name] } ?: default @@ -48,7 +48,7 @@ class DisplayObjectDelegateWrapper( override fun getValue(thisRef: VisualObject, property: KProperty<*>): T { val name = key ?: property.name.asName() return if (inherited) { - read(thisRef.properties[name]) + read(thisRef.getProperty(name)) } else { read(thisRef.config[name]) } ?: default diff --git a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt index 3a11fd8c..139bf4be 100644 --- a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt +++ b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt @@ -3,7 +3,7 @@ package hep.dataforge.vis.spatial import hep.dataforge.context.Context import hep.dataforge.meta.Meta import hep.dataforge.output.Output -import hep.dataforge.vis.common.VisualGroup +import hep.dataforge.vis.common.VisualNode import hep.dataforge.vis.common.VisualObject import javafx.scene.Group import javafx.scene.Node @@ -27,7 +27,7 @@ class FX3DOutput(override val context: Context) : Output { org.fxyz3d.geometry.Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f) } return when (obj) { - is VisualGroup -> Group(obj.map { buildNode(it) }).apply { + is VisualNode -> Group(obj.map { buildNode(it) }).apply { this.translateXProperty().bind(x) this.translateYProperty().bind(y) this.translateZProperty().bind(z) 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 1e165cb8..ff1ef79a 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 @@ -2,7 +2,6 @@ package hep.dataforge.vis.spatial import hep.dataforge.context.Global import hep.dataforge.meta.number -import hep.dataforge.vis.common.VisualGroup import javafx.scene.Parent import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay @@ -21,7 +20,7 @@ class RendererDemoView : View() { center = renderer.canvas.root } - lateinit var group: VisualGroup + lateinit var group: VisualGroup3D init { 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 new file mode 100644 index 00000000..3c1f7c72 --- /dev/null +++ b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/GDMLTransformer.kt @@ -0,0 +1,60 @@ +package hep.dataforge.vis.spatial.gdml + +import hep.dataforge.meta.Meta +import hep.dataforge.meta.buildMeta +import hep.dataforge.meta.builder +import hep.dataforge.vis.spatial.VisualGroup3D +import scientifik.gdml.GDML +import scientifik.gdml.GDMLGroup +import scientifik.gdml.GDMLMaterial +import scientifik.gdml.GDMLSolid +import kotlin.random.Random + +class GDMLTransformer(val root: GDML) { + private val materialCache = HashMap() + private val random = Random(111) + + /** + * A special group for local templates + */ + val templates by lazy { VisualGroup3D() } + + var lUnit: LUnit = LUnit.MM + var resolveColor: ColorResolver = { material, _ -> + val materialColor = materialCache.getOrPut(material) { + buildMeta { + "color" to random.nextInt(0, Int.MAX_VALUE) + } + } + + if (this?.physVolumes?.isEmpty() != false) { + materialColor + } else { + materialColor.builder().apply { "opacity" to 0.5 } + } + } + + var acceptSolid: (GDMLSolid) -> Boolean = { true } + var acceptGroup: (GDMLGroup) -> Boolean = { true } + + fun printStatistics() { + println("Solids:") + solidCounter.entries.sortedByDescending { it.value }.forEach { + println("\t$it") + } + println(println("Solids total: ${solidCounter.values.sum()}")) + } + + private val solidCounter = HashMap() + + internal fun solidAdded(solid: GDMLSolid) { + solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1 + } + + var onFinish: GDMLTransformer.() -> Unit = {} + + internal fun finished() { + onFinish(this) + } + +} \ No newline at end of file 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 684d7b40..fa3d12fc 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 @@ -1,58 +1,10 @@ package hep.dataforge.vis.spatial.gdml import hep.dataforge.meta.Meta -import hep.dataforge.meta.buildMeta -import hep.dataforge.meta.builder import hep.dataforge.vis.spatial.* import scientifik.gdml.* import kotlin.math.cos import kotlin.math.sin -import kotlin.random.Random - - -class GDMLTransformer(val root: GDML) { - private val cache = HashMap() - private val random = Random(111) - - var lUnit: LUnit = LUnit.MM - var resolveColor: ColorResolver = { material, _ -> - val materialColor = cache.getOrPut(material) { - buildMeta { - "color" to random.nextInt(0, Int.MAX_VALUE) - } - } - - if (this?.physVolumes?.isEmpty() != false) { - materialColor - } else { - materialColor.builder().apply { "opacity" to 0.5 } - } - } - - var acceptSolid: (GDMLSolid) -> Boolean = { true } - var acceptGroup: (GDMLGroup) -> Boolean = { true } - - fun printStatistics() { - println("Solids:") - solidCounter.entries.sortedByDescending { it.value }.forEach { - println("\t$it") - } - println(println("Solids total: ${solidCounter.values.sum()}")) - } - - private val solidCounter = HashMap() - - internal fun solidAdded(solid: GDMLSolid) { - solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1 - } - - var onFinish: GDMLTransformer.() -> Unit = {} - - internal fun finished(){ - onFinish(this) - } - -} private fun VisualObject3D.withPosition( @@ -82,7 +34,6 @@ private fun VisualObject3D.withPosition( private inline operator fun Number.times(d: Double) = toDouble() * d private inline operator fun Number.times(f: Float) = toFloat() * f - private fun VisualGroup3D.addSolid( context: GDMLTransformer, solid: GDMLSolid, @@ -238,9 +189,12 @@ private fun volume( val material = group.materialref.resolve(context.root) ?: GDMLElement(group.materialref.ref) if (context.acceptSolid(solid)) { - addSolid(context, solid, solid.name) { - this.material = context.resolveColor(group, material, solid) - } + val cachedSolid = context.templates[solid.name] + ?: context.templates.addSolid(context, solid, solid.name) { + this.material = context.resolveColor(group, material, solid) + } + val wrapper = Proxy3D(this,cachedSolid) + add(wrapper) } when (val vol = group.placement) { @@ -262,7 +216,7 @@ fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D { val context = GDMLTransformer(this).apply(block) - return volume(context, world).also{ + return volume(context, world).also { context.finished() } } \ No newline at end of file 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 f153a18f..503a29af 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") - && !solid.name.startsWith("V") - && !solid.name.startsWith("U") - } +// 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-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/testMain.kt b/dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/testMain.kt index 91d5599b..7d5d2758 100644 --- a/dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/testMain.kt +++ b/dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/testMain.kt @@ -18,7 +18,7 @@ fun main() { val xml = GDML.format.parse(GDML.serializer(), xmlReader) xml.toVisual { lUnit = LUnit.CM - acceptSolid = { solid -> !solid.name.startsWith("ecal") && !solid.name.startsWith("V") } + //acceptSolid = { solid -> !solid.name.startsWith("ecal") && !solid.name.startsWith("V") } onFinish = { printStatistics() } } readLine() diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/Materials.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/Materials.kt index 0f04d318..3d7f382b 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/Materials.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/Materials.kt @@ -40,21 +40,24 @@ fun MetaItem<*>.color(): Color { } } +private val materialCache = HashMap() + /** * Infer Three material based on meta item */ fun Meta?.jsMaterial(): Material { - return if(this == null){ + return if (this == null) { Materials.DEFAULT } else - //TODO add more oprions for material - 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 + //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 + } } - } diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCompositeFactory.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCompositeFactory.kt index 2969e865..51089d35 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCompositeFactory.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCompositeFactory.kt @@ -18,7 +18,7 @@ class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory firstCSG.union(secondCSG) CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) CompositeType.SUBTRACT -> firstCSG.subtract(secondCSG) diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeConvexFactory.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeConvexFactory.kt new file mode 100644 index 00000000..7546c86f --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeConvexFactory.kt @@ -0,0 +1,11 @@ +package hep.dataforge.vis.spatial.three + +import hep.dataforge.vis.spatial.Convex +import info.laht.threekt.external.geometries.ConvexBufferGeometry + +object ThreeConvexFactory : MeshThreeFactory(Convex::class) { + override fun buildGeometry(obj: Convex): ConvexBufferGeometry { + val vectors = obj.points.map { it.asVector() }.toTypedArray() + return ConvexBufferGeometry(vectors) + } +} \ 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 de422961..390916d6 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 @@ -1,18 +1,16 @@ package hep.dataforge.vis.spatial.three import hep.dataforge.meta.boolean -import hep.dataforge.meta.get import hep.dataforge.meta.int import hep.dataforge.meta.node +import hep.dataforge.names.plus import hep.dataforge.names.startsWith import hep.dataforge.provider.Type -import hep.dataforge.vis.common.onChange +import hep.dataforge.vis.common.asName import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE -import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.buildMesh import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.Object3D -import info.laht.threekt.external.geometries.ConvexBufferGeometry import info.laht.threekt.geometries.EdgesGeometry import info.laht.threekt.geometries.WireframeGeometry import info.laht.threekt.objects.LineSegments @@ -31,55 +29,6 @@ interface ThreeFactory { companion object { const val TYPE = "threeFactory" - - fun buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh { - 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()) - - //inherited edges definition, enabled by default - if (obj.properties["edges.enabled"].boolean != false) { - val material = obj.properties["edges.material"].node?.jsMaterial() ?: Materials.DEFAULT - mesh.add(LineSegments(EdgesGeometry(mesh.geometry as BufferGeometry), material)) - } - - //inherited wireframe definition, disabled by default - if (obj.properties["wireframe.enabled"].boolean == true) { - val material = obj.properties["wireframe.material"].node?.jsMaterial() ?: Materials.DEFAULT - mesh.add(LineSegments(WireframeGeometry(mesh.geometry as BufferGeometry), material)) - } - - //set position for mesh - mesh.updatePosition(obj) - - obj.config["layer"].int?.let { - mesh.layers.set(it) - } - - //add listener to object properties - obj.onChange(this) { name, _, _ -> - if (name.startsWith(VisualObject3D.materialKey)) { - //updated material - mesh.material = obj.material.jsMaterial() - } else if ( - name.startsWith(VisualObject3D.position) || - name.startsWith(VisualObject3D.rotation) || - name.startsWith(VisualObject3D.scale) || - name == VisualObject3D.visibleKey - ) { - //update position of mesh using this object - mesh.updatePosition(obj) - } else { - //full update - mesh.geometry = geometryBuilder(obj) - mesh.material = obj.material.jsMaterial() - } - } - return mesh - } } } @@ -90,7 +39,30 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) { position.set(obj.x, obj.y, obj.z) setRotationFromEuler(obj.euler) scale.set(obj.scaleX, obj.scaleY, obj.scaleZ) - visible = obj.visible ?: true + 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) + + obj.getProperty(MeshThreeFactory.LAYER_KEY).int?.let { + layers.set(it) + } } /** @@ -118,6 +90,51 @@ abstract class MeshThreeFactory(override val type: KClass(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 + VisualObject3D.MATERIAL_KEY + val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY + val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + VisualObject3D.MATERIAL_KEY + val LAYER_KEY = "layer".asName() + + 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, _, _ -> + if (name.startsWith(VisualObject3D.MATERIAL_KEY)) { + //updated material + mesh.material = obj.material.jsMaterial() + } else if ( + name.startsWith(VisualObject3D.position) || + name.startsWith(VisualObject3D.rotation) || + name.startsWith(VisualObject3D.scale) + ) { + //update position of mesh using this object + mesh.updatePosition(obj) + } else if (name == VisualObject3D.VISIBLE_KEY) { + obj.visible = obj.visible ?: true + } else { + //full update + mesh.geometry = geometryBuilder(obj) + mesh.material = obj.material.jsMaterial() + } + } + return mesh + } + } } /** @@ -129,12 +146,4 @@ object ThreeShapeFactory : MeshThreeFactory(Shape::class) { ThreeGeometryBuilder().apply { toGeometry(this) }.build() } } -} - -//FIXME not functional yet -object ThreeConvexFactory : MeshThreeFactory(Convex::class) { - override fun buildGeometry(obj: Convex): ConvexBufferGeometry { - val vectors = obj.points.map { it.asVector() }.toTypedArray() - return ConvexBufferGeometry(vectors) - } } \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt index 99f10b8c..42af3a05 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt @@ -20,6 +20,7 @@ class ThreePlugin : AbstractPlugin() { private val objectFactories = HashMap, ThreeFactory<*>>() private val compositeFactory = ThreeCompositeFactory(this) + private val proxyFactory = ThreeProxyFactory(this) init { //Add specialized factories here @@ -38,7 +39,6 @@ class ThreePlugin : AbstractPlugin() { return when (obj) { is VisualGroup3D -> Group(obj.mapNotNull { try { - it as VisualObject3D buildObject3D(it) } catch (ex: Throwable) { console.error(ex) @@ -49,6 +49,7 @@ class ThreePlugin : AbstractPlugin() { updatePosition(obj) } is Composite -> compositeFactory(obj) + is Proxy3D -> proxyFactory(obj) else -> { //find specialized factory for this type if it is present val factory = findObjectFactory(obj::class) diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt new file mode 100644 index 00000000..8d39156c --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt @@ -0,0 +1,25 @@ +package hep.dataforge.vis.spatial.three + +import hep.dataforge.vis.spatial.Proxy3D +import hep.dataforge.vis.spatial.VisualObject3D +import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Object3D +import info.laht.threekt.objects.Mesh + +class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory { + private val cache = HashMap() + + override val type = Proxy3D::class + + override fun invoke(obj: Proxy3D): Object3D { + val templateMesh = cache.getOrPut(obj.template) { + three.buildObject3D(obj.template) as Mesh + } + + val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material) + //val mesh = templateMesh.clone() + + mesh.updatePosition(obj) + return mesh + } +} \ No newline at end of file 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 8ee118c8..12db537b 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 @@ -1,11 +1,9 @@ package hep.dataforge.vis.spatial -import hep.dataforge.meta.Meta -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject -class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize: Number, meta: Array) : - VisualLeaf3D(parent, meta), Shape { +class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize: Number) : + VisualLeaf3D(parent), Shape { //TODO add helper for color configuration override fun toGeometry(geometryBuilder: GeometryBuilder) { @@ -34,14 +32,10 @@ class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize } } -//fun VisualGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) = -// Box(this, meta).apply(action).also { add(it) } - -inline fun VisualGroup.box( +inline fun VisualGroup3D.box( xSize: Number, ySize: Number, zSize: Number, name: String? = null, - vararg meta: Meta, action: Box.() -> Unit = {} -) = Box(this, xSize, ySize, zSize, meta).apply(action).also { set(name, it) } \ No newline at end of file +) = Box(this, xSize, ySize, zSize).apply(action).also { set(name, it) } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt index efefaeff..85248893 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Composite.kt @@ -1,9 +1,7 @@ package hep.dataforge.vis.spatial -import hep.dataforge.meta.Meta import hep.dataforge.meta.isEmpty import hep.dataforge.meta.update -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject enum class CompositeType { @@ -16,21 +14,19 @@ open class Composite( parent: VisualObject?, val first: VisualObject3D, val second: VisualObject3D, - val type: CompositeType = CompositeType.UNION, - meta: Array -) : VisualLeaf3D(parent, meta) + val compositeType: CompositeType = CompositeType.UNION +) : VisualLeaf3D(parent) -fun VisualGroup.composite( +fun VisualGroup3D.composite( type: CompositeType, name: String? = null, - vararg meta: Meta, builder: VisualGroup3D.() -> Unit ): Composite { val group = VisualGroup3D().apply(builder) val children = group.filterIsInstance() if (children.size != 2) error("Composite requires exactly two children") - return Composite(this, children[0], children[1], type, meta).also { - if( !group.config.isEmpty()) { + return Composite(this, children[0], children[1], type).also { + if (!group.config.isEmpty()) { it.config.update(group.config) } it.position = group.position @@ -41,11 +37,11 @@ fun VisualGroup.composite( } } -fun VisualGroup3D.union(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) = - composite(CompositeType.UNION, name, *meta, builder = builder) +fun VisualGroup3D.union(name: String? = null, builder: VisualGroup3D.() -> Unit) = + composite(CompositeType.UNION, name, builder = builder) -fun VisualGroup3D.subtract(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) = - composite(CompositeType.SUBTRACT, name, *meta, builder = builder) +fun VisualGroup3D.subtract(name: String? = null, builder: VisualGroup3D.() -> Unit) = + composite(CompositeType.SUBTRACT, name, builder = builder) -fun VisualGroup3D.intersect(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) = - composite(CompositeType.INTERSECT, name, *meta, builder = builder) \ No newline at end of file +fun VisualGroup3D.intersect(name: String? = null, builder: VisualGroup3D.() -> Unit) = + composite(CompositeType.INTERSECT, name, builder = builder) \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt index 87af491c..1d85a50b 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Convex.kt @@ -1,10 +1,8 @@ package hep.dataforge.vis.spatial -import hep.dataforge.meta.Meta -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject -class Convex(parent: VisualObject?, val points: List, meta: Array) : VisualLeaf3D(parent, meta) { +class Convex(parent: VisualObject?, val points: List) : VisualLeaf3D(parent) { companion object { @@ -12,8 +10,8 @@ class Convex(parent: VisualObject?, val points: List, meta: Array Unit = {}) = - ConvexBuilder().apply(action).build(this, meta).also { add(it) } +fun VisualGroup3D.convex(action: ConvexBuilder.() -> Unit = {}) = + ConvexBuilder().apply(action).build(this).also { add(it) } class ConvexBuilder { private val points = ArrayList() @@ -22,7 +20,7 @@ class ConvexBuilder { points.add(Point3D(x, y, z)) } - fun build(parent: VisualObject?, meta: Array): Convex { - return Convex(parent, points, meta) + fun build(parent: VisualObject?): Convex { + return Convex(parent, points) } } \ No newline at end of file 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 850335ec..e9471f61 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,7 +1,5 @@ package hep.dataforge.vis.spatial -import hep.dataforge.meta.Meta -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.number import kotlin.math.PI @@ -9,21 +7,19 @@ import kotlin.math.PI /** * A cylinder or cut cone segment */ -class Cylinder(parent: VisualObject?, var radius: Number, var height: Number, meta: Array) : - VisualLeaf3D(parent, meta) { +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) } -fun VisualGroup.cylinder( +fun VisualGroup3D.cylinder( r: Number, height: Number, name: String? = null, - vararg meta: Meta, block: Cylinder.() -> Unit = {} ): Cylinder { - val cylinder = Cylinder(this, r, height, meta) + val cylinder = Cylinder(this, r, height) cylinder.apply(block) return cylinder.also { set(name, it) } } \ No newline at end of file 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 6d4a33aa..c32118d4 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 @@ -1,7 +1,5 @@ package hep.dataforge.vis.spatial -import hep.dataforge.meta.Meta -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject import kotlin.math.PI import kotlin.math.cos @@ -32,7 +30,7 @@ fun Shape2DBuilder.polygon(vertices: Int, radius: Number) { data class Layer(var x: Number, var y: Number, var z: Number, var scale: Number) -class Extruded(parent: VisualObject?, meta: Array) : VisualLeaf3D(parent, meta), Shape { +class Extruded(parent: VisualObject?) : VisualLeaf3D(parent), Shape { var shape: List = ArrayList() @@ -112,5 +110,5 @@ class Extruded(parent: VisualObject?, meta: Array) : VisualLeaf3D(pare } } -fun VisualGroup.extrude(name: String? = null, vararg meta: Meta, action: Extruded.() -> Unit = {}) = - Extruded(this, meta).apply(action).also { set(name, it) } \ No newline at end of file +fun VisualGroup3D.extrude(name: String? = null, action: Extruded.() -> Unit = {}) = + Extruded(this).apply(action).also { set(name, it) } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy3D.kt new file mode 100644 index 00000000..f3cdcc1e --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Proxy3D.kt @@ -0,0 +1,34 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.MetaBuilder +import hep.dataforge.meta.MetaItem +import hep.dataforge.names.Name +import hep.dataforge.vis.common.AbstractVisualObject +import hep.dataforge.vis.common.VisualObject + +/** + * A proxy [VisualObject3D] to reuse a [template] object + */ +class Proxy3D(parent: VisualObject?, val template: VisualObject3D) : AbstractVisualObject(parent), VisualObject3D { + override var position: Value3 = Value3() + override var rotation: Value3 = Value3() + override var scale: Value3 = Value3(1f, 1f, 1f) + + override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { + return if (inherit) { + super.getProperty(name, false) ?: template.getProperty(name, false) ?: parent?.getProperty(name, inherit) + } else { + super.getProperty(name, false) ?: template.getProperty(name, false) + } + } + + override fun MetaBuilder.updateMeta() { + updatePosition() + } +} + +inline fun VisualGroup3D.proxy( + template: VisualObject3D, + name: String? = null, + action: Proxy3D.() -> Unit = {} +) = Proxy3D(this, template).apply(action).also { set(name, it) } \ No newline at end of file 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 0f21b4d8..0c679976 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 @@ -1,26 +1,23 @@ package hep.dataforge.vis.spatial -import hep.dataforge.meta.Meta -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.number import kotlin.math.PI -class Sphere(parent: VisualObject?, var radius: Number, meta: Array) : VisualLeaf3D(parent, meta) { +class Sphere(parent: VisualObject?, var radius: Number) : VisualLeaf3D(parent) { var phiStart by number(0.0) var phi by number(2 * PI) var thetaStart by number(0.0) var theta by number(PI) } -fun VisualGroup.sphere( +fun VisualGroup3D.sphere( radius: Number, phi: Number = 2 * PI, theta: Number = PI, name: String? = null, - vararg meta: Meta, action: Sphere.() -> Unit = {} -) = Sphere(this, radius, meta).apply(action).apply { +) = Sphere(this, radius).apply(action).apply { this.phi = phi.toDouble() this.theta = theta.toDouble() }.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 ea14f6f9..691d1197 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 @@ -1,16 +1,15 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.* -import hep.dataforge.names.Name import hep.dataforge.names.plus import hep.dataforge.output.Output +import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualLeaf import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.asName -import hep.dataforge.vis.spatial.VisualObject3D.Companion.detailKey -import hep.dataforge.vis.spatial.VisualObject3D.Companion.materialKey -import hep.dataforge.vis.spatial.VisualObject3D.Companion.visibleKey +import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY +import hep.dataforge.vis.spatial.VisualObject3D.Companion.MATERIAL_KEY +import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY data class Value3(var x: Float = 0f, var y: Float = 0f, var z: Float = 0f) @@ -19,13 +18,22 @@ interface VisualObject3D : VisualObject { var rotation: Value3 var scale: Value3 - fun setProperty(name: Name, value: Any?) - fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>? + fun MetaBuilder.updatePosition() { + xPos to position.x + yPos to position.y + zPos to position.z + xRotation to rotation.x + yRotation to rotation.y + zRotation to rotation.z + xScale to scale.x + yScale to scale.y + zScale to scale.z + } companion object { - val materialKey = "material".asName() - val visibleKey = "visible".asName() - val detailKey = "detail".asName() + val MATERIAL_KEY = "material".asName() + val VISIBLE_KEY = "visible".asName() + val DETAIL_KEY = "detail".asName() val x = "x".asName() val y = "y".asName() @@ -53,64 +61,32 @@ interface VisualObject3D : VisualObject { } } -open class VisualLeaf3D(parent: VisualObject?, tagRefs: Array) : VisualLeaf(parent, tagRefs), VisualObject3D { +abstract class VisualLeaf3D(parent: VisualObject?) : AbstractVisualObject(parent), VisualObject3D, Configurable { override var position: Value3 = Value3() override var rotation: Value3 = Value3() override var scale: Value3 = Value3(1f, 1f, 1f) - - private var _config: Config? = null - override val config: Config get() = _config ?: Config().also { _config = it } - - override fun setProperty(name: Name, value: Any?) { - config[name] = value - } - - override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { - return if (inherit) { - config[name] ?: (parent as? VisualObject3D)?.getProperty(name, inherit) ?: parent?.properties[name] - } else { - _config?.get(name) - } - } } -class VisualGroup3D( - parent: VisualObject? = null, - tagRefs: Array = emptyArray() -) : VisualGroup(parent, tagRefs), VisualObject3D { +class VisualGroup3D(parent: VisualObject? = null) : VisualGroup(parent), VisualObject3D, Configurable { override var position: Value3 = Value3() override var rotation: Value3 = Value3() override var scale: Value3 = Value3(1f, 1f, 1f) - private var _config: Config? = null - override val config: Config get() = _config ?: Config().also { _config = it } - - override fun setProperty(name: Name, value: Any?) { - config[name] = value - } - - override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? { - return if (inherit) { - config[name] ?: (parent as? VisualObject3D)?.getProperty(name, inherit) ?: parent?.properties[name] - } else { - _config?.get(name) - } + override fun MetaBuilder.updateMeta() { + updatePosition() + updateChildren() } } +fun VisualGroup3D.group(key: String? = null, action: VisualGroup3D.() -> Unit = {}): VisualGroup3D = + VisualGroup3D(this).apply(action).also { set(key, it) } -fun VisualGroup.group(key: String? = null, vararg meta: Meta, action: VisualGroup3D.() -> Unit = {}): VisualGroup3D = - VisualGroup3D(this, meta).apply(action).also { set(key, it) } - - -fun Output.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) = +fun Output.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) = render(VisualGroup3D().apply(action), meta) - // Common properties - enum class RotationOrder { XYZ, YZX, @@ -132,16 +108,16 @@ var VisualObject3D.rotationOrder: RotationOrder * Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default */ var VisualObject3D.detail: Int? - get() = getProperty(detailKey).int - set(value) = setProperty(detailKey, value) + get() = getProperty(DETAIL_KEY).int + set(value) = setProperty(DETAIL_KEY, value) var VisualObject3D.material: Meta? - get() = getProperty(materialKey).node - set(value) = setProperty(materialKey, value) + get() = getProperty(MATERIAL_KEY).node + set(value) = setProperty(MATERIAL_KEY, value) var VisualObject3D.visible: Boolean? - get() = getProperty(visibleKey).boolean - set(value) = setProperty(visibleKey, value) + get() = getProperty(VISIBLE_KEY).boolean + set(value) = setProperty(VISIBLE_KEY, value) fun VisualObject3D.color(rgb: Int) { material = buildMeta { "color" to rgb } @@ -157,11 +133,66 @@ fun VisualObject3D.color(r: Int, g: Int, b: Int) = material { "blue" to b } -object World { - const val CAMERA_INITIAL_DISTANCE = -500.0 - const val CAMERA_INITIAL_X_ANGLE = -50.0 - const val CAMERA_INITIAL_Y_ANGLE = 0.0 - 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 +var VisualObject3D.x: Number + get() = position.x + set(value) { + position.x = value.toFloat() + propertyChanged(VisualObject3D.xPos) + } + +var VisualObject3D.y: Number + get() = position.y + set(value) { + position.y = value.toFloat() + propertyChanged(VisualObject3D.yPos) + } + +var VisualObject3D.z: Number + get() = position.z + set(value) { + position.z = value.toFloat() + propertyChanged(VisualObject3D.zPos) + } + +var VisualObject3D.rotationX: Number + get() = rotation.x + set(value) { + rotation.x = value.toFloat() + propertyChanged(VisualObject3D.xRotation) + } + +var VisualObject3D.rotationY: Number + get() = rotation.y + set(value) { + rotation.y = value.toFloat() + propertyChanged(VisualObject3D.xRotation) + } + +var VisualObject3D.rotationZ: Number + get() = rotation.z + set(value) { + rotation.z = value.toFloat() + propertyChanged(VisualObject3D.zRotation) + } + +var VisualObject3D.scaleX: Number + get() = scale.x + set(value) { + scale.x = value.toFloat() + propertyChanged(VisualObject3D.xScale) + } + +var VisualObject3D.scaleY: Number + get() = scale.y + set(value) { + scale.y = value.toFloat() + propertyChanged(VisualObject3D.yScale) + } + +var VisualObject3D.scaleZ: Number + get() = scale.z + set(value) { + scale.z = value.toFloat() + propertyChanged(VisualObject3D.zScale) + } + 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 new file mode 100644 index 00000000..639ae56f --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/World.kt @@ -0,0 +1,10 @@ +package hep.dataforge.vis.spatial + +object World { + const val CAMERA_INITIAL_DISTANCE = -500.0 + const val CAMERA_INITIAL_X_ANGLE = -50.0 + const val CAMERA_INITIAL_Y_ANGLE = 0.0 + 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 diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/visualObjectAccess.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/visualObjectAccess.kt deleted file mode 100644 index b3b054a8..00000000 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/visualObjectAccess.kt +++ /dev/null @@ -1,37 +0,0 @@ -package hep.dataforge.vis.spatial - -var VisualObject3D.x: Number - get() = position.x - set(value) {position.x = value.toFloat()} - -var VisualObject3D.y: Number - get() = position.y - set(value) {position.y = value.toFloat()} - -var VisualObject3D.z: Number - get() = position.z - set(value) {position.z = value.toFloat()} - -var VisualObject3D.rotationX: Number - get() = rotation.x - set(value) {rotation.x = value.toFloat()} - -var VisualObject3D.rotationY: Number - get() = rotation.y - set(value) {rotation.y = value.toFloat()} - -var VisualObject3D.rotationZ: Number - get() = rotation.z - set(value) {rotation.z = value.toFloat()} - -var VisualObject3D.scaleX: Number - get() = scale.x - set(value) {scale.x = value.toFloat()} - -var VisualObject3D.scaleY: Number - get() = scale.y - set(value) {scale.y = value.toFloat()} - -var VisualObject3D.scaleZ: Number - get() = scale.z - set(value) {scale.z = value.toFloat()} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/ConvexTest.kt b/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/ConvexTest.kt index 85291506..05b3b957 100644 --- a/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/ConvexTest.kt +++ b/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/ConvexTest.kt @@ -4,14 +4,13 @@ import hep.dataforge.meta.get import hep.dataforge.meta.getAll import hep.dataforge.meta.node import hep.dataforge.names.toName -import hep.dataforge.vis.common.VisualGroup import kotlin.test.Test import kotlin.test.assertEquals class ConvexTest { @Test fun testConvexBuilder() { - val group = VisualGroup().apply { + val group = VisualNode().apply { convex { point(50, 50, -50) point(50, -50, -50) diff --git a/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/GroupTest.kt b/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/GroupTest.kt index a5a16827..4ce69f73 100644 --- a/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/GroupTest.kt +++ b/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/GroupTest.kt @@ -1,7 +1,6 @@ package hep.dataforge.vis.spatial import hep.dataforge.vis.common.Colors -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.color import kotlin.math.PI import kotlin.test.Test @@ -10,7 +9,7 @@ import kotlin.test.assertEquals class GroupTest { @Test fun testGroupWithComposite(){ - val group = VisualGroup().apply{ + val group = VisualNode().apply{ union { box(100, 100, 100) { z = 100