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))
//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
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<HTMLElement>.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<HTMLElement>.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<HTMLElement>.subTree(
.forEach { (childToken, child) ->
append {
li().apply {
subTree(fullName, childToken, child, clickCallback)
subTree(fullName + childToken, child, clickCallback)
}
}
}
@ -68,11 +66,11 @@ private fun TagConsumer<HTMLElement>.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) }
}
}
}

View File

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

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.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 }

View File

@ -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<in T : VisualObject3D>(
//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<in T : VisualObject3D>(
//set position for mesh
updatePosition(obj)
//set color for mesh
updateMaterial(obj)
layers.enable(obj.layer)
children.forEach {
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.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<VisualObject3D> {
/**
*
*/
class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> {
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<Visua
val camera = buildCamera(spec.camera)
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)
}
}
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<Visua
addControls(renderer.domElement, spec.controls)
fun animate() {
val mesh = pick()
if (mesh != null && highlighted != mesh) {
highlighted?.toggleHighlight(false)
mesh.toggleHighlight(true)
}
window.requestAnimationFrame {
animate()
}
@ -90,16 +118,98 @@ class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<Visua
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(
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)
}
}
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.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)

View File

@ -34,7 +34,7 @@ class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
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)
}

View File

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

View File

@ -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<Meta, Material>()
private val lineMaterialCache = HashMap<Meta?, LineBasicMaterial>()
// 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
}
}
}

View File

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

View File

@ -25,7 +25,7 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
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 {

View File

@ -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<HTMLElement>.() -> Unit = {}) {
fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
clear()
append {
card("Settings") {

View File

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

View File

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

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.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<VisualObject3D> {
//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)