Fix reference rendering

This commit is contained in:
2025-02-05 17:22:24 +03:00
parent 5d5e284fe2
commit 83f650e9ee
12 changed files with 132 additions and 36 deletions

View File

@ -9,9 +9,11 @@ import space.kscience.visionforge.Colors
import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.html.VisionForgeStyles
import space.kscience.visionforge.html.startApplication
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.solid.ambientLight
import space.kscience.visionforge.solid.invoke
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.visionManager
fun main() = startApplication { document ->
@ -26,6 +28,7 @@ fun main() = startApplication { document ->
ambientLight {
color(Colors.white)
}
setAsRoot(context.visionManager)
}
renderComposable(element) {

View File

@ -78,7 +78,7 @@ internal class VisionPropertyTest {
var callCounter = 0
val subscription = child.useProperty("test", inherited = true) {
deferred.complete(it.value)
deferred.complete(it?.value)
callCounter++
}

View File

@ -0,0 +1,34 @@
package space.kscience.visionforge.gdml
import org.junit.jupiter.api.fail
import space.kscience.dataforge.context.Global
import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.plus
import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromStream
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.setAsRoot
import space.kscience.visionforge.visionManager
import kotlin.test.Test
class TestConsistency {
private fun failOnOrphan(vision: Vision, prefix: Name = Name.EMPTY) {
if(vision.parent == null) fail { "Parent is not defined for $vision with name '$prefix'" }
if(vision is VisionGroup<*>){
vision.visions.forEach { (token, child)->
failOnOrphan(child, prefix + token)
}
}
}
@Test
public fun noOrphans() {
val stream = javaClass.getResourceAsStream("/gdml/cubes.gdml")!!
val gdml = Gdml.decodeFromStream(stream)
val vision = gdml.toVision()
vision.setAsRoot(Global.visionManager)
failOnOrphan(vision)
}
}

View File

@ -63,7 +63,7 @@ public class SolidGroup : AbstractVision(), SolidContainer, PrototypeHolder, Mut
* If a prototype is a ref, then it is unfolded automatically.
*/
override fun getPrototype(name: Name): Solid? =
prototypes?.getVision(name)?.prototype ?: (parent as? PrototypeHolder)?.getPrototype(name)
prototypes?.getVision(name) ?: (parent as? PrototypeHolder)?.getPrototype(name)
/**
* Create or edit prototype node as a group

View File

@ -27,8 +27,8 @@ import space.kscience.visionforge.solid.SolidReference.Companion.REFERENCE_CHILD
@Suppress("RecursivePropertyAccessor")
public val Vision.prototype: Solid
get() = when (this) {
is SolidReference -> prototype.prototype
is SolidReferenceChild -> prototype.prototype
is SolidReference -> prototype
is SolidReferenceChild -> prototype
is Solid -> this
else -> error("This Vision is neither Solid nor SolidReference")
}
@ -75,7 +75,9 @@ public class SolidReference(
} ?: emptyMap()
override fun getVision(token: NameToken): Solid? = (prototype as? SolidContainer)?.getVision(token)
override fun getVision(token: NameToken): Solid? = (prototype as? SolidContainer)?.getVision(token)?.let {
SolidReferenceChild(this@SolidReference, it, token.asName())
}
override fun readProperty(
@ -208,6 +210,7 @@ private class SolidReferenceChild(
inherited: Boolean,
useStyles: Boolean
): Meta? {
println("read reference property")
val listOfMeta = buildList {
//1. resolve own properties
add(properties[name])
@ -239,6 +242,10 @@ private class SolidReferenceChild(
SolidReferenceChild(owner, this, childName + key)
} ?: emptyMap()
override fun getVision(token: NameToken): Solid? = (prototype as? SolidContainer)?.getVision(token)?.let {
SolidReferenceChild(owner, it, childName + token)
}
companion object {
private fun childToken(childName: Name): NameToken =

View File

@ -1,12 +1,15 @@
package space.kscience.visionforge.solid
import kotlinx.serialization.json.encodeToJsonElement
import space.kscience.dataforge.meta.string
import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.get
import space.kscience.dataforge.names.parseAsName
import space.kscience.visionforge.style
import space.kscience.visionforge.useStyle
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@DFExperimental
class SolidReferenceTest {
@ -14,23 +17,59 @@ class SolidReferenceTest {
val theStyle by style {
SolidMaterial.MATERIAL_COLOR_KEY put "red"
}
newRef("test", Box(100f,100f,100f).apply {
newRef("test", Box(100f, 100f, 100f).apply {
color("blue")
useStyle(theStyle)
})
}
@Test
fun testReferenceProperty(){
fun testReferenceProperty() {
assertEquals("blue", (groupWithReference.get("test") as Solid).color.string)
}
@Test
fun testReferenceSerialization(){
fun testReferenceSerialization() {
val serialized = Solids.jsonForSolids.encodeToJsonElement(groupWithReference)
val deserialized = Solids.jsonForSolids.decodeFromJsonElement(SolidGroup.serializer(), serialized)
assertEquals(groupWithReference.visions["test"]?.color?.string, deserialized.visions["test"]?.color?.string)
assertEquals("blue", (deserialized["test"] as Solid).color.string)
}
@Test
fun refRef() {
val group = testSolids.solidGroup {
prototypes {
solidGroup("group") {
box(100, 100, 100, "refGroupChild")
}
ref("group.refGroupChild".parseAsName(), "refRef")
}
ref("refRef".parseAsName(), "refRefRef")
ref("group".parseAsName(), "groupRef")
}
val boxRef = group["refRefRef"]!!
boxRef.color("red")
assertTrue { boxRef.prototype is SolidReference }
assertTrue { boxRef.prototype.prototype is Box }
assertEquals("red", boxRef.properties[SolidMaterial.MATERIAL_COLOR_KEY].string)
assertEquals(null, boxRef.prototype.properties[SolidMaterial.MATERIAL_COLOR_KEY])
assertEquals(null, boxRef.prototype.prototype.properties[SolidMaterial.MATERIAL_COLOR_KEY])
val groupRef = group["groupRef"] as SolidContainer
val groupChildRef = groupRef.getVision("refGroupChild")
assertTrue(groupChildRef?.prototype is Box)
groupChildRef.color("blue")
assertEquals("blue", groupChildRef.properties[SolidMaterial.MATERIAL_COLOR_KEY].string)
assertEquals(null, boxRef.prototype.properties[SolidMaterial.MATERIAL_COLOR_KEY])
assertEquals(null, boxRef.prototype.prototype.properties[SolidMaterial.MATERIAL_COLOR_KEY])
}
}

View File

@ -23,8 +23,8 @@ kscience {
api("io.github.vinceglb:filekit-core:0.8.8")
// api(npm("file-saver", "2.0.5"))
// api(npm("@types/file-saver", "2.0.7"))
implementation(npm("three", "0.143.0"))
implementation(npm("three-csg-ts", "3.1.13"))
implementation(npm("three", "0.173.0"))
implementation(npm("three-csg-ts", "3.2.0"))
implementation(npm("three.meshline", "1.4.0"))
}
}

View File

@ -65,7 +65,7 @@ public fun Object3D.updatePosition(vision: Vision) {
* Update non-position non-geometry property
*/
public fun Object3D.updateProperty(source: Solid, propertyName: Name) {
//console.log("${source::class} updated $propertyName with ${source.readProperty(propertyName, inherited = true)}")
// console.log("${source::class} updated $propertyName with ${source.readProperty(propertyName, inherited = true)}")
if (isMesh(this) && propertyName.startsWith(MATERIAL_KEY)) {
updateMaterialProperty(source, propertyName)
} else if (

View File

@ -6,6 +6,7 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus
import space.kscience.visionforge.Colors
import space.kscience.visionforge.Vision
import space.kscience.visionforge.getStyleNodes
import space.kscience.visionforge.solid.ColorAccessor
import space.kscience.visionforge.solid.SolidMaterial
import space.kscience.visionforge.solid.SolidReference
@ -132,8 +133,8 @@ internal var Material.cached: Boolean
public fun Mesh.setMaterial(vision: Vision) {
if (
vision.readProperty(SolidMaterial.MATERIAL_KEY) == null
// && vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()
vision.properties[SolidMaterial.MATERIAL_KEY] == null
&& vision.getStyleNodes(SolidMaterial.MATERIAL_KEY).isEmpty()
) {
//if this is a reference, use material of the prototype
if (vision is SolidReference) {
@ -141,7 +142,7 @@ public fun Mesh.setMaterial(vision: Vision) {
} else {
material = vision.parent?.let { parent ->
//TODO cache parent material
ThreeMaterials.buildMaterial(parent.properties[SolidMaterial.MATERIAL_KEY] ?: Meta.EMPTY)
ThreeMaterials.buildMaterial(parent.readProperty(SolidMaterial.MATERIAL_KEY) ?: Meta.EMPTY)
} ?: ThreeMaterials.cacheMaterial(vision)
}
} else {
@ -168,8 +169,7 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
SolidMaterial.SPECULAR_COLOR_KEY -> {
material.asDynamic().specular =
vision.readProperty(SolidMaterial.SPECULAR_COLOR_KEY)
?.threeColor()
vision.readProperty(SolidMaterial.SPECULAR_COLOR_KEY)?.threeColor()
?: ThreeMaterials.DEFAULT_COLOR
}
@ -192,11 +192,12 @@ public fun Mesh.updateMaterialProperty(vision: Vision, propertyName: Name) {
material.asDynamic().wireframe = vision.readProperty(
SolidMaterial.MATERIAL_WIREFRAME_KEY,
inherited = true,
)?.boolean ?: false
)?.boolean == true
}
else -> console.warn("Unrecognized material property: $propertyName")
}
material.needsUpdate = true
}
}

View File

@ -37,6 +37,7 @@ public class ThreePlugin : AbstractPlugin(), ComposeHtmlVisionRenderer {
init {
//Add specialized factories here
objectFactories[SolidReference::class] = ThreeReferenceFactory
objectFactories[Box::class] = ThreeBoxFactory
objectFactories[Convex::class] = ThreeConvexFactory
objectFactories[Sphere::class] = ThreeSphereFactory
@ -63,7 +64,6 @@ public class ThreePlugin : AbstractPlugin(), ComposeHtmlVisionRenderer {
*/
public suspend 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()
vision.visions.forEach { (name, child) ->

View File

@ -12,37 +12,48 @@ import three.core.Object3D
import three.objects.Mesh
import kotlin.reflect.KClass
private fun Object3D.lockMaterial(){
if(this is Mesh){
material.cached = true
}
children.forEach {
it.lockMaterial()
}
}
public object ThreeReferenceFactory : ThreeFactory<SolidReference> {
private val cache = HashMap<Solid, Object3D>()
override val type: KClass<SolidReference> = SolidReference::class
private fun Object3D.replicate(): Object3D = when {
isMesh(this) -> Mesh(geometry, material).also {
//clone geometry
it.material.cached = true
it.applyMatrix4(matrix)
}
// private fun Object3D.replicate(): Object3D = when {
// isMesh(this) -> Mesh(geometry, material).also {
// //clone geometry
// it.material.cached = true
// it.applyMatrix4(matrix)
// }
//
// else -> clone(false)
// }.also { obj: Object3D ->
// obj.name = this.name
// children.forEach { child: Object3D ->
// obj.add(child.replicate())
// }
// }
else -> clone(false)
}.also { obj: Object3D ->
obj.name = this.name
children.forEach { child: Object3D ->
obj.add(child.replicate())
}
}
override suspend fun build(three: ThreePlugin, vision: SolidReference, observe: Boolean): Object3D {
val template = vision.prototype
val cachedObject = cache.getOrPut(template) {
three.buildObject3D(template)
three.buildObject3D(template).apply {
lockMaterial()
}
}
val object3D: Object3D = cachedObject.replicate()
val object3D: Object3D = cachedObject.clone(true)
object3D.updatePosition(vision)
if (isMesh(object3D)) {
//object3D.material = ThreeMaterials.buildMaterial(obj.getProperty(SolidMaterial.MATERIAL_KEY).node!!)
object3D.applyProperties(vision)
}

View File

@ -23,6 +23,7 @@ import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import space.kscience.visionforge.solid.three.ThreeCanvas
import space.kscience.visionforge.solid.three.ThreePlugin
import space.kscience.visionforge.styles
import space.kscience.visionforge.writeProperties
@Composable
private fun SimpleThreeView(
@ -137,7 +138,7 @@ public fun ThreeView(
NameCrumbs(selected) { selected = it }
Hr()
PropertyEditor(
rootMeta = vision.properties,
rootMeta = vision.writeProperties(),
getPropertyState = { name ->
if (vision.properties[name] != null) {
EditorPropertyState.Defined