diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml new file mode 100644 index 00000000..adc74adf --- /dev/null +++ b/.github/workflows/gradle.yml @@ -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 diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt index 42536f23..a5e47b6f 100644 --- a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt @@ -107,3 +107,11 @@ tailrec fun VisualObject.findStyle(name: String): Meta? = 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 +// } +//} + diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsTree.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsTree.kt index 51edd87a..3cf4a985 100644 --- a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsTree.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/jsTree.kt @@ -1,7 +1,6 @@ package hep.dataforge.vis.js.editor import hep.dataforge.names.Name -import hep.dataforge.names.NameToken import hep.dataforge.names.plus import hep.dataforge.vis.common.VisualGroup import hep.dataforge.vis.common.VisualObject @@ -14,26 +13,25 @@ import org.w3c.dom.HTMLElement import org.w3c.dom.HTMLSpanElement import kotlin.dom.clear -fun Element.objectTree( - token: NameToken, +fun Element.displayObjectTree( obj: VisualObject, - clickCallback: (Name, VisualObject) -> Unit = {_,_->} + clickCallback: (Name) -> Unit = {} ) { clear() append { card("Object tree") { - subTree(Name.EMPTY, token, obj, clickCallback) + subTree(Name.EMPTY, obj, clickCallback) } } } private fun TagConsumer.subTree( - parentName: Name, - token: NameToken, + fullName: Name, 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 if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) { @@ -41,8 +39,8 @@ private fun TagConsumer.subTree( div("d-inline-block text-truncate") { toggle = span("objTree-caret") label("objTree-label") { - +token.toString() - onClickFunction = { clickCallback(fullName, obj) } + +token + onClickFunction = { clickCallback(fullName) } } } val subtree = ul("objTree-subtree") @@ -57,7 +55,7 @@ private fun TagConsumer.subTree( .forEach { (childToken, child) -> append { li().apply { - subTree(fullName, childToken, child, clickCallback) + subTree(fullName + childToken, child, clickCallback) } } } @@ -68,11 +66,11 @@ private fun TagConsumer.subTree( } } } else { - val div = div("d-inline-block text-truncate") { + div("d-inline-block text-truncate") { span("objTree-leaf") label("objTree-label") { - +token.toString() - onClickFunction = { clickCallback(fullName, obj) } + +token + onClickFunction = { clickCallback(fullName) } } } } diff --git a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/propertyEditor.kt b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/propertyEditor.kt index 59a026c3..590c208f 100644 --- a/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/propertyEditor.kt +++ b/dataforge-vis-common/src/jsMain/kotlin/hep/dataforge/vis/js/editor/propertyEditor.kt @@ -12,22 +12,29 @@ import hep.dataforge.vis.common.findStyle import kotlinx.html.dom.append import kotlinx.html.js.* import org.w3c.dom.Element +import kotlin.collections.forEach +import kotlin.collections.isNotEmpty +import kotlin.collections.set import kotlin.dom.clear //FIXME something rotten in JS-Meta converter fun Meta.toDynamic() = JSON.parse(toJson().toString()) //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() append { card("Properties") { - if(!name.isEmpty()) { + if (!name.isEmpty()) { nav { attributes["aria-label"] = "breadcrumb" ol("breadcrumb") { - name.tokens.forEach {token-> + name.tokens.forEach { token -> li("breadcrumb-item") { +token.toString() } diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt index 4a07c260..78b7c88f 100644 --- a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/VisualObject3D.kt @@ -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.IGNORE_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 kotlinx.serialization.UseSerializers @@ -118,9 +117,9 @@ var VisualObject.ignore: Boolean? get() = getProperty(IGNORE_KEY,false).boolean set(value) = setProperty(IGNORE_KEY, value) -var VisualObject.selected: Boolean? - get() = getProperty(SELECTED_KEY).boolean - set(value) = setProperty(SELECTED_KEY, value) +//var VisualObject.selected: Boolean? +// get() = getProperty(SELECTED_KEY).boolean +// set(value) = setProperty(SELECTED_KEY, value) private fun VisualObject3D.position(): Point3D = position ?: Point3D(0.0, 0.0, 0.0).also { position = it } diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/MeshThreeFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/MeshThreeFactory.kt index 7c3ead15..76adef48 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/MeshThreeFactory.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/MeshThreeFactory.kt @@ -1,8 +1,6 @@ package hep.dataforge.vis.spatial.three -import hep.dataforge.meta.Meta import hep.dataforge.meta.boolean -import hep.dataforge.meta.get import hep.dataforge.meta.node import hep.dataforge.names.asName 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.VisualObject3D import hep.dataforge.vis.spatial.layer +import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial import info.laht.threekt.core.BufferGeometry import info.laht.threekt.geometries.EdgesGeometry import info.laht.threekt.geometries.WireframeGeometry -import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.Mesh import kotlin.reflect.KClass @@ -37,7 +35,7 @@ abstract class MeshThreeFactory( //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 applyEdges(obj) applyWireFrame(obj) @@ -45,9 +43,6 @@ abstract class MeshThreeFactory( //set position for mesh updatePosition(obj) - //set color for mesh - updateMaterial(obj) - layers.enable(obj.layer) children.forEach { it.layers.enable(obj.layer) diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt index 47695be4..08717e92 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvas.kt @@ -4,33 +4,55 @@ import hep.dataforge.context.Context import hep.dataforge.meta.Meta import hep.dataforge.meta.get 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.vis.common.Colors import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.specifications.CameraSpec import hep.dataforge.vis.spatial.specifications.CanvasSpec 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.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.TrackballControls +import info.laht.threekt.geometries.EdgesGeometry 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 org.w3c.dom.HTMLElement import org.w3c.dom.Node +import org.w3c.dom.events.MouseEvent import kotlin.browser.window import kotlin.dom.clear import kotlin.math.cos import kotlin.math.max import kotlin.math.sin -class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer { +/** + * + */ +class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val spec: CanvasSpec) : Renderer { override val context: Context get() = three.context var content: VisualObject3D? = null 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 { visible = spec.axes.visible } @@ -41,27 +63,26 @@ class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer TrackballControls(camera, element) - else -> OrbitControls(camera, element) - } - } - - fun attach(element: HTMLElement) { + 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 { @@ -72,6 +93,13 @@ class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer + 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( + spec.fov, + 1.0, + spec.nearClip, + spec.farClip + ).apply { + translateX(spec.distance * sin(spec.zenith) * sin(spec.azimuth)) + translateY(spec.distance * cos(spec.zenith)) + translateZ(spec.distance * sin(spec.zenith) * cos(spec.azimuth)) + } + + private fun addControls(element: Node, controlsSpec: ControlsSpec) { + when (controlsSpec["type"].string) { + "trackball" -> TrackballControls(camera, element) + else -> OrbitControls(camera, element) + } + } + 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) + object3D.name = "@root" 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) } + } + } + + /** + * Toggle highlight for element with given name + */ + 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? = null, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas = - ThreeCanvas(this, spec).apply { - if (element != null) { - attach(element) - } - } \ No newline at end of file +fun ThreePlugin.output(element: HTMLElement, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas = + ThreeCanvas(element, this, spec) \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvasLabelFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvasLabelFactory.kt new file mode 100644 index 00000000..23a1bf3a --- /dev/null +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeCanvasLabelFactory.kt @@ -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 { + override val type: KClass 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 + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt index b3e76a20..65dc71ef 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeFactory.kt @@ -7,6 +7,7 @@ import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY 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.Object3D import info.laht.threekt.objects.Mesh @@ -53,14 +54,13 @@ fun Object3D.updatePosition(obj: VisualObject3D) { /** * 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)) { - updateMaterial(source) + this.material = getMaterial(source) } else if ( - source is VisualObject3D && - (propertyName.startsWith(VisualObject3D.position) - || propertyName.startsWith(VisualObject3D.rotation) - || propertyName.startsWith(VisualObject3D.scale)) + propertyName.startsWith(VisualObject3D.position) + || propertyName.startsWith(VisualObject3D.rotation) + || propertyName.startsWith(VisualObject3D.scale) ) { //update position of mesh using this object updatePosition(source) diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeGeometryBuilder.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeGeometryBuilder.kt index c45d6caa..c263e031 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeGeometryBuilder.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeGeometryBuilder.kt @@ -34,7 +34,7 @@ class ThreeGeometryBuilder : GeometryBuilder { 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)) meta["materialIndex"].int?.let { face.materialIndex = it } - meta["color"]?.color()?.let { face.color = it } + meta["color"]?.getColor()?.let { face.color = it } faces.add(face) } diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeLabelFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeLabelFactory.kt index 08699726..c3e1024a 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeLabelFactory.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeLabelFactory.kt @@ -1,56 +1,33 @@ 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.color -import info.laht.threekt.DoubleSide +import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial import info.laht.threekt.core.Object3D -import info.laht.threekt.geometries.PlaneBufferGeometry -import info.laht.threekt.materials.MeshBasicMaterial +import info.laht.threekt.geometries.TextBufferGeometry 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 ThreeLabelFactory : ThreeFactory { override val type: KClass 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 textGeo = TextBufferGeometry( obj.text, jsObject { + font = obj.fontFamily + size = 20 + height = 1 + curveSegments = 1 + } ) + return Mesh(textGeo, getMaterial(obj)).apply { + updatePosition(obj) + obj.onPropertyChange(this@ThreeLabelFactory){name: Name, before: MetaItem<*>?, after: MetaItem<*>? -> + //TODO + } } - - val mesh = Mesh( - PlaneBufferGeometry(canvas.width, canvas.height), - material - ) - - mesh.updatePosition(obj) - - return mesh } } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt index aa09b2d3..ff3ddcf9 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeMaterials.kt @@ -3,18 +3,18 @@ package hep.dataforge.vis.spatial.three import hep.dataforge.meta.* import hep.dataforge.values.ValueType import hep.dataforge.vis.common.Colors -import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.spatial.Material3D +import hep.dataforge.vis.spatial.VisualObject3D import info.laht.threekt.materials.LineBasicMaterial +import info.laht.threekt.materials.Material import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.materials.MeshPhongMaterial import info.laht.threekt.math.Color -import info.laht.threekt.objects.Mesh object ThreeMaterials { val DEFAULT_COLOR = Color(Colors.darkgreen) - val DEFAULT = MeshPhongMaterial().apply { + val DEFAULT = MeshBasicMaterial().apply { color.set(DEFAULT_COLOR) } val DEFAULT_LINE_COLOR = Color(Colors.black) @@ -22,34 +22,49 @@ object ThreeMaterials { color.set(DEFAULT_LINE_COLOR) } + val HIGHLIGHT_MATERIAL = LineBasicMaterial().apply { + color.set(Colors.ivory) + linewidth = 8.0 + } - // private val materialCache = HashMap() - private val lineMaterialCache = HashMap() - - -// 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 + fun getLineMaterial(meta: Meta?): LineBasicMaterial { + if (meta == null) return DEFAULT_LINE + return LineBasicMaterial().apply { + color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0 transparent = opacity < 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 */ -fun MetaItem<*>.color(): Color { +fun MetaItem<*>.getColor(): Color { return when (this) { is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) { 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 - } - } -} diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt index 037a34f2..11117b27 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreePlugin.kt @@ -24,7 +24,7 @@ class ThreePlugin : AbstractPlugin() { objectFactories[Sphere::class] = ThreeSphereFactory objectFactories[ConeSegment::class] = ThreeCylinderFactory objectFactories[PolyLine::class] = ThreeLineFactory - objectFactories[Label3D::class] = ThreeLabelFactory + objectFactories[Label3D::class] = ThreeCanvasLabelFactory } @Suppress("UNCHECKED_CAST") diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt index 05bdd9e1..84fcb86e 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/ThreeProxyFactory.kt @@ -25,7 +25,7 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory { if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) { val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'") 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") child.updateProperty(proxyChild, propertyName) } else { diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/outputConfig.kt b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/outputConfig.kt index 1c582ee2..1f3c0a6d 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/outputConfig.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/three/outputConfig.kt @@ -25,7 +25,7 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl fileSaver.saveAs(blob, fileName) } -fun Element.threeSettings(canvas: ThreeCanvas, block: TagConsumer.() -> Unit = {}) { +fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer.() -> Unit = {}) { clear() append { card("Settings") { diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/core/Raycaster.kt b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/core/Raycaster.kt index bc9e0820..2e2944af 100644 --- a/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/core/Raycaster.kt +++ b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/core/Raycaster.kt @@ -50,7 +50,7 @@ external interface Intersect { var `object`: Object3D } -external class Raycaster { +external class Raycaster() { constructor(origin: Vector3, direction: Vector3, near: Number, far: Number) @@ -62,8 +62,8 @@ external class Raycaster { fun setFromCamera(coord: Vector2, camera: Camera) - fun intersectObject(object3D: Object3D, recursive: Boolean): List + fun intersectObject(object3D: Object3D, recursive: Boolean): Array - fun intersectObjects(objects: List, recursive: Boolean): List + fun intersectObjects(objects: List, recursive: Boolean): Array } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/geometries/ExtrudedGeometry.kt b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/geometries/ExtrudedGeometry.kt new file mode 100644 index 00000000..ab17c116 --- /dev/null +++ b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/geometries/ExtrudedGeometry.kt @@ -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, + indexA: Number, + indexB: Number, + indexC: Number + ): Array + + fun generateSideWallUV( + geometry: ExtrudeBufferGeometry, + vertices: Array, + indexA: Number, + indexB: Number, + indexC: Number, + indexD: Number + ): Array +} + +external open class ExtrudeBufferGeometry : BufferGeometry { + constructor(shapes: Shape, options: ExtrudeGeometryOptions?) + constructor(shapes: Array, options: ExtrudeGeometryOptions?) + + open fun addShapeList(shapes: Array, 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, options: ExtrudeGeometryOptions?) + + open fun addShapeList(shapes: Array, options: Any? = definedExternally) + open fun addShape(shape: Shape, options: Any? = definedExternally) + + companion object { + var WorldUVGenerator: UVGenerator + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/geometries/TextBufferGeometry.kt b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/geometries/TextBufferGeometry.kt new file mode 100644 index 00000000..ca9f93b3 --- /dev/null +++ b/dataforge-vis-spatial/src/jsMain/kotlin/info/laht/threekt/geometries/TextBufferGeometry.kt @@ -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 +} \ No newline at end of file diff --git a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt index bc3383f6..f4e6d4c5 100644 --- a/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt +++ b/demo/gdml/src/jsMain/kotlin/hep/dataforge/vis/spatial/gdml/demo/GDMLDemoApp.kt @@ -5,9 +5,12 @@ import hep.dataforge.js.Application import hep.dataforge.js.startApplication import hep.dataforge.meta.buildMeta import hep.dataforge.meta.withBottom -import hep.dataforge.names.NameToken -import hep.dataforge.vis.js.editor.objectTree -import hep.dataforge.vis.js.editor.propertyEditor +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.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_OPACITY_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.toVisual 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.threeSettings import hep.dataforge.vis.spatial.visible import kotlinx.html.dom.append import kotlinx.html.js.p @@ -154,13 +157,18 @@ private class GDMLDemoApp : Application { message("Rendering") //output.camera.layers.enable(1) - val output = three.output(canvasElement as HTMLElement) + val canvas = three.output(canvasElement as HTMLElement) - output.camera.layers.set(0) - configElement.threeSettings(output) + canvas.camera.layers.set(0) + configElement.displayCanvasControls(canvas) //tree.visualObjectTree(visual, editor::propertyEditor) - treeElement.objectTree(NameToken("World"), visual) { objName, obj -> - editorElement.propertyEditor(objName, 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 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) spinner(false) } diff --git a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt index 79a4454e..a9f7dd3d 100644 --- a/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt +++ b/demo/muon-monitor/src/jsMain/kotlin/ru/mipt/npm/muon/monitor/MMDemoApp.kt @@ -5,10 +5,13 @@ import hep.dataforge.js.Application import hep.dataforge.js.startApplication import hep.dataforge.meta.buildMeta 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.objectTree -import hep.dataforge.vis.js.editor.propertyEditor +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_OPACITY_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.Companion.VISIBLE_KEY 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.threeSettings import hep.dataforge.vis.spatial.visible import io.ktor.client.HttpClient 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 canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page") - val settingsElement = - document.getElementById("settings") ?: error("Element with id 'settings' not found on page") + val settingsElement = document.getElementById("settings") + ?: error("Element with id 'settings' 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") @@ -57,12 +60,12 @@ private class GDMLDemoApp : Application { val visual: VisualObject3D = model.root //output.camera.layers.enable(1) - val output = three.output(canvasElement as HTMLElement) + val canvas = three.output(canvasElement as HTMLElement) - output.camera.layers.set(0) - output.camera.position.z = -2000.0 - output.camera.position.y = 500.0 - settingsElement.threeSettings(output) { + canvas.camera.layers.set(0) + canvas.camera.position.z = -2000.0 + canvas.camera.position.y = 500.0 + settingsElement.displayCanvasControls(canvas) { card("Events") { button { +"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 properties = item.allProperties() @@ -98,7 +107,15 @@ private class GDMLDemoApp : Application { 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) } } diff --git a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vis/spatial/demo/VariableBox.kt b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vis/spatial/demo/VariableBox.kt index 2827dcbd..20540777 100644 --- a/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vis/spatial/demo/VariableBox.kt +++ b/demo/spatial-showcase/src/jsMain/kotlin/hep/dataforge/vis/spatial/demo/VariableBox.kt @@ -12,10 +12,10 @@ import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY import hep.dataforge.vis.spatial.demo.VariableBoxThreeFactory.Z_SIZE_KEY 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.Object3D import info.laht.threekt.geometries.BoxBufferGeometry -import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.objects.Mesh import kotlinx.serialization.UseSerializers import kotlin.math.max @@ -69,16 +69,13 @@ private object VariableBoxThreeFactory : ThreeFactory { //JS sometimes tries to pass Geometry as BufferGeometry @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) applyWireFrame(obj) //set position for mesh updatePosition(obj) - //set color for mesh - updateMaterial(obj) - layers.enable(obj.layer) children.forEach { it.layers.enable(obj.layer)