Optimizations... optimizations

This commit is contained in:
Alexander Nozik 2022-08-14 17:22:10 +03:00
parent ac651c4d50
commit e2f281debe
No known key found for this signature in database
GPG Key ID: F7FCF2DD25C71357
25 changed files with 259 additions and 196 deletions

View File

@ -91,7 +91,6 @@ private object RootDecoder {
private fun <T> KSerializer<T>.unref(refCache: List<RefEntry>): KSerializer<T> = RootUnrefSerializer(this, refCache) private fun <T> KSerializer<T>.unref(refCache: List<RefEntry>): KSerializer<T> = RootUnrefSerializer(this, refCache)
@OptIn(ExperimentalSerializationApi::class)
fun unrefSerializersModule( fun unrefSerializersModule(
refCache: List<RefEntry>, refCache: List<RefEntry>,
): SerializersModule = SerializersModule { ): SerializersModule = SerializersModule {

View File

@ -21,19 +21,11 @@ kotlin {
useCommonJs() useCommonJs()
browser { browser {
commonWebpackConfig { commonWebpackConfig {
cssSupport.enabled = false cssSupport {
enabled = false
} }
} }
} }
afterEvaluate {
val jsBrowserDistribution by tasks.getting
tasks.getByName<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution)
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(jsBrowserDistribution)
}
} }
sourceSets { sourceSets {
@ -65,6 +57,23 @@ application {
mainClass.set("ru.mipt.npm.muon.monitor.server.MMServerKt") 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<ProcessResources>("jvmProcessResources") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
if (devMode) {
dependsOn(jsBrowserDevelopmentExecutableDistribution)
from(jsBrowserDevelopmentExecutableDistribution)
} else {
dependsOn(jsBrowserDistribution)
from(jsBrowserDistribution)
}
}
//distributions { //distributions {
// main { // main {
// contents { // contents {

View File

@ -40,5 +40,5 @@ kotlin {
} }
application { application {
mainClassName = "space.kscience.visionforge.solid.demo.FXDemoAppKt" mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt")
} }

View File

@ -20,7 +20,7 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
} }
val vision = solids.solidGroup { val vision = solids.solidGroup {
block() block()
ambientLight{ ambientLight {
color.set(Colors.white) color.set(Colors.white)
} }
} }

View File

@ -1,7 +1,7 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.mpp.stability.nowarn=true kotlin.mpp.stability.nowarn=true
kotlin.jupyter.add.scanner=false kotlin.jupyter.add.scanner=false
kotlin.incremental.js.ir=true #kotlin.incremental.js.ir=true
org.gradle.parallel=true org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4G org.gradle.jvmargs=-Xmx4G

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.bootstrap package space.kscience.visionforge.bootstrap
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.css.BorderStyle import kotlinx.css.BorderStyle
import kotlinx.css.Color import kotlinx.css.Color
@ -47,6 +48,7 @@ public external interface CanvasControlsProps : Props {
public var vision: Vision? public var vision: Vision?
} }
public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props -> public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn { flexColumn {
flexRow { flexRow {
@ -68,6 +70,7 @@ public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { prop
} }
} }
} }
@OptIn(DelicateCoroutinesApi::class)
propertyEditor( propertyEditor(
scope = props.vision?.manager?.context ?: GlobalScope, scope = props.vision?.manager?.context ?: GlobalScope,
properties = props.canvasOptions.meta, properties = props.canvasOptions.meta,

View File

@ -1,5 +1,6 @@
package space.kscience.visionforge.react package space.kscience.visionforge.react
import info.laht.threekt.math.Color
import kotlinx.css.margin import kotlinx.css.margin
import kotlinx.css.pct import kotlinx.css.pct
import kotlinx.css.px import kotlinx.css.px
@ -149,7 +150,7 @@ public val ColorValueChooser: FC<ValueChooserProps> = fc("ColorValueChooser") {
attrs { attrs {
this.value = props.value?.let { value -> this.value = props.value?.let { value ->
if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int) if (value.type == ValueType.NUMBER) Colors.rgbToString(value.int)
else value.string else "#" + Color(value.string).getHexString()
} ?: "#000000" } ?: "#000000"
onChangeFunction = handleChange onChangeFunction = handleChange
} }

View File

@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.* import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.MetaDescriptor
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
import kotlin.jvm.Synchronized import kotlin.jvm.Synchronized
@ -16,18 +17,37 @@ import kotlin.time.Duration
* Create a deep copy of given Vision without external connections. * Create a deep copy of given Vision without external connections.
*/ */
private fun Vision.deepCopy(manager: VisionManager): Vision { private fun Vision.deepCopy(manager: VisionManager): Vision {
if(this is NullVision) return NullVision
//Assuming that unrooted visions are already isolated //Assuming that unrooted visions are already isolated
//TODO replace by efficient deep copy //TODO replace by efficient deep copy
val json = manager.encodeToJsonElement(this) val json = manager.encodeToJsonElement(this)
return manager.decodeFromJson(json) 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] * An update for a [Vision]
*/ */
public class VisionChangeBuilder(private val manager: VisionManager) : MutableVisionContainer<Vision> { public class VisionChangeBuilder(private val manager: VisionManager) : MutableVisionContainer<Vision> {
private var reset: Boolean = false
private var vision: Vision? = null private var vision: Vision? = null
private val propertyChange = MutableMeta() private val propertyChange = MutableMeta()
private val children: HashMap<Name, VisionChangeBuilder> = HashMap() private val children: HashMap<Name, VisionChangeBuilder> = HashMap()
@ -50,8 +70,7 @@ public class VisionChangeBuilder(private val manager: VisionManager) : MutableVi
override fun setChild(name: Name?, child: Vision?) { override fun setChild(name: Name?, child: Vision?) {
if (name == null) error("Static children are not allowed in VisionChange") if (name == null) error("Static children are not allowed in VisionChange")
getOrPutChild(name).apply { getOrPutChild(name).apply {
vision = child vision = child ?: NullVision
reset = vision == null
} }
} }
@ -59,7 +78,6 @@ public class VisionChangeBuilder(private val manager: VisionManager) : MutableVi
* Isolate collected changes by creating detached copies of given visions * Isolate collected changes by creating detached copies of given visions
*/ */
public fun deepCopy(): VisionChange = VisionChange( public fun deepCopy(): VisionChange = VisionChange(
reset,
vision?.deepCopy(manager), vision?.deepCopy(manager),
if (propertyChange.isEmpty()) null else propertyChange.seal(), if (propertyChange.isEmpty()) null else propertyChange.seal(),
if (children.isEmpty()) null else children.mapValues { it.value.deepCopy() } 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. If the Vision is to be removed should be [NullVision]
* @param vision a new value for vision content
* @param properties updated properties * @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. * @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [delete] flag to true.
*/ */
@Serializable @Serializable
public data class VisionChange( public data class VisionChange(
public val delete: Boolean = false,
public val vision: Vision? = null, public val vision: Vision? = null,
public val properties: Meta? = null, public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null, public val children: Map<Name, VisionChange>? = null,

View File

@ -5,6 +5,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.visionforge.VisionChildren.Companion.STATIC_TOKEN_BODY
import kotlin.jvm.Synchronized import kotlin.jvm.Synchronized
@DslMarker @DslMarker
@ -40,6 +41,8 @@ public interface VisionChildren : VisionContainer<Vision> {
} }
public companion object { public companion object {
public const val STATIC_TOKEN_BODY: String = "@static"
public fun empty(owner: Vision): VisionChildren = object : VisionChildren { public fun empty(owner: Vision): VisionChildren = object : VisionChildren {
override val group: Vision get() = owner override val group: Vision get() = owner
override val keys: Set<NameToken> get() = emptySet() override val keys: Set<NameToken> 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. * 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 { public fun MutableVisionChildren.static(child: Vision) {
set(NameToken("@static", index = child.hashCode().toString()), child) set(NameToken(STATIC_TOKEN_BODY, index = child.hashCode().toString()), child)
} }
public fun VisionChildren.asSequence(): Sequence<Pair<NameToken, Vision>> = sequence { public fun VisionChildren.asSequence(): Sequence<Pair<NameToken, Vision>> = sequence {
@ -185,14 +188,14 @@ internal abstract class VisionChildrenImpl(
} }
override fun clear() { override fun clear() {
if (!items.isNullOrEmpty()) { items?.forEach { set(it.key, null) }
updateJobs.values.forEach { // if (!items.isNullOrEmpty()) {
it.cancel() // updateJobs.values.forEach {
} // it.cancel()
updateJobs.clear() // }
items?.clear() // updateJobs.clear()
onChange(Name.EMPTY) // items?.clear()
} // }
} }
} }
// //

View File

@ -35,7 +35,7 @@ public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
change.children?.forEach { (name, change) -> change.children?.forEach { (name, change) ->
when { when {
change.delete -> children.setChild(name, null) change.vision == NullVision -> children.setChild(name, null)
change.vision != null -> children.setChild(name, change.vision) change.vision != null -> children.setChild(name, change.vision)
else -> children.getChild(name)?.update(change) else -> children.getChild(name)?.update(change)
} }

View File

@ -73,6 +73,7 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta), MutableVisionCont
private val defaultSerialModule: SerializersModule = SerializersModule { private val defaultSerialModule: SerializersModule = SerializersModule {
polymorphic(Vision::class) { polymorphic(Vision::class) {
default { SimpleVisionGroup.serializer() } default { SimpleVisionGroup.serializer() }
subclass(NullVision.serializer())
subclass(SimpleVisionGroup.serializer()) subclass(SimpleVisionGroup.serializer())
subclass(VisionOfNumberField.serializer()) subclass(VisionOfNumberField.serializer())
subclass(VisionOfTextField.serializer()) subclass(VisionOfTextField.serializer())

View File

@ -115,7 +115,7 @@ public interface Solid : Vision {
public var Solid.layer: Int public var Solid.layer: Int
get() = properties.getValue(LAYER_KEY, inherit = true)?.int ?: 0 get() = properties.getValue(LAYER_KEY, inherit = true)?.int ?: 0
set(value) { set(value) {
properties.set(LAYER_KEY, value) properties[LAYER_KEY] = value
} }
// Common properties // Common properties

View File

@ -186,7 +186,7 @@ internal class SolidReferenceChild(
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
change.children?.forEach { (name, change) -> change.children?.forEach { (name, change) ->
when { 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") change.vision != null -> error("Updating content of the ref is not allowed")
else -> children.getChild(name)?.update(change) else -> children.getChild(name)?.update(change)
} }

View File

@ -8,10 +8,10 @@ import kotlin.reflect.KClass
public object ThreeAmbientLightFactory : ThreeFactory<AmbientLightSource> { public object ThreeAmbientLightFactory : ThreeFactory<AmbientLightSource> {
override val type: KClass<in AmbientLightSource> get() = AmbientLightSource::class override val type: KClass<in AmbientLightSource> 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 { val res = AmbientLight().apply {
color = obj.color.threeColor() ?: Color(0x404040) color = vision.color.threeColor() ?: Color(0x404040)
intensity = obj.intensity.toDouble() intensity = vision.intensity.toDouble()
} }
return res return res

View File

@ -22,17 +22,17 @@ import kotlin.reflect.KClass
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> { public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class override val type: KClass<in SolidLabel> 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 canvas = document.createElement("canvas") as HTMLCanvasElement
val context = canvas.getContext("2d") as CanvasRenderingContext2D val context = canvas.getContext("2d") as CanvasRenderingContext2D
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}" context.font = "Bold ${vision.fontSize}pt ${vision.fontFamily}"
context.fillStyle = obj.properties.getValue(SolidMaterial.MATERIAL_COLOR_KEY, false, true)?.value ?: "black" context.fillStyle = vision.properties.getValue(SolidMaterial.MATERIAL_COLOR_KEY, false, true)?.value ?: "black"
context.textBaseline = CanvasTextBaseline.MIDDLE context.textBaseline = CanvasTextBaseline.MIDDLE
val metrics = context.measureText(obj.text) val metrics = context.measureText(vision.text)
//canvas.width = metrics.width.toInt() //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 // canvas contents will be used for a texture
@ -50,7 +50,7 @@ public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
material material
) )
mesh.updatePosition(obj) mesh.updatePosition(vision)
mesh.userData[DO_NOT_HIGHLIGHT_TAG] = true mesh.userData[DO_NOT_HIGHLIGHT_TAG] = true
return mesh return mesh

View File

@ -37,21 +37,25 @@ public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory
override val type: KClass<in Composite> get() = Composite::class override val type: KClass<in Composite> get() = Composite::class
override fun build(three: ThreePlugin, obj: Composite): Mesh { override fun build(three: ThreePlugin, vision: Composite, observe: Boolean): Mesh {
val first = three.buildObject3D(obj.first).takeIfMesh() ?: error("First part of composite is not a mesh") val first =
val second = three.buildObject3D(obj.second).takeIfMesh() ?: error("Second part of composite is not a mesh") three.buildObject3D(vision.first, observe).takeIfMesh() ?: error("First part of composite is not a mesh")
return when (obj.compositeType) { 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.GROUP, CompositeType.UNION -> CSG.union(first, second)
CompositeType.INTERSECT -> CSG.intersect(first, second) CompositeType.INTERSECT -> CSG.intersect(first, second)
CompositeType.SUBTRACT -> CSG.subtract(first, second) CompositeType.SUBTRACT -> CSG.subtract(first, second)
}.apply { }.apply {
updatePosition(obj) updatePosition(vision)
applyProperties(obj) applyProperties(vision)
obj.onPropertyChange { name -> if (observe) {
vision.onPropertyChange { name ->
when { when {
//name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
name.startsWith(ThreeMeshFactory.EDGES_KEY) -> applyEdges(obj) name.startsWith(ThreeMeshFactory.EDGES_KEY) -> applyEdges(vision)
else -> updateProperty(obj, name) else -> updateProperty(vision, name)
}
} }
} }
} }

View File

@ -22,7 +22,11 @@ public interface ThreeFactory<in T : Vision> {
public val type: KClass<in T> public val type: KClass<in T>
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 companion object {
public const val TYPE: String = "threeFactory" public const val TYPE: String = "threeFactory"
@ -32,10 +36,10 @@ public interface ThreeFactory<in T : Vision> {
/** /**
* Update position, rotation and visibility * Update position, rotation and visibility
*/ */
public fun Object3D.updatePosition(obj: Vision) { public fun Object3D.updatePosition(vision: Vision) {
visible = obj.visible ?: true visible = vision.visible ?: true
if (obj is Solid) { if (vision is Solid) {
position.set(obj.x, obj.y, obj.z) position.set(vision.x, vision.y, vision.z)
// val quaternion = obj.quaternion // 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(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() updateMatrix()
} }
} }

View File

@ -17,20 +17,22 @@ import kotlin.reflect.KClass
public object ThreeLabelFactory : ThreeFactory<SolidLabel> { public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun build(three: ThreePlugin, obj: SolidLabel): Object3D { override fun build(three: ThreePlugin, vision: SolidLabel, observe: Boolean): Object3D {
val textGeo = TextBufferGeometry(obj.text, jso { val textGeo = TextBufferGeometry(vision.text, jso {
font = obj.fontFamily font = vision.fontFamily
size = 20 size = 20
height = 1 height = 1
curveSegments = 1 curveSegments = 1
}) })
return Mesh(textGeo, ThreeMaterials.DEFAULT).apply { return Mesh(textGeo, ThreeMaterials.DEFAULT).apply {
updateMaterial(obj) createMaterial(vision)
updatePosition(obj) updatePosition(vision)
obj.onPropertyChange { if(observe) {
vision.onPropertyChange(three.context) {
//TODO //TODO
three.logger.warn { "Label parameter change not implemented" } three.logger.warn { "Label parameter change not implemented" }
} }
} }
} }
}
} }

View File

@ -16,27 +16,29 @@ import kotlin.reflect.KClass
public object ThreeLineFactory : ThreeFactory<PolyLine> { public object ThreeLineFactory : ThreeFactory<PolyLine> {
override val type: KClass<PolyLine> get() = PolyLine::class override val type: KClass<PolyLine> 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 { val geometry = BufferGeometry().apply {
setFromPoints(Array((obj.points.size - 1) * 2) { setFromPoints(Array((vision.points.size - 1) * 2) {
obj.points[ceil(it / 2.0).toInt()].toVector() vision.points[ceil(it / 2.0).toInt()].toVector()
}) })
} }
val material = ThreeMaterials.getLineMaterial( val material = ThreeMaterials.getLineMaterial(
obj.properties.getProperty(SolidMaterial.MATERIAL_KEY), vision.properties.getProperty(SolidMaterial.MATERIAL_KEY),
false false
) )
material.linewidth = obj.thickness.toDouble() material.linewidth = vision.thickness.toDouble()
material.color = obj.color.string?.let { Color(it) } ?: DEFAULT_LINE_COLOR material.color = vision.color.string?.let { Color(it) } ?: DEFAULT_LINE_COLOR
return LineSegments(geometry, material).apply { return LineSegments(geometry, material).apply {
updatePosition(obj) updatePosition(vision)
//layers.enable(obj.layer) //layers.enable(obj.layer)
//add listener to object properties //add listener to object properties
obj.onPropertyChange { propertyName -> if(observe) {
updateProperty(obj, propertyName) vision.onPropertyChange(three.context) { propertyName ->
updateProperty(vision, propertyName)
}
} }
} }
} }

View File

@ -49,7 +49,7 @@ public object ThreeMaterials {
cached = true cached = true
} }
private val lineMaterialCache = HashMap<Meta, LineBasicMaterial>() private val lineMaterialCache = HashMap<Int, LineBasicMaterial>()
private fun buildLineMaterial(meta: Meta): LineBasicMaterial = LineBasicMaterial().apply { private fun buildLineMaterial(meta: Meta): LineBasicMaterial = LineBasicMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_LINE_COLOR color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_LINE_COLOR
@ -61,14 +61,12 @@ public object ThreeMaterials {
public fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial { public fun getLineMaterial(meta: Meta?, cache: Boolean): LineBasicMaterial {
if (meta == null) return DEFAULT_LINE if (meta == null) return DEFAULT_LINE
return if (cache) { return if (cache) {
lineMaterialCache.getOrPut(meta) { buildLineMaterial(meta) } lineMaterialCache.getOrPut(meta.hashCode()) { buildLineMaterial(meta) }
} else { } else {
buildLineMaterial(meta) buildLineMaterial(meta)
} }
} }
private val materialCache = HashMap<Meta, Material>()
internal fun buildMaterial(meta: Meta): Material = when (meta[SolidMaterial.TYPE_KEY]?.string) { internal fun buildMaterial(meta: Meta): Material = when (meta[SolidMaterial.TYPE_KEY]?.string) {
"simple" -> MeshBasicMaterial().apply { "simple" -> MeshBasicMaterial().apply {
color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR color = meta[SolidMaterial.COLOR_KEY]?.threeColor() ?: DEFAULT_COLOR
@ -85,7 +83,9 @@ public object ThreeMaterials {
needsUpdate = true needsUpdate = true
} }
internal fun cacheMaterial(meta: Meta): Material = materialCache.getOrPut(meta) { private val materialCache = HashMap<Int, Material>()
internal fun cacheMaterial(meta: Meta): Material = materialCache.getOrPut(meta.hashCode()) {
buildMaterial(meta).apply { buildMaterial(meta).apply {
cached = true cached = true
} }
@ -130,11 +130,11 @@ private var Material.cached: Boolean
userData["cached"] = value userData["cached"] = value
} }
public fun Mesh.updateMaterial(vision: Vision) { public fun Mesh.createMaterial(vision: Vision) {
val ownMaterialMeta = vision.properties.own?.get(SolidMaterial.MATERIAL_KEY) val ownMaterialMeta = vision.properties.own?.get(SolidMaterial.MATERIAL_KEY)
if (ownMaterialMeta == null) { if (ownMaterialMeta == null) {
if (vision is SolidReference && vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()) { if (vision is SolidReference && vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()) {
updateMaterial(vision.prototype) createMaterial(vision.prototype)
} else { } else {
material = ThreeMaterials.cacheMaterial(vision.properties.getProperty(SolidMaterial.MATERIAL_KEY)) 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 || propertyName == SolidMaterial.MATERIAL_KEY + SolidMaterial.TYPE_KEY
) { ) {
//generate a new material since cached material should not be changed //generate a new material since cached material should not be changed
updateMaterial(vision) createMaterial(vision)
} else { } else {
when (propertyName) { when (propertyName) {
SolidMaterial.MATERIAL_COLOR_KEY -> { SolidMaterial.MATERIAL_COLOR_KEY -> {

View File

@ -30,32 +30,34 @@ public abstract class ThreeMeshFactory<in T : Solid>(
*/ */
public abstract fun buildGeometry(obj: T): BufferGeometry public abstract fun buildGeometry(obj: T): BufferGeometry
override fun build(three: ThreePlugin, obj: T): Mesh { override fun build(three: ThreePlugin, vision: T, observe: Boolean): Mesh {
val geometry = buildGeometry(obj) val geometry = buildGeometry(vision)
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty //val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply { val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply {
matrixAutoUpdate = false matrixAutoUpdate = false
//set position for mesh //set position for mesh
updatePosition(obj) updatePosition(vision)
applyProperties(obj) applyProperties(vision)
} }
if(observe) {
//add listener to object properties //add listener to object properties
obj.onPropertyChange { name-> vision.onPropertyChange(three.context) { name ->
when { when {
name.startsWith(Solid.GEOMETRY_KEY) -> { name.startsWith(Solid.GEOMETRY_KEY) -> {
val oldGeometry = mesh.geometry val oldGeometry = mesh.geometry
val newGeometry = buildGeometry(obj) val newGeometry = buildGeometry(vision)
oldGeometry.attributes = newGeometry.attributes oldGeometry.attributes = newGeometry.attributes
//mesh.applyWireFrame(obj) //mesh.applyWireFrame(obj)
mesh.applyEdges(obj) mesh.applyEdges(vision)
newGeometry.dispose() newGeometry.dispose()
} }
//name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj) name.startsWith(EDGES_KEY) -> mesh.applyEdges(vision)
else -> mesh.updateProperty(obj, name) else -> mesh.updateProperty(vision, name)
}
} }
} }
@ -76,26 +78,26 @@ public abstract class ThreeMeshFactory<in T : Solid>(
@VisionBuilder @VisionBuilder
public fun Solid.edges(enabled: Boolean = true, block: SolidMaterial.() -> Unit = {}) { 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) SolidMaterial.write(properties.getProperty(EDGES_MATERIAL_KEY)).apply(block)
} }
internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { internal fun Mesh.applyProperties(vision: Solid): Mesh = apply {
updateMaterial(obj) createMaterial(vision)
applyEdges(obj) applyEdges(vision)
//applyWireFrame(obj) //applyWireFrame(obj)
layers.set(obj.layer) layers.set(vision.layer)
children.forEach { 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 val edges = children.find { it.name == "@edges" } as? LineSegments
//inherited edges definition, enabled by default //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 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) { if (edges == null) {
add( add(
LineSegments( LineSegments(

View File

@ -11,7 +11,7 @@ import space.kscience.dataforge.meta.update
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.visionforge.ElementVisionRenderer import space.kscience.visionforge.ElementVisionRenderer
import space.kscience.visionforge.Vision 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.*
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.visible import space.kscience.visionforge.visible
@ -48,42 +48,47 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
as ThreeFactory<Solid>? as ThreeFactory<Solid>?
} }
public fun buildObject3D(obj: Solid): Object3D = when (obj) { public fun buildObject3D(vision: Solid, observe: Boolean = true): Object3D = when (vision) {
is ThreeJsVision -> obj.render(this) is ThreeJsVision -> vision.render(this)
is SolidReference -> ThreeReferenceFactory.build(this, obj) is SolidReference -> ThreeReferenceFactory.build(this, vision, observe)
is SolidGroup -> { is SolidGroup -> {
val group = ThreeGroup() val group = ThreeGroup()
obj.items.forEach { (token, child) -> vision.items.forEach { (token, child) ->
if (token != SolidGroup.PROTOTYPES_TOKEN && child.ignore != true) { if (token != SolidGroup.PROTOTYPES_TOKEN && child.ignore != true) {
try { 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 group[token] = object3D
} catch (ex: Throwable) { } catch (ex: Throwable) {
logger.error(ex) { "Failed to render $child" } logger.error(ex) { "Failed to render $child" }
ex.printStackTrace()
} }
} }
} }
group.apply { group.apply {
updatePosition(obj) updatePosition(vision)
//obj.onChildrenChange() //obj.onChildrenChange()
if (observe) {
obj.onPropertyChange(context) { name -> vision.properties.changes.onEach { name ->
if ( if (
name.startsWith(Solid.POSITION_KEY) || name.startsWith(Solid.POSITION_KEY) ||
name.startsWith(Solid.ROTATION_KEY) || name.startsWith(Solid.ROTATION_KEY) ||
name.startsWith(Solid.SCALE_KEY) name.startsWith(Solid.SCALE_KEY)
) { ) {
//update position of mesh using this object //update position of mesh using this object
updatePosition(obj) updatePosition(vision)
} else if (name == Vision.VISIBLE_KEY) { } else if (name == Vision.VISIBLE_KEY) {
visible = obj.visible ?: true visible = vision.visible ?: true
}
} }
}.launchIn(context)
obj.children.changes.onEach { childName -> vision.children.changes.onEach { childName ->
val child = obj.children.getChild(childName) if(childName.isEmpty()) return@onEach
val child = vision.children.getChild(childName)
//removing old object //removing old object
findChild(childName)?.let { oldChild -> findChild(childName)?.let { oldChild ->
@ -102,15 +107,16 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
}.launchIn(context) }.launchIn(context)
} }
} }
}
is Composite -> compositeFactory.build(this, obj) is Composite -> compositeFactory.build(this, vision, observe)
else -> { else -> {
//find specialized factory for this type if it is present //find specialized factory for this type if it is present
val factory: ThreeFactory<Solid>? = findObjectFactory(obj::class) val factory: ThreeFactory<Solid>? = findObjectFactory(vision::class)
when { when {
factory != null -> factory.build(this, obj) factory != null -> factory.build(this, vision, observe)
obj is GeometrySolid -> ThreeShapeFactory.build(this, obj) vision is GeometrySolid -> ThreeShapeFactory.build(this, vision, observe)
else -> error("Renderer for ${obj::class} not found") else -> error("Renderer for ${vision::class} not found")
} }
} }
} }

View File

@ -13,19 +13,21 @@ public object ThreePointLightFactory : ThreeFactory<PointLightSource> {
private val DEFAULT_COLOR = Color(0x404040) 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 { val res = PointLight().apply {
matrixAutoUpdate = false matrixAutoUpdate = false
color = obj.color.threeColor() ?: DEFAULT_COLOR color = vision.color.threeColor() ?: DEFAULT_COLOR
intensity = obj.intensity.toDouble() intensity = vision.intensity.toDouble()
updatePosition(obj) updatePosition(vision)
} }
obj.onPropertyChange { name -> if(observe) {
vision.onPropertyChange(three.context) { name ->
when (name) { when (name) {
LightSource::color.name.asName() -> res.color = obj.color.threeColor() ?: DEFAULT_COLOR LightSource::color.name.asName() -> res.color = vision.color.threeColor() ?: DEFAULT_COLOR
LightSource::intensity.name.asName() -> res.intensity = obj.intensity.toDouble() LightSource::intensity.name.asName() -> res.intensity = vision.intensity.toDouble()
else -> res.updateProperty(obj, name) else -> res.updateProperty(vision, name)
}
} }
} }

View File

@ -31,33 +31,35 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReference> {
} }
} }
override fun build(three: ThreePlugin, obj: SolidReference): Object3D { override fun build(three: ThreePlugin, vision: SolidReference, observe: Boolean): Object3D {
val template = obj.prototype val template = vision.prototype
val cachedObject = cache.getOrPut(template) { val cachedObject = cache.getOrPut(template) {
three.buildObject3D(template) three.buildObject3D(template)
} }
val object3D: Object3D = cachedObject.replicate() val object3D: Object3D = cachedObject.replicate()
object3D.updatePosition(obj) object3D.updatePosition(vision)
if (object3D is Mesh) { if (object3D is Mesh) {
//object3D.material = ThreeMaterials.buildMaterial(obj.getProperty(SolidMaterial.MATERIAL_KEY).node!!) //object3D.material = ThreeMaterials.buildMaterial(obj.getProperty(SolidMaterial.MATERIAL_KEY).node!!)
object3D.applyProperties(obj) object3D.applyProperties(vision)
} }
//TODO apply child properties //TODO apply child properties
obj.onPropertyChange { name -> if (observe) {
vision.onPropertyChange(three.context) { name ->
if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) { if (name.firstOrNull()?.body == REFERENCE_CHILD_PROPERTY_PREFIX) {
val childName = name.firstOrNull()?.index?.let(Name::parse) val childName = name.firstOrNull()?.index?.let(Name::parse)
?: error("Wrong syntax for reference child property: '$name'") ?: error("Wrong syntax for reference child property: '$name'")
val propertyName = name.cutFirst() val propertyName = name.cutFirst()
val referenceChild = val referenceChild =
obj.children.getChild(childName) ?: error("Reference child with name '$childName' not found") 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") val child = object3D.findChild(childName) ?: error("Object child with name '$childName' not found")
child.updateProperty(referenceChild, propertyName) child.updateProperty(referenceChild, propertyName)
} else { } else {
object3D.updateProperty(obj, name) object3D.updateProperty(vision, name)
}
} }
} }

View File

@ -1,11 +1,11 @@
plugins { plugins {
id("space.kscience.gradle.mpp") id("space.kscience.gradle.mpp")
} }
val ktorVersion: String by rootProject.extra val ktorVersion: String by rootProject.extra
kotlin { kotlin {
js(IR){ js(IR) {
browser { browser {
webpackTask { webpackTask {
this.outputFileName = "js/visionforge-three.js" this.outputFileName = "js/visionforge-three.js"
@ -14,17 +14,6 @@ kotlin {
binaries.executable() binaries.executable()
} }
afterEvaluate {
val jsBrowserDistribution by tasks.getting
tasks.getByName<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution)
afterEvaluate {
from(jsBrowserDistribution)
}
}
}
sourceSets { sourceSets {
commonMain { commonMain {
dependencies { dependencies {
@ -43,3 +32,21 @@ kotlin {
} }
} }
} }
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<ProcessResources>("jvmProcessResources") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
if (devMode) {
dependsOn(jsBrowserDevelopmentExecutableDistribution)
from(jsBrowserDevelopmentExecutableDistribution)
} else {
dependsOn(jsBrowserDistribution)
from(jsBrowserDistribution)
}
}