forked from kscience/visionforge
Optimizations... optimizations
This commit is contained in:
parent
ac651c4d50
commit
e2f281debe
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -40,5 +40,5 @@ kotlin {
|
||||
}
|
||||
|
||||
application {
|
||||
mainClassName = "space.kscience.visionforge.solid.demo.FXDemoAppKt"
|
||||
mainClass.set("space.kscience.visionforge.solid.demo.FXDemoAppKt")
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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()
|
||||
// }
|
||||
}
|
||||
}
|
||||
//
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 -> {
|
||||
|
@ -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(
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user