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 bfb51fd4..0365e300 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,11 +1,5 @@ package hep.dataforge.vis.common -import hep.dataforge.meta.Meta -import hep.dataforge.meta.MetaBuilder -import hep.dataforge.meta.buildMeta -import hep.dataforge.meta.set -import hep.dataforge.names.toName - /** * Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt */ @@ -180,24 +174,4 @@ object Colors { const val whitesmoke = 0xF5F5F5 const val yellow = 0xFFFF00 const val yellowgreen = 0x9ACD32 -} - -private val material = "material".toName() - -fun VisualObject.color(rgb: Int) { - this.config[material] = rgb -} - -fun VisualObject.color(meta: Meta) { - this.config[material] = meta -} - -fun VisualObject.color(builder: MetaBuilder.() -> Unit) { - color(buildMeta(builder)) -} - -fun VisualObject.color(r: Int, g: Int, b: Int) = color { - "red" to r - "green" to g - "blue" to b } \ No newline at end of file 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 index 66fdaec3..0e3c2c58 100644 --- 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 @@ -5,22 +5,12 @@ import hep.dataforge.meta.Laminate import hep.dataforge.meta.Meta import hep.dataforge.names.Name import hep.dataforge.provider.Provider -import kotlin.collections.ArrayList -import kotlin.collections.HashMap -import kotlin.collections.HashSet -import kotlin.collections.Iterable -import kotlin.collections.Iterator -import kotlin.collections.Map -import kotlin.collections.emptyMap -import kotlin.collections.forEach -import kotlin.collections.plus -import kotlin.collections.removeAll import kotlin.collections.set /** * A display group which allows both named and unnamed children */ -class VisualGroup( +open class VisualGroup( override val parent: VisualObject? = null, tagRefs: Array = emptyArray() ) : VisualObject, Iterable, Provider { @@ -30,7 +20,9 @@ class VisualGroup( override val defaultTarget: String get() = VisualObject.TYPE override val config = Config() - override val properties: Laminate by lazy { combineProperties(parent, config, tagRefs) } + override val properties: Laminate by lazy { + combineProperties(parent, config, tagRefs) + } override fun iterator(): Iterator = (namedChildren.values + unnamedChildren).iterator() 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 0802b20f..20e3d653 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 @@ -78,9 +78,11 @@ open class VisualLeaf( final override val parent: VisualObject?, tagRefs: Array ) : VisualObject { - final override val config = Config() + override val config = Config() - override val properties: Laminate by lazy { combineProperties(parent, config, tagRefs) } + override val properties: Laminate by lazy { + combineProperties(parent, config, tagRefs) + } } internal fun combineProperties(parent: VisualObject?, config: Config, tagRefs: Array): Laminate { diff --git a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/units.kt b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/units.kt index 25319f58..e1ef3e36 100644 --- a/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/units.kt +++ b/dataforge-vis-spatial-gdml/src/commonMain/kotlin/hep/dataforge/vis/spatial/gdml/units.kt @@ -5,71 +5,72 @@ import scientifik.gdml.GDMLRotation import scientifik.gdml.GDMLSolid import kotlin.math.PI -enum class LUnit(val value: Double) { - MM(1.0), - CM(10.0), - M(1000.0) +enum class LUnit(val value: Float) { + MM(1f), + CM(10f), + M(1000f) } -enum class AUnit(val value: Double) { - DEG(PI / 180), - RAD(1.0), - RADIAN(1.0) +enum class AUnit(val value: Float) { + DEG(PI.toFloat() / 180), + DEGREE(PI.toFloat() / 180), + RAD(1f), + RADIAN(1f) } fun GDMLPosition.unit(): LUnit = LUnit.valueOf(unit.toUpperCase()) -fun GDMLPosition.x(unit: LUnit): Double = if (unit.name == this.unit) { - x.toDouble() +fun GDMLPosition.x(unit: LUnit): Float = if (unit.name == this.unit) { + x.toFloat() } else { - x.toDouble() / unit.value * unit().value + x.toFloat() / unit.value * unit().value } -fun GDMLPosition.y(unit: LUnit): Double = if (unit.name == this.unit) { - y.toDouble() +fun GDMLPosition.y(unit: LUnit): Float = if (unit.name == this.unit) { + y.toFloat() } else { - y.toDouble() / unit.value * unit().value + y.toFloat() / unit.value * unit().value } -fun GDMLPosition.z(unit: LUnit): Double = if (unit.name == this.unit) { - z.toDouble() +fun GDMLPosition.z(unit: LUnit): Float = if (unit.name == this.unit) { + z.toFloat() } else { - z.toDouble() / unit.value * unit().value + z.toFloat() / unit.value * unit().value } fun GDMLRotation.unit(): AUnit = AUnit.valueOf(unit.toUpperCase()) -fun GDMLRotation.x(unit: AUnit = AUnit.RAD): Double = if (unit.name == this.unit) { - x.toDouble() +fun GDMLRotation.x(unit: AUnit = AUnit.RAD): Float = if (unit.name == this.unit) { + x.toFloat() } else { - x.toDouble() / unit.value * unit().value + x.toFloat() / unit.value * unit().value } -fun GDMLRotation.y(unit: AUnit = AUnit.RAD): Double = if (unit.name == this.unit) { - y.toDouble() +fun GDMLRotation.y(unit: AUnit = AUnit.RAD): Float = if (unit.name == this.unit) { + y.toFloat() } else { - y.toDouble() / unit.value * unit().value + y.toFloat() / unit.value * unit().value } -fun GDMLRotation.z(unit: AUnit = AUnit.RAD): Double = if (unit.name == this.unit) { - z.toDouble() +fun GDMLRotation.z(unit: AUnit = AUnit.RAD): Float = if (unit.name == this.unit) { + z.toFloat() } else { - z.toDouble() / unit.value * unit().value + z.toFloat() / unit.value * unit().value } -fun GDMLSolid.lscale(unit: LUnit): Double { - val solidUnit = lunit?.let { LUnit.valueOf(it.toUpperCase()) } ?: return 1.0 +fun GDMLSolid.lscale(unit: LUnit): Float { + val solidUnit = lunit?.let { LUnit.valueOf(it.toUpperCase()) } ?: return 1f return if (solidUnit == unit) { - 1.0 + 1f } else { solidUnit.value / unit.value } } -fun GDMLSolid.ascale(unit: AUnit = AUnit.RAD): Double { - val solidUnit = aunit?.let { AUnit.valueOf(it.toUpperCase()) } ?: return 1.0 +fun GDMLSolid.ascale(unit: AUnit = AUnit.RAD): Float { + val solidUnit = aunit?.let { AUnit.valueOf(it.toUpperCase()) } ?: return 1f return if (solidUnit == unit) { - 1.0 + 1f } else { solidUnit.value / unit.value } 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 f8f3be26..684d7b40 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 @@ -2,9 +2,7 @@ package hep.dataforge.vis.spatial.gdml import hep.dataforge.meta.Meta import hep.dataforge.meta.buildMeta -import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualObject -import hep.dataforge.vis.common.color +import hep.dataforge.meta.builder import hep.dataforge.vis.spatial.* import scientifik.gdml.* import kotlin.math.cos @@ -12,41 +10,87 @@ import kotlin.math.sin import kotlin.random.Random -private fun VisualObject.withPosition( +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( lUnit: LUnit, pos: GDMLPosition? = null, rotation: GDMLRotation? = null, scale: GDMLScale? = null -): VisualObject = apply { +): VisualObject3D = apply { pos?.let { - x = pos.x(lUnit) - y = pos.y(lUnit) - z = pos.z(lUnit) + this@withPosition.position.x = pos.x(lUnit) + this@withPosition.position.y = pos.y(lUnit) + this@withPosition.position.z = pos.z(lUnit) } rotation?.let { - rotationX = rotation.x() - rotationY = rotation.y() - rotationZ = rotation.z() + this@withPosition.rotation.x = rotation.x() + this@withPosition.rotation.y = rotation.y() + this@withPosition.rotation.z = rotation.z() } scale?.let { - scaleX = scale.x - scaleY = scale.y - scaleZ = scale.z + this@withPosition.scale.x = scale.x.toFloat() + this@withPosition.scale.y = scale.y.toFloat() + this@withPosition.scale.z = scale.z.toFloat() } //TODO convert units if needed } private inline operator fun Number.times(d: Double) = toDouble() * d +private inline operator fun Number.times(f: Float) = toFloat() * f -private fun VisualGroup.addSolid( - root: GDML, +private fun VisualGroup3D.addSolid( + context: GDMLTransformer, solid: GDMLSolid, - lUnit: LUnit, name: String? = null, - block: VisualObject.() -> Unit = {} -): VisualObject { - val lScale = solid.lscale(lUnit) + block: VisualObject3D.() -> Unit = {} +): VisualObject3D { + context.solidAdded(solid) + val lScale = solid.lscale(context.lUnit) val aScale = solid.ascale() return when (solid) { is GDMLBox -> box(solid.x * lScale, solid.y * lScale, solid.z * lScale, name) @@ -71,14 +115,14 @@ private fun VisualGroup.addSolid( } is GDMLScaledSolid -> { //Add solid with modified scale - val innerSolid = solid.solidref.resolve(root) + val innerSolid = solid.solidref.resolve(context.root) ?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined") - addSolid(root, innerSolid, lUnit) { + addSolid(context, innerSolid) { block() - scaleX = scaleX.toDouble() * solid.scale.x.toDouble() - scaleY = scaleY.toDouble() * solid.scale.y.toDouble() - scaleZ = scaleZ.toDouble() * solid.scale.z.toDouble() + scale.x *= solid.scale.x.toFloat() + scale.y *= solid.scale.y.toFloat() + scale.z = solid.scale.z.toFloat() } } is GDMLSphere -> sphere(solid.rmax * lScale, solid.deltaphi * aScale, solid.deltatheta * aScale, name) { @@ -102,8 +146,8 @@ private fun VisualGroup.addSolid( } } is GDMLBoolSolid -> { - val first = solid.first.resolve(root) ?: error("") - val second = solid.second.resolve(root) ?: error("") + val first = solid.first.resolve(context.root) ?: error("") + val second = solid.second.resolve(context.root) ?: error("") val type: CompositeType = when (solid) { is GDMLUnion -> CompositeType.UNION is GDMLSubtraction -> CompositeType.SUBTRACT @@ -111,97 +155,114 @@ private fun VisualGroup.addSolid( } return composite(type, name) { - addSolid(root, first, lUnit) { - withPosition(lUnit, solid.resolveFirstPosition(root), solid.resolveFirstRotation(root), null) + addSolid(context, first) { + withPosition( + context.lUnit, + solid.resolveFirstPosition(context.root), + solid.resolveFirstRotation(context.root), + null + ) } - addSolid(root, second, lUnit) { - withPosition(lUnit, solid.resolvePosition(root), solid.resolveRotation(root), null) + addSolid(context, second) { + withPosition( + context.lUnit, + solid.resolvePosition(context.root), + solid.resolveRotation(context.root), + null + ) } } } }.apply(block) } -private fun VisualGroup.addPhysicalVolume( - root: GDML, - physVolume: GDMLPhysVolume, - lUnit: LUnit, - resolveColor: GDMLMaterial.() -> Meta +private fun VisualGroup3D.addPhysicalVolume( + context: GDMLTransformer, + physVolume: GDMLPhysVolume ) { - val volume: GDMLGroup = physVolume.volumeref.resolve(root) + val volume: GDMLGroup = physVolume.volumeref.resolve(context.root) ?: error("Volume with ref ${physVolume.volumeref.ref} could not be resolved") - addVolume( - root, - volume, - lUnit, - physVolume.resolvePosition(root), - physVolume.resolveRotation(root), - physVolume.resolveScale(root), - resolveColor - ) + if (context.acceptGroup(volume)) { + + this[physVolume.name] = volume( + context, + volume, + physVolume.resolvePosition(context.root), + physVolume.resolveRotation(context.root), + physVolume.resolveScale(context.root) + ) + } } -private fun VisualGroup.addDivisionVolume( - root: GDML, - divisionVolume: GDMLDivisionVolume, - lUnit: LUnit, - resolveColor: GDMLMaterial.() -> Meta +private fun VisualGroup3D.addDivisionVolume( + context: GDMLTransformer, + divisionVolume: GDMLDivisionVolume ) { - val volume: GDMLGroup = divisionVolume.volumeref.resolve(root) + val volume: GDMLGroup = divisionVolume.volumeref.resolve(context.root) ?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved") //TODO add divisions - addVolume( - root, - volume, - lUnit, - resolveColor = resolveColor + add( + volume( + context, + volume + ) ) } -private fun VisualGroup.addVolume( - root: GDML, +private fun VisualGroup3D.addVolume( + context: GDMLTransformer, group: GDMLGroup, - lUnit: LUnit, position: GDMLPosition? = null, rotation: GDMLRotation? = null, - scale: GDMLScale? = null, - resolveColor: GDMLMaterial.() -> Meta + scale: GDMLScale? = null ) { + this[group.name] = volume(context, group, position, rotation, scale) +} - group(group.name) { - withPosition(lUnit, position, rotation, scale) +private fun volume( + context: GDMLTransformer, + group: GDMLGroup, + position: GDMLPosition? = null, + rotation: GDMLRotation? = null, + scale: GDMLScale? = null +): VisualGroup3D { + + return VisualGroup3D().apply { + withPosition(context.lUnit, position, rotation, scale) if (group is GDMLVolume) { - val solid = group.solidref.resolve(root) + val solid = group.solidref.resolve(context.root) ?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined") - val material = group.materialref.resolve(root) ?: GDMLElement(group.materialref.ref) - //?: error("Material with tag ${group.materialref.ref} for volume ${group.name} not defined") + val material = group.materialref.resolve(context.root) ?: GDMLElement(group.materialref.ref) - addSolid(root, solid, lUnit, solid.name) { - color(material.resolveColor()) + if (context.acceptSolid(solid)) { + addSolid(context, solid, solid.name) { + this.material = context.resolveColor(group, material, solid) + } } when (val vol = group.placement) { - is GDMLPhysVolume -> addPhysicalVolume(root, vol, lUnit, resolveColor) - is GDMLDivisionVolume -> addDivisionVolume(root, vol, lUnit, resolveColor) + is GDMLPhysVolume -> addPhysicalVolume(context, vol) + is GDMLDivisionVolume -> addDivisionVolume(context, vol) } } group.physVolumes.forEach { physVolume -> - addPhysicalVolume(root, physVolume, lUnit, resolveColor) + addPhysicalVolume(context, physVolume) } } } -fun GDML.toVisual(lUnit: LUnit = LUnit.MM): VisualGroup { - val cache = HashMap() - val random = Random(111) +typealias ColorResolver = GDMLGroup?.(GDMLMaterial, GDMLSolid?) -> Meta - fun GDMLMaterial.color(): Meta = cache.getOrPut(this) { - buildMeta { "color" to random.nextInt(0, Int.MAX_VALUE) } + +fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D { + + val context = GDMLTransformer(this).apply(block) + + return volume(context, world).also{ + context.finished() } - - return VisualGroup().also { it.addVolume(this, world, lUnit) { color() } } } \ 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 f3ab193a..f153a18f 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 @@ -88,7 +88,14 @@ private class GDMLDemoApp : ApplicationBase() { launch { message("Loading GDML") } val gdml = GDML.format.parse(GDML.serializer(), it) launch { message("Converting GDML into DF-VIS format") } - val visual = gdml.toVisual(LUnit.CM) + val visual = gdml.toVisual { + lUnit = LUnit.CM + acceptSolid = { solid -> + !solid.name.startsWith("ecal") + && !solid.name.startsWith("V") + && !solid.name.startsWith("U") + } + } launch { message("Rendering") } val output = three.output(canvas) output.render(visual) 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 new file mode 100644 index 00000000..91d5599b --- /dev/null +++ b/dataforge-vis-spatial-gdml/src/jvmMain/kotlin/hep/dataforge/vis/spatial/gdml/testMain.kt @@ -0,0 +1,25 @@ +package hep.dataforge.vis.spatial.gdml + +import nl.adaptivity.xmlutil.StAXReader +import scientifik.gdml.GDML +import java.io.File +import java.net.URL + +fun main() { + val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO") + val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml") + val stream = if (file.exists()) { + file.inputStream() + } else { + url.openStream() + } + + val xmlReader = StAXReader(stream, "UTF-8") + val xml = GDML.format.parse(GDML.serializer(), xmlReader) + xml.toVisual { + lUnit = LUnit.CM + acceptSolid = { solid -> !solid.name.startsWith("ecal") && !solid.name.startsWith("V") } + onFinish = { printStatistics() } + } + readLine() +} \ No newline at end of file diff --git a/dataforge-vis-spatial-gdml/src/jvmTest/kotlin/hep/dataforge/vis/spatial/gdml/BMNTest.kt b/dataforge-vis-spatial-gdml/src/jvmTest/kotlin/hep/dataforge/vis/spatial/gdml/BMNTest.kt deleted file mode 100644 index b98993f5..00000000 --- a/dataforge-vis-spatial-gdml/src/jvmTest/kotlin/hep/dataforge/vis/spatial/gdml/BMNTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package hep.dataforge.vis.spatial.gdml - -import nl.adaptivity.xmlutil.StAXReader -import org.junit.Test -import scientifik.gdml.GDML -import java.io.File -import java.net.URL - -class BMNTest { - - @Test - fun testRead() { - - val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO") - val file = File("D:\\Work\\Projects\\gdml.kt\\src\\commonTest\\resources\\gdml\\geofile_full.xml") - val stream = if (file.exists()) { - file.inputStream() - } else { - url.openStream() - } - - val xmlReader = StAXReader(stream, "UTF-8") - val xml = GDML.format.parse(GDML.serializer(), xmlReader) - repeat(20) { - xml.toVisual() - } - } -} \ No newline at end of file diff --git a/dataforge-vis-spatial-gdml/src/jvmTest/kotlin/hep/dataforge/vis/spatial/gdml/TestConvertor.kt b/dataforge-vis-spatial-gdml/src/jvmTest/kotlin/hep/dataforge/vis/spatial/gdml/TestConvertor.kt new file mode 100644 index 00000000..3644139c --- /dev/null +++ b/dataforge-vis-spatial-gdml/src/jvmTest/kotlin/hep/dataforge/vis/spatial/gdml/TestConvertor.kt @@ -0,0 +1,40 @@ +package hep.dataforge.vis.spatial.gdml + +import nl.adaptivity.xmlutil.StAXReader +import org.junit.Test +import scientifik.gdml.GDML +import java.io.File +import java.net.URL + +class TestConvertor { + + @Test + fun testBMNGeometry() { + val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO") + val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml") + val stream = if (file.exists()) { + file.inputStream() + } else { + url.openStream() + } + + val xmlReader = StAXReader(stream, "UTF-8") + val xml = GDML.format.parse(GDML.serializer(), xmlReader) + xml.toVisual() + } + + @Test + fun testCubes() { + val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml ") + val stream = if (file.exists()) { + file.inputStream() + } else { + return + } + + val xmlReader = StAXReader(stream, "UTF-8") + val xml = GDML.format.parse(GDML.serializer(), xmlReader) + val visual = xml.toVisual() + println(visual) + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt index 41f833da..d0846f36 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoApp.kt @@ -1,9 +1,7 @@ package hep.dataforge.vis.spatial.demo import hep.dataforge.context.ContextBuilder -import hep.dataforge.meta.number import hep.dataforge.vis.common.Colors -import hep.dataforge.vis.common.color import hep.dataforge.vis.hmr.ApplicationBase import hep.dataforge.vis.hmr.startApplication import hep.dataforge.vis.spatial.* @@ -42,6 +40,7 @@ private class ThreeDemoApp : ApplicationBase() { box(100, 100, 100) { z = 110.0 } + box(100, 100, 100) { visible = false x = 110.0 @@ -51,32 +50,32 @@ private class ThreeDemoApp : ApplicationBase() { GlobalScope.launch { while (isActive) { delay(500) - visible = !visible + visible = !(visible ?: false) } } } } - var material by group.config.number(1530).int - GlobalScope.launch { val random = Random(111) while (isActive) { delay(1000) - material = random.nextInt(0, Int.MAX_VALUE) + group.color(random.nextInt(0, Int.MAX_VALUE)) } } } -// demo("jsroot", "JSROOT cube") { -// jsRootGeometry { -// y = 110.0 -// shape = box(50, 50, 50) -// color(Colors.lightcoral) -// rotationX = PI / 4 -// rotationY = PI / 4 -// } -// } + 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 { @@ -86,9 +85,8 @@ private class ThreeDemoApp : ApplicationBase() { for (i in 0..100) { layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i)) } + color(Colors.teal) } - - color(Colors.teal) } demo("CSG", "CSG operations") { @@ -97,7 +95,7 @@ private class ThreeDemoApp : ApplicationBase() { z = 50 } sphere(50) - color { + material { "color" to Colors.lightgreen "opacity" to 0.3 } diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoGrid.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoGrid.kt index a9746081..044eab05 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoGrid.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/demo/ThreeDemoGrid.kt @@ -12,8 +12,8 @@ import hep.dataforge.names.Name import hep.dataforge.names.toName import hep.dataforge.output.Output import hep.dataforge.output.OutputManager -import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject +import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.vis.spatial.render import hep.dataforge.vis.spatial.three.ThreeOutput import hep.dataforge.vis.spatial.three.ThreePlugin @@ -79,7 +79,7 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager { } } -fun ThreeDemoGrid.demo(name: String, title: String = name, block: VisualGroup.() -> Unit) { +fun ThreeDemoGrid.demo(name: String, title: String = name, block: VisualGroup3D.() -> Unit) { val meta = buildMeta { "title" to title } 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 1fd7f49d..0f04d318 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 @@ -10,8 +10,9 @@ import info.laht.threekt.math.Color object Materials { + val DEFAULT_COLOR = Color(Colors.darkgreen) val DEFAULT = MeshPhongMaterial().apply { - this.color.set(Colors.darkgreen) + this.color.set(DEFAULT_COLOR) } } @@ -42,19 +43,18 @@ fun MetaItem<*>.color(): Color { /** * Infer Three material based on meta item */ -fun MetaItem<*>?.material(): Material { - return when (this) { - null -> Materials.DEFAULT - is MetaItem.ValueItem -> MeshBasicMaterial().apply { - color = this@material.color() - } - is MetaItem.NodeItem -> MeshBasicMaterial().apply { - (node["color"] ?: this@material).let { color = it.color() } - opacity = node["opacity"]?.double ?: 1.0 - transparent = node["transparent"].boolean ?: (opacity < 1.0) +fun Meta?.jsMaterial(): Material { + 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 } - } + } diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCylinderFactory.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCylinderFactory.kt index 003c6a29..78765e08 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCylinderFactory.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeCylinderFactory.kt @@ -4,11 +4,12 @@ import hep.dataforge.vis.spatial.Cylinder import hep.dataforge.vis.spatial.detail import info.laht.threekt.core.BufferGeometry import info.laht.threekt.geometries.CylinderBufferGeometry +import kotlin.math.PI import kotlin.math.pow object ThreeCylinderFactory : MeshThreeFactory(Cylinder::class) { override fun buildGeometry(obj: Cylinder): BufferGeometry { - return obj.detail?.let { + val cylinder = obj.detail?.let { val segments = it.toDouble().pow(0.5).toInt() CylinderBufferGeometry( radiusTop = obj.upperRadius, @@ -28,5 +29,6 @@ object ThreeCylinderFactory : MeshThreeFactory(Cylinder::class) { thetaStart = obj.startAngle, thetaLength = obj.angle ) + return cylinder.rotateX(PI/2) } } \ 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 81d1031b..de422961 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 @@ -2,9 +2,10 @@ 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.startsWith import hep.dataforge.provider.Type -import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.onChange import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE @@ -18,13 +19,11 @@ import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.Mesh import kotlin.reflect.KClass -internal val VisualObject.material get() = properties["material"].material() - /** * Builder and updater for three.js object */ @Type(TYPE) -interface ThreeFactory { +interface ThreeFactory { val type: KClass @@ -33,46 +32,50 @@ interface ThreeFactory { companion object { const val TYPE = "threeFactory" - fun buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh { + 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) + 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"]?.material() ?: Materials.DEFAULT + 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["edges.material"]?.material() ?: Materials.DEFAULT + val material = obj.properties["wireframe.material"].node?.jsMaterial() ?: Materials.DEFAULT mesh.add(LineSegments(WireframeGeometry(mesh.geometry as BufferGeometry), material)) } - //set position for meseh + //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.toString() == "material") { + if (name.startsWith(VisualObject3D.materialKey)) { //updated material - mesh.material = obj.material + mesh.material = obj.material.jsMaterial() } else if ( - name.startsWith(PropertyNames3D.position) || - name.startsWith(PropertyNames3D.rotation) || - name.startsWith(PropertyNames3D.scale) || - name.toString() == "visible" + 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 + mesh.material = obj.material.jsMaterial() } } return mesh @@ -83,17 +86,17 @@ interface ThreeFactory { /** * Update position, rotation and visibility */ -internal fun Object3D.updatePosition(obj: VisualObject) { +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 + visible = obj.visible ?: true } /** * Unsafe invocation of a factory */ -operator fun ThreeFactory.invoke(obj: Any): Object3D { +operator fun ThreeFactory.invoke(obj: Any): Object3D { if (type.isInstance(obj)) { return invoke(obj as T) } else { @@ -104,8 +107,7 @@ operator fun ThreeFactory.invoke(obj: Any): Object3D { /** * Basic geometry-based factory */ -abstract class MeshThreeFactory(override val type: KClass) : - ThreeFactory { +abstract class MeshThreeFactory(override val type: KClass) : ThreeFactory { /** * Build a geometry for an object */ @@ -114,8 +116,7 @@ abstract class MeshThreeFactory(override val type: KClass(obj) { buildGeometry(it) } } } diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeOutput.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeOutput.kt index d7c1e78d..c77deea1 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeOutput.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/ThreeOutput.kt @@ -4,8 +4,8 @@ import hep.dataforge.context.Context import hep.dataforge.meta.* import hep.dataforge.output.Output import hep.dataforge.vis.common.Colors -import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.hmr.require +import hep.dataforge.vis.spatial.VisualObject3D import info.laht.threekt.WebGLRenderer import info.laht.threekt.helpers.AxesHelper import info.laht.threekt.lights.AmbientLight @@ -15,7 +15,7 @@ import kotlin.browser.window private val elementResizeEvent = require("element-resize-event") -class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output { +class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output { override val context: Context get() = three.context @@ -58,7 +58,7 @@ class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output, ThreeFactory<*>>() + private val objectFactories = HashMap, ThreeFactory<*>>() private val compositeFactory = ThreeCompositeFactory(this) init { @@ -31,15 +29,16 @@ class ThreePlugin : AbstractPlugin() { objectFactories[Cylinder::class] = ThreeCylinderFactory } - private fun findObjectFactory(type: KClass): ThreeFactory<*>? { + private fun findObjectFactory(type: KClass): ThreeFactory<*>? { return objectFactories[type] ?: context.content>(ThreeFactory.TYPE).values.find { it.type == type } } - fun buildObject3D(obj: VisualObject): Object3D { + fun buildObject3D(obj: VisualObject3D): Object3D { return when (obj) { - is VisualGroup -> Group(obj.mapNotNull { + is VisualGroup3D -> Group(obj.mapNotNull { try { + it as VisualObject3D buildObject3D(it) } catch (ex: Throwable) { console.error(ex) diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/three.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/three.kt index 02d88b8f..20639b70 100644 --- a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/three.kt +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three/three.kt @@ -4,11 +4,7 @@ import hep.dataforge.meta.MetaItem import hep.dataforge.meta.float import hep.dataforge.meta.get import hep.dataforge.meta.node -import hep.dataforge.vis.common.VisualObject -import hep.dataforge.vis.spatial.rotationOrder -import hep.dataforge.vis.spatial.rotationX -import hep.dataforge.vis.spatial.rotationY -import hep.dataforge.vis.spatial.rotationZ +import hep.dataforge.vis.spatial.* import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.Geometry import info.laht.threekt.core.Object3D @@ -25,7 +21,7 @@ fun Group(children: Collection) = info.laht.threekt.objects.Group().ap children.forEach { this.add(it) } } -val VisualObject.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name) +val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name) val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f) 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 b504f8e8..8ee118c8 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 @@ -2,14 +2,12 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.Meta import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualLeaf import hep.dataforge.vis.common.VisualObject class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize: Number, meta: Array) : - VisualLeaf(parent, meta), Shape { + VisualLeaf3D(parent, meta), Shape { //TODO add helper for color configuration - override fun toGeometry(geometryBuilder: GeometryBuilder) { val dx = xSize.toDouble() / 2 val dy = ySize.toDouble() / 2 @@ -39,12 +37,11 @@ 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) } -fun VisualGroup.box( +inline fun VisualGroup.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, meta).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 9cf26382..efefaeff 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,10 +1,9 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.Meta -import hep.dataforge.meta.seal +import hep.dataforge.meta.isEmpty import hep.dataforge.meta.update import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualLeaf import hep.dataforge.vis.common.VisualObject enum class CompositeType { @@ -15,33 +14,38 @@ enum class CompositeType { open class Composite( parent: VisualObject?, - val first: VisualObject, - val second: VisualObject, + val first: VisualObject3D, + val second: VisualObject3D, val type: CompositeType = CompositeType.UNION, meta: Array -) : VisualLeaf(parent, meta) +) : VisualLeaf3D(parent, meta) fun VisualGroup.composite( type: CompositeType, name: String? = null, vararg meta: Meta, - builder: VisualGroup.() -> Unit + builder: VisualGroup3D.() -> Unit ): Composite { - val group = VisualGroup().apply(builder) - val children = group.toList() + val group = VisualGroup3D().apply(builder) + val children = group.filterIsInstance() if (children.size != 2) error("Composite requires exactly two children") - val groupMeta = group.properties.seal() return Composite(this, children[0], children[1], type, meta).also { - it.config.update(groupMeta) + if( !group.config.isEmpty()) { + it.config.update(group.config) + } + it.position = group.position + it.rotation = group.rotation + it.scale = group.scale + it.material = group.material set(name, it) } } -fun VisualGroup.union(name: String? = null, vararg meta: Meta, builder: VisualGroup.() -> Unit) = +fun VisualGroup3D.union(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) = composite(CompositeType.UNION, name, *meta, builder = builder) -fun VisualGroup.subtract(name: String? = null, vararg meta: Meta, builder: VisualGroup.() -> Unit) = +fun VisualGroup3D.subtract(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) = composite(CompositeType.SUBTRACT, name, *meta, builder = builder) -fun VisualGroup.intersect(name: String? = null, vararg meta: Meta, builder: VisualGroup.() -> Unit) = +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 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 35cadcd6..87af491c 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 @@ -2,10 +2,9 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.Meta import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualLeaf import hep.dataforge.vis.common.VisualObject -class Convex(parent: VisualObject?, val points: List, meta: Array) : VisualLeaf(parent, meta) { +class Convex(parent: VisualObject?, val points: List, meta: Array) : VisualLeaf3D(parent, meta) { companion object { 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 be539ba6..850335ec 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 @@ -2,7 +2,6 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.Meta import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualLeaf import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.number import kotlin.math.PI @@ -10,11 +9,9 @@ import kotlin.math.PI /** * A cylinder or cut cone segment */ -class Cylinder(parent: VisualObject?, radius: Number, height: Number, meta: Array) : - VisualLeaf(parent, meta) { - var radius by number(radius) +class Cylinder(parent: VisualObject?, var radius: Number, var height: Number, meta: Array) : + VisualLeaf3D(parent, meta) { var upperRadius by number(radius) - var height by number(height) var startAngle by number(0.0) var angle by number(2 * PI) } 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 d2908323..6d4a33aa 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 @@ -2,7 +2,6 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.Meta import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualLeaf import hep.dataforge.vis.common.VisualObject import kotlin.math.PI import kotlin.math.cos @@ -33,7 +32,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) : VisualLeaf(parent, meta), Shape { +class Extruded(parent: VisualObject?, meta: Array) : VisualLeaf3D(parent, meta), Shape { var shape: List = ArrayList() diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt index befdcbb1..351c8ead 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/GeometryBuilder.kt @@ -1,7 +1,6 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.* -import hep.dataforge.vis.common.VisualObject data class Point2D(val x: Number, val y: Number) : MetaRepr { override fun toMeta(): Meta = buildMeta { @@ -60,6 +59,6 @@ fun GeometryBuilder<*>.face4( face(vertex1, vertex3, vertex4, normal, meta) } -interface Shape : VisualObject { +interface Shape : VisualObject3D { fun toGeometry(geometryBuilder: GeometryBuilder) } \ 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 cdc8e0b9..0f21b4d8 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 @@ -2,17 +2,15 @@ package hep.dataforge.vis.spatial import hep.dataforge.meta.Meta import hep.dataforge.vis.common.VisualGroup -import hep.dataforge.vis.common.VisualLeaf import hep.dataforge.vis.common.VisualObject -import hep.dataforge.vis.common.double +import hep.dataforge.vis.common.number import kotlin.math.PI -class Sphere(parent: VisualObject?, meta: Array) : VisualLeaf(parent, meta) { - var radius by double(50.0) - var phiStart by double(0.0) - var phi by double(2 * PI) - var thetaStart by double(0.0) - var theta by double(PI) +class Sphere(parent: VisualObject?, var radius: Number, meta: Array) : VisualLeaf3D(parent, meta) { + 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( @@ -22,8 +20,7 @@ fun VisualGroup.sphere( name: String? = null, vararg meta: Meta, action: Sphere.() -> Unit = {} -) = Sphere(this, meta).apply(action).apply { - this.radius = radius.toDouble() +) = Sphere(this, radius, meta).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 103ae1a5..ea14f6f9 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,118 +1,115 @@ 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.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 -fun VisualGroup.group(key: String? = null, vararg meta: Meta, action: VisualGroup.() -> Unit = {}): VisualGroup = - VisualGroup(this, meta).apply(action).also { set(key, it) } +data class Value3(var x: Float = 0f, var y: Float = 0f, var z: Float = 0f) +interface VisualObject3D : VisualObject { + var position: Value3 + var rotation: Value3 + var scale: Value3 -fun Output.render(meta: Meta = EmptyMeta, action: VisualGroup.() -> Unit) = - render(VisualGroup().apply(action), meta) + fun setProperty(name: Name, value: Any?) + fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>? -//TODO replace properties by containers? + companion object { + val materialKey = "material".asName() + val visibleKey = "visible".asName() + val detailKey = "detail".asName() -object PropertyNames3D { - val x = "x".asName() - val y = "y".asName() - val z = "z".asName() + val x = "x".asName() + val y = "y".asName() + val z = "z".asName() - val position = "pos".asName() + val position = "pos".asName() - val xPos = position + x - val yPos = position + y - val zPos = position + z + val xPos = position + x + val yPos = position + y + val zPos = position + z - val rotation = "rotation".asName() + val rotation = "rotation".asName() - val xRotation = rotation + x - val yRotation = rotation + y - val zRotation = rotation + z + val xRotation = rotation + x + val yRotation = rotation + y + val zRotation = rotation + z - val rotationOrder = rotation + "order" + val rotationOrder = rotation + "order" - val scale = "scale".asName() + val scale = "scale".asName() - val xScale = scale + x - val yScale = scale + y - val zScale = scale + z + val xScale = scale + x + val yScale = scale + y + val zScale = scale + z + } } +open class VisualLeaf3D(parent: VisualObject?, tagRefs: Array) : VisualLeaf(parent, tagRefs), VisualObject3D { + 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 { + + 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) + } + } +} + + +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) = + render(VisualGroup3D().apply(action), meta) + + // Common properties -/** - * Visibility property. Inherited from parent - */ -var VisualObject.visible - get() = properties["visible"].boolean ?: true - set(value) { - config["visible"] = value - } - -// 3D Object position - -/** - * x position property relative to parent. Not inherited - */ -var VisualObject.x - get() = config[PropertyNames3D.xPos].number ?: 0.0 - set(value) { - config[PropertyNames3D.xPos] = value - } - - -/** - * y position property. Not inherited - */ -var VisualObject.y - get() = config[PropertyNames3D.yPos].number ?: 0.0 - set(value) { - config[PropertyNames3D.yPos] = value - } - - -/** - * z position property. Not inherited - */ -var VisualObject.z - get() = config[PropertyNames3D.zPos].number ?: 0.0 - set(value) { - config[PropertyNames3D.zPos] = value - } - -// 3D Object rotation - - -/** - * x rotation relative to parent. Not inherited - */ -var VisualObject.rotationX - get() = config[PropertyNames3D.xRotation].number ?: 0.0 - set(value) { - config[PropertyNames3D.xRotation] = value - } - -/** - * y rotation relative to parent. Not inherited - */ -var VisualObject.rotationY - get() = config[PropertyNames3D.yRotation].number ?: 0.0 - set(value) { - config[PropertyNames3D.yRotation] = value - } - -/** - * z rotation relative to parent. Not inherited - */ -var VisualObject.rotationZ - get() = config[PropertyNames3D.zRotation].number ?: 0.0 - set(value) { - config[PropertyNames3D.zRotation] = value - } enum class RotationOrder { XYZ, @@ -124,53 +121,41 @@ enum class RotationOrder { } /** - * Rotation order. Not inherited + * Rotation order */ -var VisualObject.rotationOrder: RotationOrder - get() = config[PropertyNames3D.rotationOrder].enum() ?: RotationOrder.XYZ - set(value) { - config[PropertyNames3D.rotationOrder] = value - } +var VisualObject3D.rotationOrder: RotationOrder + get() = getProperty(VisualObject3D.rotationOrder).enum() ?: RotationOrder.XYZ + set(value) = setProperty(VisualObject3D.rotationOrder, value) -// 3D object scale - -/** - * X scale. Not inherited - */ -var VisualObject.scaleX - get() = config[PropertyNames3D.xScale].number ?: 1.0 - set(value) { - config[PropertyNames3D.xScale] = value - } - -/** - * Y scale. Not inherited - */ -var VisualObject.scaleY - get() = config[PropertyNames3D.yScale].number ?: 1.0 - set(value) { - config[PropertyNames3D.yScale] = value - } - -/** - * Z scale. Not inherited - */ -var VisualObject.scaleZ - get() = config[PropertyNames3D.zScale].number ?: 1.0 - set(value) { - config[PropertyNames3D.zScale] = value - } - -//TODO add inherited scale /** * Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default */ -var VisualObject.detail: Int? - get() = properties["detail"]?.int - set(value) { - config["detail"] = value - } +var VisualObject3D.detail: Int? + get() = getProperty(detailKey).int + set(value) = setProperty(detailKey, value) + +var VisualObject3D.material: Meta? + get() = getProperty(materialKey).node + set(value) = setProperty(materialKey, value) + +var VisualObject3D.visible: Boolean? + get() = getProperty(visibleKey).boolean + set(value) = setProperty(visibleKey, value) + +fun VisualObject3D.color(rgb: Int) { + material = buildMeta { "color" to rgb } +} + +fun VisualObject3D.material(builder: MetaBuilder.() -> Unit) { + material = buildMeta(builder) +} + +fun VisualObject3D.color(r: Int, g: Int, b: Int) = material { + "red" to r + "green" to g + "blue" to b +} object World { const val CAMERA_INITIAL_DISTANCE = -500.0 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 new file mode 100644 index 00000000..b3b054a8 --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/visualObjectAccess.kt @@ -0,0 +1,37 @@ +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/GroupTest.kt b/dataforge-vis-spatial/src/commonTest/kotlin/hep/dataforge/vis/spatial/GroupTest.kt index c9f2ea51..a5a16827 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 @@ -18,7 +18,7 @@ class GroupTest { rotationY = PI / 4 } box(100, 100, 100) - color { + material { "color" to Colors.lightgreen "opacity" to 0.3 } @@ -31,7 +31,7 @@ class GroupTest { } box(100, 100, 100) y = 300 - color(Colors.red) + material(Colors.red) } subtract{ box(100, 100, 100) { @@ -41,7 +41,7 @@ class GroupTest { } box(100, 100, 100) y = -300 - color(Colors.blue) + material(Colors.blue) } }