From e2f281debec9d7bafa46438fc02d38767f1c9e65 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Sun, 14 Aug 2022 17:22:10 +0300 Subject: [PATCH] Optimizations... optimizations --- .../mipt/npm/root/serialization/jsonToRoot.kt | 1 - demo/muon-monitor/build.gradle.kts | 31 +++--- demo/solid-showcase/build.gradle.kts | 2 +- .../kscience/visionforge/solid/demo/demo.kt | 2 +- gradle.properties | 2 +- .../visionforge/bootstrap/outputConfig.kt | 3 + .../visionforge/react/valueChooser.kt | 3 +- .../kscience/visionforge/VisionChange.kt | 30 ++++-- .../kscience/visionforge/VisionContainer.kt | 23 +++-- .../space/kscience/visionforge/VisionGroup.kt | 2 +- .../kscience/visionforge/VisionManager.kt | 1 + .../space/kscience/visionforge/solid/Solid.kt | 2 +- .../visionforge/solid/SolidReference.kt | 2 +- .../solid/three/ThreeAmbientLightFactory.kt | 6 +- .../solid/three/ThreeCanvasLabelFactory.kt | 12 +-- .../solid/three/ThreeCompositeFactory.kt | 26 ++--- .../visionforge/solid/three/ThreeFactory.kt | 18 ++-- .../solid/three/ThreeLabelFactory.kt | 18 ++-- .../solid/three/ThreeLineFactory.kt | 20 ++-- .../visionforge/solid/three/ThreeMaterials.kt | 16 ++-- .../solid/three/ThreeMeshFactory.kt | 54 ++++++----- .../visionforge/solid/three/ThreePlugin.kt | 94 ++++++++++--------- .../solid/three/ThreePointLightFactory.kt | 20 ++-- .../solid/three/ThreeReferenceFactory.kt | 32 ++++--- .../build.gradle.kts | 35 ++++--- 25 files changed, 259 insertions(+), 196 deletions(-) diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt index b4a63748..96c6098c 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt @@ -91,7 +91,6 @@ private object RootDecoder { private fun KSerializer.unref(refCache: List): KSerializer = RootUnrefSerializer(this, refCache) - @OptIn(ExperimentalSerializationApi::class) fun unrefSerializersModule( refCache: List, ): SerializersModule = SerializersModule { diff --git a/demo/muon-monitor/build.gradle.kts b/demo/muon-monitor/build.gradle.kts index b2c69a7d..fe3f89cb 100644 --- a/demo/muon-monitor/build.gradle.kts +++ b/demo/muon-monitor/build.gradle.kts @@ -21,21 +21,13 @@ kotlin { useCommonJs() browser { commonWebpackConfig { - cssSupport.enabled = false + cssSupport { + enabled = false + } } } } - afterEvaluate { - val jsBrowserDistribution by tasks.getting - - tasks.getByName("jvmProcessResources") { - dependsOn(jsBrowserDistribution) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from(jsBrowserDistribution) - } - } - sourceSets { commonMain { dependencies { @@ -65,6 +57,23 @@ application { mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt") } +val jsBrowserDistribution by tasks.getting +val jsBrowserDevelopmentExecutableDistribution by tasks.getting + +val devMode = rootProject.findProperty("visionforge.development") as? Boolean + ?: rootProject.version.toString().contains("dev") + +tasks.getByName("jvmProcessResources") { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + if (devMode) { + dependsOn(jsBrowserDevelopmentExecutableDistribution) + from(jsBrowserDevelopmentExecutableDistribution) + } else { + dependsOn(jsBrowserDistribution) + from(jsBrowserDistribution) + } +} + //distributions { // main { // contents { diff --git a/demo/solid-showcase/build.gradle.kts b/demo/solid-showcase/build.gradle.kts index d3e03135..2da1299f 100644 --- a/demo/solid-showcase/build.gradle.kts +++ b/demo/solid-showcase/build.gradle.kts @@ -40,5 +40,5 @@ kotlin { } application { - mainClassName = "space.kscience.visionforge.solid.demo.FXDemoAppKt" + mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt") } \ No newline at end of file diff --git a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt index 0398c6bf..ef009b82 100644 --- a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt +++ b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt @@ -20,7 +20,7 @@ fun VisionLayout.demo(name: String, title: String = name, block: SolidGro } val vision = solids.solidGroup { block() - ambientLight{ + ambientLight { color.set(Colors.white) } } diff --git a/gradle.properties b/gradle.properties index 668f0f5d..aeb2ca21 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ kotlin.code.style=official kotlin.mpp.stability.nowarn=true kotlin.jupyter.add.scanner=false -kotlin.incremental.js.ir=true +#kotlin.incremental.js.ir=true org.gradle.parallel=true org.gradle.jvmargs=-Xmx4G diff --git a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt index deb63381..3dfba202 100644 --- a/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt +++ b/ui/bootstrap/src/main/kotlin/space/kscience/visionforge/bootstrap/outputConfig.kt @@ -1,5 +1,6 @@ package space.kscience.visionforge.bootstrap +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.css.BorderStyle import kotlinx.css.Color @@ -47,6 +48,7 @@ public external interface CanvasControlsProps : Props { public var vision: Vision? } + public val CanvasControls: FC = fc("CanvasControls") { props -> flexColumn { flexRow { @@ -68,6 +70,7 @@ public val CanvasControls: FC = fc("CanvasControls") { prop } } } + @OptIn(DelicateCoroutinesApi::class) propertyEditor( scope = props.vision?.manager?.context ?: GlobalScope, properties = props.canvasOptions.meta, diff --git a/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt b/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt index 7ba72e1c..04c7d946 100644 --- a/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt +++ b/ui/react/src/main/kotlin/space/kscience/visionforge/react/valueChooser.kt @@ -1,5 +1,6 @@ package space.kscience.visionforge.react +import info.laht.threekt.math.Color import kotlinx.css.margin import kotlinx.css.pct import kotlinx.css.px @@ -149,7 +150,7 @@ public val ColorValueChooser: FC = fc("ColorValueChooser") { attrs { this.value = props.value?.let { value -> if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) - else value.string + else "#" + Color(value.string).getHexString() } ?: "#000000" onChangeFunction = handleChange } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt index cfd522b3..1edcdf70 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.serialization.Serializable import space.kscience.dataforge.meta.* +import space.kscience.dataforge.meta.descriptors.MetaDescriptor import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.plus import kotlin.jvm.Synchronized @@ -16,18 +17,37 @@ import kotlin.time.Duration * Create a deep copy of given Vision without external connections. */ private fun Vision.deepCopy(manager: VisionManager): Vision { + if(this is NullVision) return NullVision + //Assuming that unrooted visions are already isolated //TODO replace by efficient deep copy val json = manager.encodeToJsonElement(this) return manager.decodeFromJson(json) } +/** + * A vision used only in change propagation and showing that the target should be removed + */ +@Serializable +public object NullVision : Vision { + override var parent: Vision? + get() = null + set(_) { + error("Can't set parent for null vision") + } + + override val properties: MutableVisionProperties get() = error("Can't get properties of `NullVision`") + + override val descriptor: MetaDescriptor? = null + +} + + /** * An update for a [Vision] */ public class VisionChangeBuilder(private val manager: VisionManager) : MutableVisionContainer { - private var reset: Boolean = false private var vision: Vision? = null private val propertyChange = MutableMeta() private val children: HashMap = HashMap() @@ -50,8 +70,7 @@ public class VisionChangeBuilder(private val manager: VisionManager) : MutableVi override fun setChild(name: Name?, child: Vision?) { if (name == null) error("Static children are not allowed in VisionChange") getOrPutChild(name).apply { - vision = child - reset = vision == null + vision = child ?: NullVision } } @@ -59,7 +78,6 @@ public class VisionChangeBuilder(private val manager: VisionManager) : MutableVi * Isolate collected changes by creating detached copies of given visions */ public fun deepCopy(): VisionChange = VisionChange( - reset, vision?.deepCopy(manager), if (propertyChange.isEmpty()) null else propertyChange.seal(), if (children.isEmpty()) null else children.mapValues { it.value.deepCopy() } @@ -67,14 +85,12 @@ public class VisionChangeBuilder(private val manager: VisionManager) : MutableVi } /** - * @param delete flag showing that this vision child should be removed - * @param vision a new value for vision content + * @param vision a new value for vision content. If the Vision is to be removed should be [NullVision] * @param properties updated properties * @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [delete] flag to true. */ @Serializable public data class VisionChange( - public val delete: Boolean = false, public val vision: Vision? = null, public val properties: Meta? = null, public val children: Map? = null, diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionContainer.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionContainer.kt index bda9ab42..975e170a 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionContainer.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionContainer.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import space.kscience.dataforge.names.* +import space.kscience.visionforge.VisionChildren.Companion.STATIC_TOKEN_BODY import kotlin.jvm.Synchronized @DslMarker @@ -40,6 +41,8 @@ public interface VisionChildren : VisionContainer { } public companion object { + public const val STATIC_TOKEN_BODY: String = "@static" + public fun empty(owner: Vision): VisionChildren = object : VisionChildren { override val group: Vision get() = owner override val keys: Set get() = emptySet() @@ -105,8 +108,8 @@ public operator fun MutableVisionChildren.set(name: String?, vision: Vision?) { /** * Add a static child. Statics could not be found by name, removed or replaced. Changing statics also do not trigger events. */ -public fun MutableVisionChildren.static(child: Vision): Unit { - set(NameToken("@static", index = child.hashCode().toString()), child) +public fun MutableVisionChildren.static(child: Vision) { + set(NameToken(STATIC_TOKEN_BODY, index = child.hashCode().toString()), child) } public fun VisionChildren.asSequence(): Sequence> = sequence { @@ -185,14 +188,14 @@ internal abstract class VisionChildrenImpl( } override fun clear() { - if (!items.isNullOrEmpty()) { - updateJobs.values.forEach { - it.cancel() - } - updateJobs.clear() - items?.clear() - onChange(Name.EMPTY) - } + items?.forEach { set(it.key, null) } +// if (!items.isNullOrEmpty()) { +// updateJobs.values.forEach { +// it.cancel() +// } +// updateJobs.clear() +// items?.clear() +// } } } // diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt index 01786453..8a651ffc 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt @@ -35,7 +35,7 @@ public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup override fun update(change: VisionChange) { change.children?.forEach { (name, change) -> when { - change.delete -> children.setChild(name, null) + change.vision == NullVision -> children.setChild(name, null) change.vision != null -> children.setChild(name, change.vision) else -> children.getChild(name)?.update(change) } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt index 314b15d5..4e64f63a 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt @@ -73,6 +73,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont private val defaultSerialModule: SerializersModule = SerializersModule { polymorphic(Vision::class) { default { SimpleVisionGroup.serializer() } + subclass(NullVision.serializer()) subclass(SimpleVisionGroup.serializer()) subclass(VisionOfNumberField.serializer()) subclass(VisionOfTextField.serializer()) diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt index 2283f187..7d0d0b94 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt @@ -115,7 +115,7 @@ public interface Solid : Vision { public var Solid.layer: Int get() = properties.getValue(LAYER_KEY, inherit = true)?.int ?: 0 set(value) { - properties.set(LAYER_KEY, value) + properties[LAYER_KEY] = value } // Common properties diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt index 0b1bc43e..b54e3ab6 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt @@ -186,7 +186,7 @@ internal class SolidReferenceChild( override fun update(change: VisionChange) { change.children?.forEach { (name, change) -> when { - change.delete -> error("Deleting children inside ref is not allowed.") + change.vision == NullVision -> error("Deleting children inside ref is not allowed.") change.vision != null -> error("Updating content of the ref is not allowed") else -> children.getChild(name)?.update(change) } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt index 9e105bde..5df6cc7a 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeAmbientLightFactory.kt @@ -8,10 +8,10 @@ import kotlin.reflect.KClass public object ThreeAmbientLightFactory : ThreeFactory { override val type: KClass get() = AmbientLightSource::class - override fun build(three: ThreePlugin, obj: AmbientLightSource): AmbientLight { + override fun build(three: ThreePlugin, vision: AmbientLightSource, observe: Boolean): AmbientLight { val res = AmbientLight().apply { - color = obj.color.threeColor() ?: Color(0x404040) - intensity = obj.intensity.toDouble() + color = vision.color.threeColor() ?: Color(0x404040) + intensity = vision.intensity.toDouble() } return res diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt index 8b4e823b..cc798297 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt @@ -22,17 +22,17 @@ import kotlin.reflect.KClass public object ThreeCanvasLabelFactory : ThreeFactory { override val type: KClass get() = SolidLabel::class - override fun build(three: ThreePlugin, obj: SolidLabel): Object3D { + override fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D { val canvas = document.createElement("canvas") as HTMLCanvasElement val context = canvas.getContext("2d") as CanvasRenderingContext2D - context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}" - context.fillStyle = obj.properties.getValue(SolidMaterial.MATERIAL_COLOR_KEY, false, true)?.value ?: "black" + context.font = "Bold ${vision.fontSize}pt ${vision.fontFamily}" + context.fillStyle = vision.properties.getValue(SolidMaterial.MATERIAL_COLOR_KEY, false, true)?.value ?: "black" context.textBaseline = CanvasTextBaseline.MIDDLE - val metrics = context.measureText(obj.text) + val metrics = context.measureText(vision.text) //canvas.width = metrics.width.toInt() - context.fillText(obj.text, (canvas.width - metrics.width) / 2, 0.5 * canvas.height) + context.fillText(vision.text, (canvas.width - metrics.width) / 2, 0.5 * canvas.height) // canvas contents will be used for a texture @@ -50,7 +50,7 @@ public object ThreeCanvasLabelFactory : ThreeFactory { material ) - mesh.updatePosition(obj) + mesh.updatePosition(vision) mesh.userData[DO_NOT_HIGHLIGHT_TAG] = true return mesh diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt index de318f95..e4cb84d5 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt @@ -37,21 +37,25 @@ public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory override val type: KClass get() = Composite::class - override fun build(three: ThreePlugin, obj: Composite): Mesh { - val first = three.buildObject3D(obj.first).takeIfMesh() ?: error("First part of composite is not a mesh") - val second = three.buildObject3D(obj.second).takeIfMesh() ?: error("Second part of composite is not a mesh") - return when (obj.compositeType) { + override fun build(three: ThreePlugin, vision: Composite, observe: Boolean): Mesh { + val first = + three.buildObject3D(vision.first, observe).takeIfMesh() ?: error("First part of composite is not a mesh") + val second = + three.buildObject3D(vision.second, observe).takeIfMesh() ?: error("Second part of composite is not a mesh") + return when (vision.compositeType) { CompositeType.GROUP, CompositeType.UNION -> CSG.union(first, second) CompositeType.INTERSECT -> CSG.intersect(first, second) CompositeType.SUBTRACT -> CSG.subtract(first, second) }.apply { - updatePosition(obj) - applyProperties(obj) - obj.onPropertyChange { name -> - when { - //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) - name.startsWith(ThreeMeshFactory.EDGES_KEY) -> applyEdges(obj) - else -> updateProperty(obj, name) + updatePosition(vision) + applyProperties(vision) + if (observe) { + vision.onPropertyChange { name -> + when { + //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) + name.startsWith(ThreeMeshFactory.EDGES_KEY) -> applyEdges(vision) + else -> updateProperty(vision, name) + } } } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index 8c39aeb3..5c4c6cd5 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt @@ -22,7 +22,11 @@ public interface ThreeFactory { public val type: KClass - public fun build(three: ThreePlugin, obj: T): Object3D + /** + * Build an [Object3D] from [vision]. + * @param observe if false, does not observe the changes in [vision] after render (useful for statics). + */ + public fun build(three: ThreePlugin, vision: T, observe: Boolean = true): Object3D public companion object { public const val TYPE: String = "threeFactory" @@ -32,10 +36,10 @@ public interface ThreeFactory { /** * Update position, rotation and visibility */ -public fun Object3D.updatePosition(obj: Vision) { - visible = obj.visible ?: true - if (obj is Solid) { - position.set(obj.x, obj.y, obj.z) +public fun Object3D.updatePosition(vision: Vision) { + visible = vision.visible ?: true + if (vision is Solid) { + position.set(vision.x, vision.y, vision.z) // val quaternion = obj.quaternion // @@ -46,9 +50,9 @@ public fun Object3D.updatePosition(obj: Vision) { // setRotationFromEuler( Euler(obj.rotationX, obj.rotationY, obj.rotationZ, obj.rotationOrder.name)) // } - setRotationFromEuler( Euler(obj.rotationX, obj.rotationY, obj.rotationZ, obj.rotationOrder.name)) + setRotationFromEuler( Euler(vision.rotationX, vision.rotationY, vision.rotationZ, vision.rotationOrder.name)) - scale.set(obj.scaleX, obj.scaleY, obj.scaleZ) + scale.set(vision.scaleX, vision.scaleY, vision.scaleZ) updateMatrix() } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt index b122f9c3..c9244ea5 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLabelFactory.kt @@ -17,19 +17,21 @@ import kotlin.reflect.KClass public object ThreeLabelFactory : ThreeFactory { override val type: KClass get() = SolidLabel::class - override fun build(three: ThreePlugin, obj: SolidLabel): Object3D { - val textGeo = TextBufferGeometry(obj.text, jso { - font = obj.fontFamily + override fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D { + val textGeo = TextBufferGeometry(vision.text, jso { + font = vision.fontFamily size = 20 height = 1 curveSegments = 1 }) return Mesh(textGeo, ThreeMaterials.DEFAULT).apply { - updateMaterial(obj) - updatePosition(obj) - obj.onPropertyChange { - //TODO - three.logger.warn { "Label parameter change not implemented" } + createMaterial(vision) + updatePosition(vision) + if(observe) { + vision.onPropertyChange(three.context) { + //TODO + three.logger.warn { "Label parameter change not implemented" } + } } } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt index fcd92b7c..557a375f 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt @@ -16,27 +16,29 @@ import kotlin.reflect.KClass public object ThreeLineFactory : ThreeFactory { override val type: KClass get() = PolyLine::class - override fun build(three: ThreePlugin, obj: PolyLine): Object3D { + override fun build(three: ThreePlugin, vision: PolyLine, observe: Boolean): Object3D { val geometry = BufferGeometry().apply { - setFromPoints(Array((obj.points.size - 1) * 2) { - obj.points[ceil(it / 2.0).toInt()].toVector() + setFromPoints(Array((vision.points.size - 1) * 2) { + vision.points[ceil(it / 2.0).toInt()].toVector() }) } val material = ThreeMaterials.getLineMaterial( - obj.properties.getProperty(SolidMaterial.MATERIAL_KEY), + vision.properties.getProperty(SolidMaterial.MATERIAL_KEY), false ) - material.linewidth = obj.thickness.toDouble() - material.color = obj.color.string?.let { Color(it) } ?: DEFAULT_LINE_COLOR + material.linewidth = vision.thickness.toDouble() + material.color = vision.color.string?.let { Color(it) } ?: DEFAULT_LINE_COLOR return LineSegments(geometry, material).apply { - updatePosition(obj) + updatePosition(vision) //layers.enable(obj.layer) //add listener to object properties - obj.onPropertyChange { propertyName -> - updateProperty(obj, propertyName) + if(observe) { + vision.onPropertyChange(three.context) { propertyName -> + updateProperty(vision, propertyName) + } } } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt index 20bd7f3a..57e90750 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMaterials.kt @@ -49,7 +49,7 @@ public object ThreeMaterials { cached = true } - private val lineMaterialCache = HashMap() + private val lineMaterialCache = HashMap() private fun buildLineMaterial(meta: Meta): LineBasicMaterial = LineBasicMaterial().apply { color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_LINE_COLOR @@ -61,14 +61,12 @@ public object ThreeMaterials { public fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial { if (meta == null) return DEFAULT_LINE return if (cache) { - lineMaterialCache.getOrPut(meta) { buildLineMaterial(meta) } + lineMaterialCache.getOrPut(meta.hashCode()) { buildLineMaterial(meta) } } else { buildLineMaterial(meta) } } - private val materialCache = HashMap() - internal fun buildMaterial(meta: Meta): Material = when (meta[SolidMaterial.TYPE_KEY]?.string) { "simple" -> MeshBasicMaterial().apply { color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR @@ -85,7 +83,9 @@ public object ThreeMaterials { needsUpdate = true } - internal fun cacheMaterial(meta: Meta): Material = materialCache.getOrPut(meta) { + private val materialCache = HashMap() + + internal fun cacheMaterial(meta: Meta): Material = materialCache.getOrPut(meta.hashCode()) { buildMaterial(meta).apply { cached = true } @@ -130,11 +130,11 @@ private var Material.cached: Boolean userData["cached"] = value } -public fun Mesh.updateMaterial(vision: Vision) { +public fun Mesh.createMaterial(vision: Vision) { val ownMaterialMeta = vision.properties.own?.get(SolidMaterial.MATERIAL_KEY) if (ownMaterialMeta == null) { if (vision is SolidReference && vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()) { - updateMaterial(vision.prototype) + createMaterial(vision.prototype) } else { material = ThreeMaterials.cacheMaterial(vision.properties.getProperty(SolidMaterial.MATERIAL_KEY)) } @@ -150,7 +150,7 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) { || propertyName == SolidMaterial.MATERIAL_KEY + SolidMaterial.TYPE_KEY ) { //generate a new material since cached material should not be changed - updateMaterial(vision) + createMaterial(vision) } else { when (propertyName) { SolidMaterial.MATERIAL_COLOR_KEY -> { diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt index a35ca12d..96fa2621 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeMeshFactory.kt @@ -30,32 +30,34 @@ public abstract class ThreeMeshFactory( */ public abstract fun buildGeometry(obj: T): BufferGeometry - override fun build(three: ThreePlugin, obj: T): Mesh { - val geometry = buildGeometry(obj) + override fun build(three: ThreePlugin, vision: T, observe: Boolean): Mesh { + val geometry = buildGeometry(vision) //val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply { matrixAutoUpdate = false //set position for mesh - updatePosition(obj) - applyProperties(obj) + updatePosition(vision) + applyProperties(vision) } - //add listener to object properties - obj.onPropertyChange { name-> - when { - name.startsWith(Solid.GEOMETRY_KEY) -> { - val oldGeometry = mesh.geometry - val newGeometry = buildGeometry(obj) - oldGeometry.attributes = newGeometry.attributes - //mesh.applyWireFrame(obj) - mesh.applyEdges(obj) - newGeometry.dispose() + if(observe) { + //add listener to object properties + vision.onPropertyChange(three.context) { name -> + when { + name.startsWith(Solid.GEOMETRY_KEY) -> { + val oldGeometry = mesh.geometry + val newGeometry = buildGeometry(vision) + oldGeometry.attributes = newGeometry.attributes + //mesh.applyWireFrame(obj) + mesh.applyEdges(vision) + newGeometry.dispose() + } + //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) + name.startsWith(EDGES_KEY) -> mesh.applyEdges(vision) + else -> mesh.updateProperty(vision, name) } - //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) - name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj) - else -> mesh.updateProperty(obj, name) } } @@ -76,26 +78,26 @@ public abstract class ThreeMeshFactory( @VisionBuilder public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) { - properties.set(EDGES_ENABLED_KEY, enabled) + properties[EDGES_ENABLED_KEY] = enabled SolidMaterial.write(properties.getProperty(EDGES_MATERIAL_KEY)).apply(block) } -internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { - updateMaterial(obj) - applyEdges(obj) +internal fun Mesh.applyProperties(vision: Solid): Mesh = apply { + createMaterial(vision) + applyEdges(vision) //applyWireFrame(obj) - layers.set(obj.layer) + layers.set(vision.layer) children.forEach { - it.layers.set(obj.layer) + it.layers.set(vision.layer) } } -public fun Mesh.applyEdges(obj: Solid) { +public fun Mesh.applyEdges(vision: Solid) { val edges = children.find { it.name == "@edges" } as? LineSegments //inherited edges definition, enabled by default - if (obj.properties.getProperty(EDGES_ENABLED_KEY, inherit = true).boolean != false) { + if (vision.properties.getValue(EDGES_ENABLED_KEY, inherit = true)?.boolean != false) { val bufferGeometry = geometry as? BufferGeometry ?: return - val material = ThreeMaterials.getLineMaterial(obj.properties.getProperty(EDGES_MATERIAL_KEY), true) + val material = ThreeMaterials.getLineMaterial(vision.properties.getProperty(EDGES_MATERIAL_KEY), true) if (edges == null) { add( LineSegments( diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index c715efdb..ba30e88b 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -11,7 +11,7 @@ import space.kscience.dataforge.meta.update import space.kscience.dataforge.names.* import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.Vision -import space.kscience.visionforge.onPropertyChange +import space.kscience.visionforge.VisionChildren import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.visible @@ -48,69 +48,75 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { as ThreeFactory? } - public fun buildObject3D(obj: Solid): Object3D = when (obj) { - is ThreeJsVision -> obj.render(this) - is SolidReference -> ThreeReferenceFactory.build(this, obj) + public fun buildObject3D(vision: Solid, observe: Boolean = true): Object3D = when (vision) { + is ThreeJsVision -> vision.render(this) + is SolidReference -> ThreeReferenceFactory.build(this, vision, observe) is SolidGroup -> { val group = ThreeGroup() - obj.items.forEach { (token, child) -> + vision.items.forEach { (token, child) -> if (token != SolidGroup.PROTOTYPES_TOKEN && child.ignore != true) { try { - val object3D = buildObject3D(child) + val object3D = buildObject3D( + child, + if (token.body == VisionChildren.STATIC_TOKEN_BODY) false else observe + ) + // disable tracking changes for statics group[token] = object3D } catch (ex: Throwable) { logger.error(ex) { "Failed to render $child" } - ex.printStackTrace() } } } group.apply { - updatePosition(obj) + updatePosition(vision) //obj.onChildrenChange() - - obj.onPropertyChange(context) { name -> - if ( - name.startsWith(Solid.POSITION_KEY) || - name.startsWith(Solid.ROTATION_KEY) || - name.startsWith(Solid.SCALE_KEY) - ) { - //update position of mesh using this object - updatePosition(obj) - } else if (name == Vision.VISIBLE_KEY) { - visible = obj.visible ?: true - } - } - - obj.children.changes.onEach { childName -> - val child = obj.children.getChild(childName) - - //removing old object - findChild(childName)?.let { oldChild -> - oldChild.parent?.remove(oldChild) - } - - //adding new object - if (child != null && child is Solid) { - try { - val object3D = buildObject3D(child) - set(childName, object3D) - } catch (ex: Throwable) { - logger.error(ex) { "Failed to render $child" } + if (observe) { + vision.properties.changes.onEach { name -> + if ( + name.startsWith(Solid.POSITION_KEY) || + name.startsWith(Solid.ROTATION_KEY) || + name.startsWith(Solid.SCALE_KEY) + ) { + //update position of mesh using this object + updatePosition(vision) + } else if (name == Vision.VISIBLE_KEY) { + visible = vision.visible ?: true } - } - }.launchIn(context) + }.launchIn(context) + + vision.children.changes.onEach { childName -> + if(childName.isEmpty()) return@onEach + + val child = vision.children.getChild(childName) + + //removing old object + findChild(childName)?.let { oldChild -> + oldChild.parent?.remove(oldChild) + } + + //adding new object + if (child != null && child is Solid) { + try { + val object3D = buildObject3D(child) + set(childName, object3D) + } catch (ex: Throwable) { + logger.error(ex) { "Failed to render $child" } + } + } + }.launchIn(context) + } } } - is Composite -> compositeFactory.build(this, obj) + is Composite -> compositeFactory.build(this, vision, observe) else -> { //find specialized factory for this type if it is present - val factory: ThreeFactory? = findObjectFactory(obj::class) + val factory: ThreeFactory? = findObjectFactory(vision::class) when { - factory != null -> factory.build(this, obj) - obj is GeometrySolid -> ThreeShapeFactory.build(this, obj) - else -> error("Renderer for ${obj::class} not found") + factory != null -> factory.build(this, vision, observe) + vision is GeometrySolid -> ThreeShapeFactory.build(this, vision, observe) + else -> error("Renderer for ${vision::class} not found") } } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt index e6c84c94..56df4b8b 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePointLightFactory.kt @@ -13,19 +13,21 @@ public object ThreePointLightFactory : ThreeFactory { private val DEFAULT_COLOR = Color(0x404040) - override fun build(three: ThreePlugin, obj: PointLightSource): PointLight { + override fun build(three: ThreePlugin, vision: PointLightSource, observe: Boolean): PointLight { val res = PointLight().apply { matrixAutoUpdate = false - color = obj.color.threeColor() ?: DEFAULT_COLOR - intensity = obj.intensity.toDouble() - updatePosition(obj) + color = vision.color.threeColor() ?: DEFAULT_COLOR + intensity = vision.intensity.toDouble() + updatePosition(vision) } - obj.onPropertyChange { name -> - when (name) { - LightSource::color.name.asName() -> res.color = obj.color.threeColor() ?: DEFAULT_COLOR - LightSource::intensity.name.asName() -> res.intensity = obj.intensity.toDouble() - else -> res.updateProperty(obj, name) + if(observe) { + vision.onPropertyChange(three.context) { name -> + when (name) { + LightSource::color.name.asName() -> res.color = vision.color.threeColor() ?: DEFAULT_COLOR + LightSource::intensity.name.asName() -> res.intensity = vision.intensity.toDouble() + else -> res.updateProperty(vision, name) + } } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt index ade11074..ec60ddb3 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeReferenceFactory.kt @@ -31,33 +31,35 @@ public object ThreeReferenceFactory : ThreeFactory { } } - override fun build(three: ThreePlugin, obj: SolidReference): Object3D { - val template = obj.prototype + override fun build(three: ThreePlugin, vision: SolidReference, observe: Boolean): Object3D { + val template = vision.prototype val cachedObject = cache.getOrPut(template) { three.buildObject3D(template) } val object3D: Object3D = cachedObject.replicate() - object3D.updatePosition(obj) + object3D.updatePosition(vision) if (object3D is Mesh) { //object3D.material = ThreeMaterials.buildMaterial(obj.getProperty(SolidMaterial.MATERIAL_KEY).node!!) - object3D.applyProperties(obj) + object3D.applyProperties(vision) } //TODO apply child properties - obj.onPropertyChange { name -> - if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { - val childName = name.firstOrNull()?.index?.let(Name::parse) - ?: error("Wrong syntax for reference child property: '$name'") - val propertyName = name.cutFirst() - val referenceChild = - obj.children.getChild(childName) ?: error("Reference child with name '$childName' not found") - val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found") - child.updateProperty(referenceChild, propertyName) - } else { - object3D.updateProperty(obj, name) + if (observe) { + vision.onPropertyChange(three.context) { name -> + if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { + val childName = name.firstOrNull()?.index?.let(Name::parse) + ?: error("Wrong syntax for reference child property: '$name'") + val propertyName = name.cutFirst() + val referenceChild = + vision.children.getChild(childName) ?: error("Reference child with name '$childName' not found") + val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found") + child.updateProperty(referenceChild, propertyName) + } else { + object3D.updateProperty(vision, name) + } } } diff --git a/visionforge-threejs/visionforge-threejs-server/build.gradle.kts b/visionforge-threejs/visionforge-threejs-server/build.gradle.kts index 399eb5b0..7623086d 100644 --- a/visionforge-threejs/visionforge-threejs-server/build.gradle.kts +++ b/visionforge-threejs/visionforge-threejs-server/build.gradle.kts @@ -1,11 +1,11 @@ plugins { id("space.kscience.gradle.mpp") - } +} val ktorVersion: String by rootProject.extra kotlin { - js(IR){ + js(IR) { browser { webpackTask { this.outputFileName = "js/visionforge-three.js" @@ -14,17 +14,6 @@ kotlin { binaries.executable() } - afterEvaluate { - val jsBrowserDistribution by tasks.getting - - tasks.getByName("jvmProcessResources") { - dependsOn(jsBrowserDistribution) - afterEvaluate { - from(jsBrowserDistribution) - } - } - } - sourceSets { commonMain { dependencies { @@ -42,4 +31,22 @@ kotlin { } } } -} \ No newline at end of file +} + + +val jsBrowserDistribution by tasks.getting +val jsBrowserDevelopmentExecutableDistribution by tasks.getting + +val devMode = rootProject.findProperty("visionforge.development") as? Boolean + ?: rootProject.version.toString().contains("dev") + +tasks.getByName("jvmProcessResources") { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + if (devMode) { + dependsOn(jsBrowserDevelopmentExecutableDistribution) + from(jsBrowserDevelopmentExecutableDistribution) + } else { + dependsOn(jsBrowserDistribution) + from(jsBrowserDistribution) + } +}