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)
@OptIn(ExperimentalSerializationApi::class)
fun unrefSerializersModule(
refCache: List<RefEntry>,
): SerializersModule = SerializersModule {

View File

@ -21,21 +21,13 @@ kotlin {
useCommonJs()
browser {
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 {
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<ProcessResources>("jvmProcessResources") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
if (devMode) {
dependsOn(jsBrowserDevelopmentExecutableDistribution)
from(jsBrowserDevelopmentExecutableDistribution)
} else {
dependsOn(jsBrowserDistribution)
from(jsBrowserDistribution)
}
}
//distributions {
// main {
// contents {

View File

@ -40,5 +40,5 @@ kotlin {
}
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 {
block()
ambientLight{
ambientLight {
color.set(Colors.white)
}
}

View File

@ -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

View File

@ -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<CanvasControlsProps> = fc("CanvasControls") { props ->
flexColumn {
flexRow {
@ -68,6 +70,7 @@ public val CanvasControls: FC<CanvasControlsProps> = fc("CanvasControls") { prop
}
}
}
@OptIn(DelicateCoroutinesApi::class)
propertyEditor(
scope = props.vision?.manager?.context ?: GlobalScope,
properties = props.canvasOptions.meta,

View File

@ -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<ValueChooserProps> = 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
}

View File

@ -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<Vision> {
private var reset: Boolean = false
private var vision: Vision? = null
private val propertyChange = MutableMeta()
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?) {
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<Name, VisionChange>? = null,

View File

@ -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<Vision> {
}
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<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.
*/
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<Pair<NameToken, Vision>> = 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()
// }
}
}
//

View File

@ -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)
}

View File

@ -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())

View File

@ -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

View File

@ -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)
}

View File

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

View File

@ -22,17 +22,17 @@ import kotlin.reflect.KClass
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
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 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<SolidLabel> {
material
)
mesh.updatePosition(obj)
mesh.updatePosition(vision)
mesh.userData[DO_NOT_HIGHLIGHT_TAG] = true
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 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)
}
}
}
}

View File

@ -22,7 +22,11 @@ public interface ThreeFactory<in T : Vision> {
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 const val TYPE: String = "threeFactory"
@ -32,10 +36,10 @@ public interface ThreeFactory<in T : Vision> {
/**
* 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()
}
}

View File

@ -17,19 +17,21 @@ import kotlin.reflect.KClass
public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> 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" }
}
}
}
}

View File

@ -16,27 +16,29 @@ import kotlin.reflect.KClass
public object ThreeLineFactory : ThreeFactory<PolyLine> {
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 {
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)
}
}
}
}

View File

@ -49,7 +49,7 @@ public object ThreeMaterials {
cached = true
}
private val lineMaterialCache = HashMap<Meta, LineBasicMaterial>()
private val lineMaterialCache = HashMap<Int, LineBasicMaterial>()
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<Meta, Material>()
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<Int, Material>()
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 -> {

View File

@ -30,32 +30,34 @@ public abstract class ThreeMeshFactory<in T : Solid>(
*/
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<in T : Solid>(
@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(

View File

@ -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<Solid>?
}
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<Solid>? = findObjectFactory(obj::class)
val factory: ThreeFactory<Solid>? = 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")
}
}
}

View File

@ -13,19 +13,21 @@ public object ThreePointLightFactory : ThreeFactory<PointLightSource> {
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)
}
}
}

View File

@ -31,33 +31,35 @@ public object ThreeReferenceFactory : ThreeFactory<SolidReference> {
}
}
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)
}
}
}

View File

@ -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<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution)
afterEvaluate {
from(jsBrowserDistribution)
}
}
}
sourceSets {
commonMain {
dependencies {
@ -42,4 +31,22 @@ 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)
}
}