Raytracing and automatic highlight for JS back-end. Partial solution for #10

This commit is contained in:
Alexander Nozik 2020-01-03 22:12:12 +03:00
parent 9e43d2c572
commit 173deca6af
21 changed files with 508 additions and 203 deletions

17
.github/workflows/gradle.yml vendored Normal file
View File

@ -0,0 +1,17 @@
name: Gradle build
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up JDK 11
uses: actions/setup-java@v1
with:
java-version: 11
- name: Build with Gradle
run: ./gradlew build

View File

@ -107,3 +107,11 @@ tailrec fun VisualObject.findStyle(name: String): Meta? =
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle)) fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
//operator fun VisualObject.get(name: Name): VisualObject?{
// return when {
// name.isEmpty() -> this
// this is VisualGroup -> this[name]
// else -> null
// }
//}

View File

@ -1,7 +1,6 @@
package hep.dataforge.vis.js.editor package hep.dataforge.vis.js.editor
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
@ -14,26 +13,25 @@ import org.w3c.dom.HTMLElement
import org.w3c.dom.HTMLSpanElement import org.w3c.dom.HTMLSpanElement
import kotlin.dom.clear import kotlin.dom.clear
fun Element.objectTree( fun Element.displayObjectTree(
token: NameToken,
obj: VisualObject, obj: VisualObject,
clickCallback: (Name, VisualObject) -> Unit = {_,_->} clickCallback: (Name) -> Unit = {}
) { ) {
clear() clear()
append { append {
card("Object tree") { card("Object tree") {
subTree(Name.EMPTY, token, obj, clickCallback) subTree(Name.EMPTY, obj, clickCallback)
} }
} }
} }
private fun TagConsumer<HTMLElement>.subTree( private fun TagConsumer<HTMLElement>.subTree(
parentName: Name, fullName: Name,
token: NameToken,
obj: VisualObject, obj: VisualObject,
clickCallback: (Name, VisualObject) -> Unit clickCallback: (Name) -> Unit
) { ) {
val fullName = parentName + token // val fullName = parentName + token
val token = fullName.last()?.toString()?:"World"
//display as node if any child is visible //display as node if any child is visible
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) { if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) {
@ -41,8 +39,8 @@ private fun TagConsumer<HTMLElement>.subTree(
div("d-inline-block text-truncate") { div("d-inline-block text-truncate") {
toggle = span("objTree-caret") toggle = span("objTree-caret")
label("objTree-label") { label("objTree-label") {
+token.toString() +token
onClickFunction = { clickCallback(fullName, obj) } onClickFunction = { clickCallback(fullName) }
} }
} }
val subtree = ul("objTree-subtree") val subtree = ul("objTree-subtree")
@ -57,7 +55,7 @@ private fun TagConsumer<HTMLElement>.subTree(
.forEach { (childToken, child) -> .forEach { (childToken, child) ->
append { append {
li().apply { li().apply {
subTree(fullName, childToken, child, clickCallback) subTree(fullName + childToken, child, clickCallback)
} }
} }
} }
@ -68,11 +66,11 @@ private fun TagConsumer<HTMLElement>.subTree(
} }
} }
} else { } else {
val div = div("d-inline-block text-truncate") { div("d-inline-block text-truncate") {
span("objTree-leaf") span("objTree-leaf")
label("objTree-label") { label("objTree-label") {
+token.toString() +token
onClickFunction = { clickCallback(fullName, obj) } onClickFunction = { clickCallback(fullName) }
} }
} }
} }

View File

@ -12,13 +12,20 @@ import hep.dataforge.vis.common.findStyle
import kotlinx.html.dom.append import kotlinx.html.dom.append
import kotlinx.html.js.* import kotlinx.html.js.*
import org.w3c.dom.Element import org.w3c.dom.Element
import kotlin.collections.forEach
import kotlin.collections.isNotEmpty
import kotlin.collections.set
import kotlin.dom.clear import kotlin.dom.clear
//FIXME something rotten in JS-Meta converter //FIXME something rotten in JS-Meta converter
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString()) fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
//TODO add node descriptor instead of configuring property selector //TODO add node descriptor instead of configuring property selector
fun Element.propertyEditor(name: Name, item: VisualObject, propertySelector: (VisualObject) -> Meta = { it.config }) { fun Element.displayPropertyEditor(
name: Name,
item: VisualObject,
propertySelector: (VisualObject) -> Meta = { it.config }
) {
clear() clear()
append { append {

View File

@ -11,7 +11,6 @@ import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.IGNORE_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.IGNORE_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.LAYER_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.LAYER_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.SELECTED_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@ -118,9 +117,9 @@ var VisualObject.ignore: Boolean?
get() = getProperty(IGNORE_KEY,false).boolean get() = getProperty(IGNORE_KEY,false).boolean
set(value) = setProperty(IGNORE_KEY, value) set(value) = setProperty(IGNORE_KEY, value)
var VisualObject.selected: Boolean? //var VisualObject.selected: Boolean?
get() = getProperty(SELECTED_KEY).boolean // get() = getProperty(SELECTED_KEY).boolean
set(value) = setProperty(SELECTED_KEY, value) // set(value) = setProperty(SELECTED_KEY, value)
private fun VisualObject3D.position(): Point3D = private fun VisualObject3D.position(): Point3D =
position ?: Point3D(0.0, 0.0, 0.0).also { position = it } position ?: Point3D(0.0, 0.0, 0.0).also { position = it }

View File

@ -1,8 +1,6 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.Meta
import hep.dataforge.meta.boolean import hep.dataforge.meta.boolean
import hep.dataforge.meta.get
import hep.dataforge.meta.node import hep.dataforge.meta.node
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
@ -10,10 +8,10 @@ import hep.dataforge.names.startsWith
import hep.dataforge.vis.spatial.Material3D import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.layer import hep.dataforge.vis.spatial.layer
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.geometries.EdgesGeometry import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -37,7 +35,7 @@ abstract class MeshThreeFactory<in T : VisualObject3D>(
//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, MeshBasicMaterial()).apply { val mesh = Mesh(geometry, getMaterial(obj)).apply {
matrixAutoUpdate = false matrixAutoUpdate = false
applyEdges(obj) applyEdges(obj)
applyWireFrame(obj) applyWireFrame(obj)
@ -45,9 +43,6 @@ abstract class MeshThreeFactory<in T : VisualObject3D>(
//set position for mesh //set position for mesh
updatePosition(obj) updatePosition(obj)
//set color for mesh
updateMaterial(obj)
layers.enable(obj.layer) layers.enable(obj.layer)
children.forEach { children.forEach {
it.layers.enable(obj.layer) it.layers.enable(obj.layer)

View File

@ -4,33 +4,55 @@ import hep.dataforge.context.Context
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.string import hep.dataforge.meta.string
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.names.toName
import hep.dataforge.output.Renderer import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.specifications.CameraSpec import hep.dataforge.vis.spatial.specifications.CameraSpec
import hep.dataforge.vis.spatial.specifications.CanvasSpec import hep.dataforge.vis.spatial.specifications.CanvasSpec
import hep.dataforge.vis.spatial.specifications.ControlsSpec import hep.dataforge.vis.spatial.specifications.ControlsSpec
import hep.dataforge.vis.spatial.three.ThreeMaterials.HIGHLIGHT_MATERIAL
import info.laht.threekt.WebGLRenderer import info.laht.threekt.WebGLRenderer
import info.laht.threekt.cameras.PerspectiveCamera import info.laht.threekt.cameras.PerspectiveCamera
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.core.Raycaster
import info.laht.threekt.external.controls.OrbitControls import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.external.controls.TrackballControls import info.laht.threekt.external.controls.TrackballControls
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.helpers.AxesHelper import info.laht.threekt.helpers.AxesHelper
import info.laht.threekt.math.Vector2
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import info.laht.threekt.scenes.Scene import info.laht.threekt.scenes.Scene
import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLElement
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.events.MouseEvent
import kotlin.browser.window import kotlin.browser.window
import kotlin.dom.clear import kotlin.dom.clear
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.max import kotlin.math.max
import kotlin.math.sin import kotlin.math.sin
class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> { /**
*
*/
class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> {
override val context: Context get() = three.context override val context: Context get() = three.context
var content: VisualObject3D? = null var content: VisualObject3D? = null
private set private set
private var root: Object3D? = null
private val raycaster = Raycaster()
private val mousePosition: Vector2 = Vector2()
var clickListener: ((Name) -> Unit)? = null
val axes = AxesHelper(spec.axes.size.toInt()).apply { val axes = AxesHelper(spec.axes.size.toInt()).apply {
visible = spec.axes.visible visible = spec.axes.visible
} }
@ -41,6 +63,85 @@ class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<Visua
val camera = buildCamera(spec.camera) val camera = buildCamera(spec.camera)
init {
element.clear()
//Attach listener to track mouse changes
element.addEventListener("mousemove", { event ->
(event as? MouseEvent)?.run {
val rect = element.getBoundingClientRect()
mousePosition.x = ((event.clientX - rect.left) / element.clientWidth) * 2 - 1
mousePosition.y = -((event.clientY - rect.top) / element.clientHeight) * 2 + 1
}
}, false)
element.addEventListener("mousedown", { event ->
val mesh = pick()
if (mesh != null) {
val name = mesh.fullName()
clickListener?.invoke(name)
}
}, false)
camera.aspect = 1.0
val renderer = WebGLRenderer { antialias = true }.apply {
setClearColor(Colors.skyblue, 1)
}
addControls(renderer.domElement, spec.controls)
fun animate() {
val mesh = pick()
if (mesh != null && highlighted != mesh) {
highlighted?.toggleHighlight(false)
mesh.toggleHighlight(true)
}
window.requestAnimationFrame {
animate()
}
renderer.render(scene, camera)
}
element.appendChild(renderer.domElement)
renderer.setSize(max(spec.minSize, element.offsetWidth), max(spec.minSize, element.offsetWidth))
element.onresize = {
renderer.setSize(element.offsetWidth, element.offsetWidth)
camera.updateProjectionMatrix()
}
animate()
}
private fun pick(): Mesh? {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera(mousePosition, camera)
// calculate objects intersecting the picking ray
return root?.let { root ->
val intersects = raycaster.intersectObject(root, true)
val intersect = intersects.firstOrNull()
intersect?.`object` as? Mesh
}
}
/**
* Resolve full name of the object relative to the global root
*/
private fun Object3D.fullName(): Name {
if (root == null) error("Can't resolve element name without the root")
return if (parent == root) {
name.toName()
} else {
(parent?.fullName() ?: Name.EMPTY) + name.toName()
}
}
private fun buildCamera(spec: CameraSpec) = PerspectiveCamera( private fun buildCamera(spec: CameraSpec) = PerspectiveCamera(
spec.fov, spec.fov,
1.0, 1.0,
@ -59,47 +160,56 @@ class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<Visua
} }
} }
fun attach(element: HTMLElement) {
element.clear()
camera.aspect = 1.0
val renderer = WebGLRenderer { antialias = true }.apply {
setClearColor(Colors.skyblue, 1)
}
addControls(renderer.domElement, spec.controls)
fun animate() {
window.requestAnimationFrame {
animate()
}
renderer.render(scene, camera)
}
element.appendChild(renderer.domElement)
renderer.setSize(max(spec.minSize, element.offsetWidth), max(spec.minSize, element.offsetWidth))
element.onresize = {
renderer.setSize(element.offsetWidth, element.offsetWidth)
camera.updateProjectionMatrix()
}
animate()
}
override fun render(obj: VisualObject3D, meta: Meta) { override fun render(obj: VisualObject3D, meta: Meta) {
content = obj //clear old root
scene.children.find { it.name == "@root" }?.let {
scene.remove(it)
}
val object3D = three.buildObject3D(obj) val object3D = three.buildObject3D(obj)
object3D.name = "@root"
scene.add(object3D) scene.add(object3D)
content = obj
root = object3D
}
private var highlighted: Mesh? = null
/**
* Toggle highlight for the given [Mesh] object
*/
private fun Mesh.toggleHighlight(highlight: Boolean) {
if (highlight) {
val edges = LineSegments(
EdgesGeometry(geometry as BufferGeometry),
HIGHLIGHT_MATERIAL
).apply {
name = "@highlight"
}
add(edges)
highlighted = this
} else {
val highlightEdges = children.find { it.name == "@highlight" }
highlightEdges?.let { remove(it) }
} }
} }
fun ThreePlugin.output(element: HTMLElement? = null, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas = /**
ThreeCanvas(this, spec).apply { * Toggle highlight for element with given name
if (element != null) { */
attach(element) fun highlight(name: Name?) {
if (name == null) {
highlighted?.toggleHighlight(false)
highlighted = null
return
}
val mesh = root?.findChild(name) as? Mesh
if (mesh != null && highlighted != mesh) {
highlighted?.toggleHighlight(false)
mesh.toggleHighlight(true)
} }
} }
}
fun ThreePlugin.output(element: HTMLElement, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas =
ThreeCanvas(element, this, spec)

View File

@ -0,0 +1,56 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Label3D
import hep.dataforge.vis.spatial.color
import info.laht.threekt.DoubleSide
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.PlaneBufferGeometry
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.CanvasTextBaseline
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.MIDDLE
import kotlin.browser.document
import kotlin.reflect.KClass
/**
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
*/
object ThreeCanvasLabelFactory: ThreeFactory<Label3D> {
override val type: KClass<in Label3D> get() = Label3D::class
override fun invoke(obj: Label3D): 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.color ?: "black"
context.textBaseline = CanvasTextBaseline.MIDDLE
val metrics = context.measureText(obj.text)
//canvas.width = metrics.width.toInt()
context.fillText(obj.text, (canvas.width - metrics.width)/2, 0.5*canvas.height)
// canvas contents will be used for a texture
val texture = Texture(canvas)
texture.needsUpdate = true
val material = MeshBasicMaterial().apply {
map = texture
side = DoubleSide
transparent = true
}
val mesh = Mesh(
PlaneBufferGeometry(canvas.width, canvas.height),
material
)
mesh.updatePosition(obj)
return mesh
}
}

View File

@ -7,6 +7,7 @@ import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
@ -53,14 +54,13 @@ fun Object3D.updatePosition(obj: VisualObject3D) {
/** /**
* Update non-position non-geometry property * Update non-position non-geometry property
*/ */
fun Object3D.updateProperty(source: VisualObject, propertyName: Name) { fun Object3D.updateProperty(source: VisualObject3D, propertyName: Name) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
updateMaterial(source) this.material = getMaterial(source)
} else if ( } else if (
source is VisualObject3D && propertyName.startsWith(VisualObject3D.position)
(propertyName.startsWith(VisualObject3D.position)
|| propertyName.startsWith(VisualObject3D.rotation) || propertyName.startsWith(VisualObject3D.rotation)
|| propertyName.startsWith(VisualObject3D.scale)) || propertyName.startsWith(VisualObject3D.scale)
) { ) {
//update position of mesh using this object //update position of mesh using this object
updatePosition(source) updatePosition(source)

View File

@ -34,7 +34,7 @@ class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) { override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal ?: Vector3(0, 0, 0)) val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal ?: Vector3(0, 0, 0))
meta["materialIndex"].int?.let { face.materialIndex = it } meta["materialIndex"].int?.let { face.materialIndex = it }
meta["color"]?.color()?.let { face.color = it } meta["color"]?.getColor()?.let { face.color = it }
faces.add(face) faces.add(face)
} }

View File

@ -1,56 +1,33 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.js.jsObject
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name
import hep.dataforge.vis.spatial.Label3D import hep.dataforge.vis.spatial.Label3D
import hep.dataforge.vis.spatial.color import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
import info.laht.threekt.DoubleSide
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.PlaneBufferGeometry import info.laht.threekt.geometries.TextBufferGeometry
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.CanvasTextBaseline
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.MIDDLE
import kotlin.browser.document
import kotlin.reflect.KClass import kotlin.reflect.KClass
/** /**
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html *
*/ */
object ThreeLabelFactory : ThreeFactory<Label3D> { object ThreeLabelFactory : ThreeFactory<Label3D> {
override val type: KClass<in Label3D> get() = Label3D::class override val type: KClass<in Label3D> get() = Label3D::class
override fun invoke(obj: Label3D): Object3D { override fun invoke(obj: Label3D): Object3D {
val canvas = document.createElement("canvas") as HTMLCanvasElement val textGeo = TextBufferGeometry( obj.text, jsObject {
val context = canvas.getContext("2d") as CanvasRenderingContext2D font = obj.fontFamily
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}" size = 20
context.fillStyle = obj.color ?: "black" height = 1
context.textBaseline = CanvasTextBaseline.MIDDLE curveSegments = 1
val metrics = context.measureText(obj.text) } )
//canvas.width = metrics.width.toInt() return Mesh(textGeo, getMaterial(obj)).apply {
updatePosition(obj)
obj.onPropertyChange(this@ThreeLabelFactory){name: Name, before: MetaItem<*>?, after: MetaItem<*>? ->
context.fillText(obj.text, (canvas.width - metrics.width)/2, 0.5*canvas.height) //TODO
}
}
// canvas contents will be used for a texture
val texture = Texture(canvas)
texture.needsUpdate = true
val material = MeshBasicMaterial().apply {
map = texture
side = DoubleSide
transparent = true
}
val mesh = Mesh(
PlaneBufferGeometry(canvas.width, canvas.height),
material
)
mesh.updatePosition(obj)
return mesh
} }
} }

View File

@ -3,18 +3,18 @@ package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.Material3D import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.VisualObject3D
import info.laht.threekt.materials.LineBasicMaterial import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.materials.MeshPhongMaterial import info.laht.threekt.materials.MeshPhongMaterial
import info.laht.threekt.math.Color import info.laht.threekt.math.Color
import info.laht.threekt.objects.Mesh
object ThreeMaterials { object ThreeMaterials {
val DEFAULT_COLOR = Color(Colors.darkgreen) val DEFAULT_COLOR = Color(Colors.darkgreen)
val DEFAULT = MeshPhongMaterial().apply { val DEFAULT = MeshBasicMaterial().apply {
color.set(DEFAULT_COLOR) color.set(DEFAULT_COLOR)
} }
val DEFAULT_LINE_COLOR = Color(Colors.black) val DEFAULT_LINE_COLOR = Color(Colors.black)
@ -22,34 +22,49 @@ object ThreeMaterials {
color.set(DEFAULT_LINE_COLOR) color.set(DEFAULT_LINE_COLOR)
} }
val HIGHLIGHT_MATERIAL = LineBasicMaterial().apply {
color.set(Colors.ivory)
linewidth = 8.0
}
// private val materialCache = HashMap<Meta, Material>() fun getLineMaterial(meta: Meta?): LineBasicMaterial {
private val lineMaterialCache = HashMap<Meta?, LineBasicMaterial>() if (meta == null) return DEFAULT_LINE
return LineBasicMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR
// fun buildMaterial(meta: Meta): Material =
// MeshBasicMaterial().apply {
// color = meta["color"]?.color() ?: DEFAULT_COLOR
// opacity = meta["opacity"]?.double ?: 1.0
// transparent = meta["transparent"].boolean ?: (opacity < 1.0)
// //node["specularColor"]?.let { specular = it.color() }
// //side = 2
// }
fun getLineMaterial(meta: Meta?): LineBasicMaterial = lineMaterialCache.getOrPut(meta) {
LineBasicMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.color() ?: DEFAULT_LINE_COLOR
opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0 opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0
transparent = opacity < 1.0 transparent = opacity < 1.0
linewidth = meta["thickness"].double ?: 1.0 linewidth = meta["thickness"].double ?: 1.0
} }
} }
fun getMaterial(visualObject3D: VisualObject3D): Material {
val meta = visualObject3D.getProperty(Material3D.MATERIAL_KEY).node ?: return ThreeMaterials.DEFAULT
return if (meta[Material3D.SPECULAR_COLOR] != null) {
MeshPhongMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
specular = meta[Material3D.SPECULAR_COLOR]!!.getColor()
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
needsUpdate = true
}
} else {
MeshBasicMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
needsUpdate = true
}
}
}
} }
/** /**
* Infer color based on meta item * Infer color based on meta item
*/ */
fun MetaItem<*>.color(): Color { fun MetaItem<*>.getColor(): Color {
return when (this) { return when (this) {
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) { is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
val int = value.number.toInt() val int = value.number.toInt()
@ -67,47 +82,3 @@ fun MetaItem<*>.color(): Color {
} }
} }
///**
// * Infer Three material based on meta item
// */
//fun Meta?.jsMaterial(): Material {
// return if (this == null) {
// ThreeMaterials.DEFAULT
// } else {
// ThreeMaterials.buildMaterial(this)
// }
//}
//
//fun Meta?.jsLineMaterial(): Material {
// return if (this == null) {
// ThreeMaterials.DEFAULT_LINE
// } else {
// ThreeMaterials.buildLineMaterial(this)
// }
//}
//fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
//fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial()
fun Mesh.updateMaterial(obj: VisualObject) {
val meta = obj.getProperty(Material3D.MATERIAL_KEY).node ?: EmptyMeta
material = if(meta[Material3D.SPECULAR_COLOR]!= null){
MeshPhongMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.color() ?: ThreeMaterials.DEFAULT_COLOR
specular = meta[Material3D.SPECULAR_COLOR]!!.color()
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
needsUpdate = true
}
}else {
MeshBasicMaterial().apply {
color = meta[Material3D.COLOR_KEY]?.color() ?: ThreeMaterials.DEFAULT_COLOR
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
transparent = opacity < 1.0
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
needsUpdate = true
}
}
}

View File

@ -24,7 +24,7 @@ class ThreePlugin : AbstractPlugin() {
objectFactories[Sphere::class] = ThreeSphereFactory objectFactories[Sphere::class] = ThreeSphereFactory
objectFactories[ConeSegment::class] = ThreeCylinderFactory objectFactories[ConeSegment::class] = ThreeCylinderFactory
objectFactories[PolyLine::class] = ThreeLineFactory objectFactories[PolyLine::class] = ThreeLineFactory
objectFactories[Label3D::class] = ThreeLabelFactory objectFactories[Label3D::class] = ThreeCanvasLabelFactory
} }
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -25,7 +25,7 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) { if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) {
val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'") val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
val propertyName = name.cutFirst() val propertyName = name.cutFirst()
val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found") val proxyChild = obj[childName] as? VisualObject3D ?: error("Proxy child with name '$childName' not found or not a 3D object")
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(proxyChild, propertyName) child.updateProperty(proxyChild, propertyName)
} else { } else {

View File

@ -25,7 +25,7 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
fileSaver.saveAs(blob, fileName) fileSaver.saveAs(blob, fileName)
} }
fun Element.threeSettings(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) { fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
clear() clear()
append { append {
card("Settings") { card("Settings") {

View File

@ -50,7 +50,7 @@ external interface Intersect {
var `object`: Object3D var `object`: Object3D
} }
external class Raycaster { external class Raycaster() {
constructor(origin: Vector3, direction: Vector3, near: Number, far: Number) constructor(origin: Vector3, direction: Vector3, near: Number, far: Number)
@ -62,8 +62,8 @@ external class Raycaster {
fun setFromCamera(coord: Vector2, camera: Camera) fun setFromCamera(coord: Vector2, camera: Camera)
fun intersectObject(object3D: Object3D, recursive: Boolean): List<Intersect> fun intersectObject(object3D: Object3D, recursive: Boolean): Array<Intersect>
fun intersectObjects(objects: List<Object3D>, recursive: Boolean): List<Intersect> fun intersectObjects(objects: List<Object3D>, recursive: Boolean): Array<Intersect>
} }

View File

@ -0,0 +1,92 @@
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS",
"EXTERNAL_DELEGATION"
)
@file:JsModule("three")
@file:JsNonModule
package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.extras.core.Shape
import info.laht.threekt.math.Vector2
external interface ExtrudeGeometryOptions {
var curveSegments: Number?
get() = definedExternally
set(value) = definedExternally
var steps: Number?
get() = definedExternally
set(value) = definedExternally
var depth: Number?
get() = definedExternally
set(value) = definedExternally
var bevelEnabled: Boolean?
get() = definedExternally
set(value) = definedExternally
var bevelThickness: Number?
get() = definedExternally
set(value) = definedExternally
var bevelSize: Number?
get() = definedExternally
set(value) = definedExternally
var bevelOffset: Number?
get() = definedExternally
set(value) = definedExternally
var bevelSegments: Number?
get() = definedExternally
set(value) = definedExternally
var extrudePath: Any?
get() = definedExternally
set(value) = definedExternally
var UVGenerator: UVGenerator?
get() = definedExternally
set(value) = definedExternally
}
external interface UVGenerator {
fun generateTopUV(
geometry: ExtrudeBufferGeometry,
vertices: Array<Number>,
indexA: Number,
indexB: Number,
indexC: Number
): Array<Vector2>
fun generateSideWallUV(
geometry: ExtrudeBufferGeometry,
vertices: Array<Number>,
indexA: Number,
indexB: Number,
indexC: Number,
indexD: Number
): Array<Vector2>
}
external open class ExtrudeBufferGeometry : BufferGeometry {
constructor(shapes: Shape, options: ExtrudeGeometryOptions?)
constructor(shapes: Array<Shape>, options: ExtrudeGeometryOptions?)
open fun addShapeList(shapes: Array<Shape>, options: Any? = definedExternally)
open fun addShape(shape: Shape, options: Any? = definedExternally)
companion object {
var WorldUVGenerator: UVGenerator
}
}
external open class ExtrudeGeometry : Geometry {
constructor(shapes: Shape, options: ExtrudeGeometryOptions?)
constructor(shapes: Array<Shape>, options: ExtrudeGeometryOptions?)
open fun addShapeList(shapes: Array<Shape>, options: Any? = definedExternally)
open fun addShape(shape: Shape, options: Any? = definedExternally)
companion object {
var WorldUVGenerator: UVGenerator
}
}

View File

@ -0,0 +1,43 @@
@file:JsModule("three")
@file:JsNonModule
package info.laht.threekt.geometries
external interface TextGeometryParameters {
var font: Any?
get() = definedExternally
set(value) = definedExternally
var size: Number?
get() = definedExternally
set(value) = definedExternally
var height: Number?
get() = definedExternally
set(value) = definedExternally
var curveSegments: Number?
get() = definedExternally
set(value) = definedExternally
var bevelEnabled: Boolean?
get() = definedExternally
set(value) = definedExternally
var bevelThickness: Number?
get() = definedExternally
set(value) = definedExternally
var bevelSize: Number?
get() = definedExternally
set(value) = definedExternally
var bevelOffset: Number?
get() = definedExternally
set(value) = definedExternally
var bevelSegments: Number?
get() = definedExternally
set(value) = definedExternally
}
external class TextBufferGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) : ExtrudeBufferGeometry {
val parameters: TextGeometryParameters
}
external class TextGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) : ExtrudeGeometry {
val parameters: TextGeometryParameters
}

View File

@ -5,9 +5,12 @@ import hep.dataforge.js.Application
import hep.dataforge.js.startApplication import hep.dataforge.js.startApplication
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.withBottom import hep.dataforge.meta.withBottom
import hep.dataforge.names.NameToken import hep.dataforge.names.Name
import hep.dataforge.vis.js.editor.objectTree import hep.dataforge.names.isEmpty
import hep.dataforge.vis.js.editor.propertyEditor import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.js.editor.displayObjectTree
import hep.dataforge.vis.js.editor.displayPropertyEditor
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY
@ -19,8 +22,8 @@ import hep.dataforge.vis.spatial.gdml.GDMLTransformer
import hep.dataforge.vis.spatial.gdml.LUnit import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.toVisual import hep.dataforge.vis.spatial.gdml.toVisual
import hep.dataforge.vis.spatial.three.ThreePlugin import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.displayCanvasControls
import hep.dataforge.vis.spatial.three.output import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.spatial.three.threeSettings
import hep.dataforge.vis.spatial.visible import hep.dataforge.vis.spatial.visible
import kotlinx.html.dom.append import kotlinx.html.dom.append
import kotlinx.html.js.p import kotlinx.html.js.p
@ -154,13 +157,18 @@ private class GDMLDemoApp : Application {
message("Rendering") message("Rendering")
//output.camera.layers.enable(1) //output.camera.layers.enable(1)
val output = three.output(canvasElement as HTMLElement) val canvas = three.output(canvasElement as HTMLElement)
output.camera.layers.set(0) canvas.camera.layers.set(0)
configElement.threeSettings(output) configElement.displayCanvasControls(canvas)
//tree.visualObjectTree(visual, editor::propertyEditor) //tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.objectTree(NameToken("World"), visual) { objName, obj -> fun selectElement(name: Name) {
editorElement.propertyEditor(objName, obj) { item -> val child: VisualObject = when {
name.isEmpty() -> visual
visual is VisualGroup -> visual[name] ?: return
else -> return
}
editorElement.displayPropertyEditor(name, child) { item ->
//val descriptorMeta = Material3D.descriptor //val descriptorMeta = Material3D.descriptor
val properties = item.allProperties() val properties = item.allProperties()
@ -176,8 +184,18 @@ private class GDMLDemoApp : Application {
} }
} }
// canvas.clickListener = ::selectElement
output.render(visual) //tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.displayObjectTree(visual) { name ->
selectElement(name)
canvas.highlight(name)
}
canvas.render(visual)
canvas.render(visual)
message(null) message(null)
spinner(false) spinner(false)
} }

View File

@ -5,10 +5,13 @@ import hep.dataforge.js.Application
import hep.dataforge.js.startApplication import hep.dataforge.js.startApplication
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.withBottom import hep.dataforge.meta.withBottom
import hep.dataforge.names.NameToken import hep.dataforge.names.Name
import hep.dataforge.names.isEmpty
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.js.editor.card import hep.dataforge.vis.js.editor.card
import hep.dataforge.vis.js.editor.objectTree import hep.dataforge.vis.js.editor.displayObjectTree
import hep.dataforge.vis.js.editor.propertyEditor import hep.dataforge.vis.js.editor.displayPropertyEditor
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY
@ -16,8 +19,8 @@ import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import hep.dataforge.vis.spatial.three.ThreePlugin import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.displayCanvasControls
import hep.dataforge.vis.spatial.three.output import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.spatial.three.threeSettings
import hep.dataforge.vis.spatial.visible import hep.dataforge.vis.spatial.visible
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.features.json.JsonFeature import io.ktor.client.features.json.JsonFeature
@ -48,8 +51,8 @@ private class GDMLDemoApp : Application {
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO") //val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page") val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
val settingsElement = val settingsElement = document.getElementById("settings")
document.getElementById("settings") ?: error("Element with id 'settings' not found on page") ?: error("Element with id 'settings' not found on page")
val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page") val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page")
val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page") val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
@ -57,12 +60,12 @@ private class GDMLDemoApp : Application {
val visual: VisualObject3D = model.root val visual: VisualObject3D = model.root
//output.camera.layers.enable(1) //output.camera.layers.enable(1)
val output = three.output(canvasElement as HTMLElement) val canvas = three.output(canvasElement as HTMLElement)
output.camera.layers.set(0) canvas.camera.layers.set(0)
output.camera.position.z = -2000.0 canvas.camera.position.z = -2000.0
output.camera.position.y = 500.0 canvas.camera.position.y = 500.0
settingsElement.threeSettings(output) { settingsElement.displayCanvasControls(canvas) {
card("Events") { card("Events") {
button { button {
+"Next" +"Next"
@ -81,9 +84,15 @@ private class GDMLDemoApp : Application {
} }
} }
} }
//tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.objectTree(NameToken("World"), visual) { name, obj ->
editorElement.propertyEditor(name, obj) { item -> fun selectElement(name: Name) {
val child: VisualObject = when {
name.isEmpty() -> visual
visual is VisualGroup -> visual[name] ?: return
else -> return
}
editorElement.displayPropertyEditor(name, child) { item ->
//val descriptorMeta = Material3D.descriptor //val descriptorMeta = Material3D.descriptor
val properties = item.allProperties() val properties = item.allProperties()
@ -98,7 +107,15 @@ private class GDMLDemoApp : Application {
properties.withBottom(bottom) properties.withBottom(bottom)
} }
} }
output.render(visual)
// canvas.clickListener = ::selectElement
//tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.displayObjectTree(visual) { name ->
selectElement(name)
canvas.highlight(name)
}
canvas.render(visual)
} }
} }

View File

@ -12,10 +12,10 @@ import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY
import hep.dataforge.vis.spatial.demo.VariableBoxThreeFactory.Z_SIZE_KEY import hep.dataforge.vis.spatial.demo.VariableBoxThreeFactory.Z_SIZE_KEY
import hep.dataforge.vis.spatial.three.* import hep.dataforge.vis.spatial.three.*
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxBufferGeometry import info.laht.threekt.geometries.BoxBufferGeometry
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.math.max import kotlin.math.max
@ -69,16 +69,13 @@ private object VariableBoxThreeFactory : ThreeFactory<VisualObject3D> {
//JS sometimes tries to pass Geometry as BufferGeometry //JS sometimes tries to pass Geometry as BufferGeometry
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected") @Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
val mesh = Mesh(geometry, MeshBasicMaterial()).apply { val mesh = Mesh(geometry, getMaterial(obj)).apply {
applyEdges(obj) applyEdges(obj)
applyWireFrame(obj) applyWireFrame(obj)
//set position for mesh //set position for mesh
updatePosition(obj) updatePosition(obj)
//set color for mesh
updateMaterial(obj)
layers.enable(obj.layer) layers.enable(obj.layer)
children.forEach { children.forEach {
it.layers.enable(obj.layer) it.layers.enable(obj.layer)