Abstracted away renderers. Updated dataforge version to 0.1.3-dev-9

This commit is contained in:
Alexander Nozik 2019-07-05 19:08:20 +03:00
parent 697e7908a7
commit b6cfb8b841
30 changed files with 474 additions and 293 deletions

View File

@ -1,4 +1,4 @@
val dataforgeVersion by extra("0.1.3-dev-7") val dataforgeVersion by extra("0.1.3-dev-9")
plugins{ plugins{
kotlin("jvm") version "1.3.40" apply false kotlin("jvm") version "1.3.40" apply false

View File

@ -17,19 +17,15 @@ class DisplayGroup(
private val namedChildren = HashMap<Name, DisplayObject>() private val namedChildren = HashMap<Name, DisplayObject>()
private val unnamedChildren = ArrayList<DisplayObject>() private val unnamedChildren = ArrayList<DisplayObject>()
override val defaultTarget: String get() = DisplayObject.TARGET override val defaultTarget: String get() = DisplayObject.TYPE
override val properties: Styled = Styled(meta) override val properties: Styled = Styled(meta)
override fun iterator(): Iterator<DisplayObject> = (namedChildren.values + unnamedChildren).iterator() override fun iterator(): Iterator<DisplayObject> = (namedChildren.values + unnamedChildren).iterator()
override fun listNames(target: String): Sequence<Name> = override fun provideTop(target: String): Map<Name, Any> {
namedChildren.keys.asSequence() return when(target){
DisplayObject.TYPE -> namedChildren
override fun provideTop(target: String, name: Name): Any? { else -> emptyMap()
return if (target == defaultTarget) {
namedChildren[name]
} else {
null
} }
} }
@ -55,14 +51,18 @@ class DisplayGroup(
/** /**
* *
*/ */
operator fun set(key: String, child: DisplayObject?) { operator fun set(key: String?, child: DisplayObject?) {
val name = key.toName() if(key == null){
if (child == null) {
namedChildren.remove(name)
} else { } else {
namedChildren[name] = child val name = key.toName()
if (child == null) {
namedChildren.remove(name)
} else {
namedChildren[name] = child
}
listeners.forEach { it.callback(name, child) }
} }
listeners.forEach { it.callback(name, child) }
} }
/** /**

View File

@ -3,13 +3,16 @@ package hep.dataforge.vis.common
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.provider.Type
import hep.dataforge.vis.common.DisplayObject.Companion.META_KEY import hep.dataforge.vis.common.DisplayObject.Companion.META_KEY
import hep.dataforge.vis.common.DisplayObject.Companion.TAGS_KEY import hep.dataforge.vis.common.DisplayObject.Companion.TAGS_KEY
import hep.dataforge.vis.common.DisplayObject.Companion.TYPE
/** /**
* A root type for display hierarchy * A root type for display hierarchy
*/ */
interface DisplayObject { @Type(TYPE)
interface DisplayObject : MetaRepr {
/** /**
* The parent object of this one. If null, this one is a root. * The parent object of this one. If null, this one is a root.
@ -18,8 +21,12 @@ interface DisplayObject {
val properties: Styled val properties: Styled
override fun toMeta(): Meta = buildMeta(properties) {
"type" to this::class
}
companion object { companion object {
const val TARGET = "display" const val TYPE = "display"
const val DEFAULT_TYPE = "" const val DEFAULT_TYPE = ""
//const val TYPE_KEY = "@type" //const val TYPE_KEY = "@type"

View File

@ -115,5 +115,5 @@ class ApplicationSurrogate : App() {
} }
fun Context.display(width: Double = 800.0, height: Double = 600.0, component: () -> UIComponent) { fun Context.display(width: Double = 800.0, height: Double = 600.0, component: () -> UIComponent) {
plugins.getOrLoad<FXPlugin>().display(component(), width, height) plugins.fetch(FXPlugin).display(component(), width, height)
} }

View File

@ -13,8 +13,7 @@ val kotlinVersion: String by rootProject.extra
dependencies { dependencies {
implementation(project(":dataforge-vis-spatial")) implementation(project(":dataforge-vis-spatial"))
//implementation("ch.viseon.threejs:wrapper:105.0.0") implementation("info.laht.threekt:threejs-wrapper:0.106-npm-2")
implementation("info.laht.threekt:threejs-wrapper:0.88-npm-2")
testCompile(kotlin("test-js")) testCompile(kotlin("test-js"))
} }
@ -22,7 +21,7 @@ configure<KotlinFrontendExtension> {
downloadNodeJsVersion = "latest" downloadNodeJsVersion = "latest"
configure<NpmExtension> { configure<NpmExtension> {
dependency("three-full") dependency("three","0.106.2")
dependency("@hi-level/three-csg") dependency("@hi-level/three-csg")
dependency("style-loader") dependency("style-loader")
dependency("element-resize-event") dependency("element-resize-event")

View File

@ -1,102 +0,0 @@
package hep.dataforge.vis.spatial
import hep.dataforge.context.Context
import hep.dataforge.context.content
import hep.dataforge.meta.*
import hep.dataforge.output.Output
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.spatial.demo.require
import hep.dataforge.vis.spatial.three.Group
import info.laht.threekt.WebGLRenderer
import info.laht.threekt.cameras.PerspectiveCamera
import info.laht.threekt.core.Object3D
import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.helpers.AxesHelper
import info.laht.threekt.lights.AmbientLight
import info.laht.threekt.math.ColorConstants
import info.laht.threekt.scenes.Scene
import org.w3c.dom.Element
import kotlin.browser.window
private val elementResizeEvent = require("element-resize-event")
class ThreeOutput(override val context: Context, val meta: Meta = EmptyMeta) : Output<DisplayObject> {
private val aspectRatio by meta.number(1.0).double
val scene: Scene = Scene().apply {
add(AmbientLight())
if (meta["axis"] != null) {
val axesHelper = AxesHelper(meta["axis.size"].int ?: 1)
add(axesHelper)
}
}
val camera = PerspectiveCamera(
meta["camera.fov"].int ?: 75,
aspectRatio,
meta["camera.nearClip"].double ?: World.CAMERA_NEAR_CLIP,
meta["camera.farClip"].double ?: World.CAMERA_FAR_CLIP
).apply {
position.setZ(World.CAMERA_INITIAL_DISTANCE)
rotation.set(World.CAMERA_INITIAL_X_ANGLE, World.CAMERA_INITIAL_Y_ANGLE, World.CAMERA_INITIAL_Z_ANGLE)
}
fun attach(element: Element, computeWidth: Element.() -> Int = { element.clientWidth }) {
val width by meta.number(computeWidth(element)).int
val height: Int = (width / aspectRatio).toInt()
val renderer = WebGLRenderer { antialias = true }.apply {
setClearColor(ColorConstants.skyblue, 1)
setSize(width, height)
}
val controls: OrbitControls = OrbitControls(camera, renderer.domElement)
fun animate() {
window.requestAnimationFrame {
animate()
}
renderer.render(scene, camera)
}
elementResizeEvent(element) {
camera.updateProjectionMatrix()
val newWidth = computeWidth(element)
renderer.setSize(newWidth, (newWidth / aspectRatio).toInt())
}
element.replaceWith(renderer.domElement)
animate()
}
private fun buildNode(obj: DisplayObject): Object3D? {
return if (obj is DisplayGroup) {
Group(obj.mapNotNull { buildNode(it) }).apply {
updatePosition(obj)
}
} else {
//find specialized factory for this type if it is present
val factory = context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == obj::class }
when {
factory != null -> factory(obj)
obj is Shape -> ThreeShapeFactory(obj)
else -> error("Renderer for ${obj::class} not found")
}
}
}
override fun render(obj: DisplayObject, meta: Meta) {
buildNode(obj)?.let {
scene.add(it)
} ?: error("Renderer for ${obj::class} not found")
}
companion object {
fun build(context: Context, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit) =
ThreeOutput(context, buildMeta(meta, override))
}
}

View File

@ -1,39 +0,0 @@
package hep.dataforge.vis.spatial
import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.set
class ThreePlugin : AbstractPlugin() {
override val tag: PluginTag get() = ThreePlugin.tag
val factories = HashMap<Name, ThreeFactory<*>>()
init {
factories["box"] = ThreeBoxFactory
factories["convex"] = ThreeConvexFactory
}
override fun listNames(target: String): Sequence<Name> {
return when (target) {
ThreeFactory.TYPE -> factories.keys.asSequence()
else -> return super.listNames(target)
}
}
override fun provideTop(target: String, name: Name): Any? {
return when (target) {
ThreeFactory.TYPE -> factories[name]
else -> return super.provideTop(target, name)
}
}
companion object : PluginFactory<ThreePlugin> {
override val tag = PluginTag("vis.three", "hep.dataforge")
override val type = ThreePlugin::class
override fun invoke(meta: Meta) = ThreePlugin()
}
}

View File

@ -2,6 +2,7 @@ package hep.dataforge.vis.spatial.demo
import hep.dataforge.context.ContextBuilder import hep.dataforge.context.ContextBuilder
import hep.dataforge.meta.number import hep.dataforge.meta.number
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.jsroot.JSRootPlugin import hep.dataforge.vis.spatial.jsroot.JSRootPlugin
import hep.dataforge.vis.spatial.jsroot.jsRootGeometry import hep.dataforge.vis.spatial.jsroot.jsRootGeometry
@ -28,7 +29,7 @@ class ThreeDemoApp : ApplicationBase() {
}.build() }.build()
context.plugins.load(ThreeDemoGrid()).run { context.plugins.load(ThreeDemoGrid()).run {
demo("group", "Group demo") { demo("dynamic", "Dynamic properties demo") {
val group = group { val group = group {
box { box {
z = 110.0 z = 110.0
@ -42,6 +43,7 @@ class ThreeDemoApp : ApplicationBase() {
xSize = 100.0 xSize = 100.0
ySize = 100.0 ySize = 100.0
zSize = 100.0 zSize = 100.0
//override color for this cube
color(1530) color(1530)
GlobalScope.launch { GlobalScope.launch {
@ -53,13 +55,13 @@ class ThreeDemoApp : ApplicationBase() {
} }
} }
var color by group.properties.number(1530).int var material by group.properties.number(1530).int
GlobalScope.launch { GlobalScope.launch {
val random = Random(111) val random = Random(111)
while (isActive) { while (isActive) {
delay(1000) delay(1000)
color = random.nextInt(0, Int.MAX_VALUE) material = random.nextInt(0, Int.MAX_VALUE)
} }
} }
} }
@ -68,7 +70,9 @@ class ThreeDemoApp : ApplicationBase() {
jsRootGeometry { jsRootGeometry {
y = 110.0 y = 110.0
shape = box(50, 50, 50) shape = box(50, 50, 50)
color(12285) color(Colors.lightcoral)
rotationX = PI / 4
rotationY = PI / 4
} }
} }
@ -81,6 +85,40 @@ class ThreeDemoApp : ApplicationBase() {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i)) layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
} }
} }
color(Colors.teal)
}
demo("CSG", "CSG operations") {
composite(CompositeType.UNION) {
box(100, 100, 100) {
z = 100
rotationX = PI / 4
rotationY = PI / 4
}
box(100, 100, 100)
color(Colors.green)
}
composite(CompositeType.INTERSECT) {
box(100, 100, 100) {
z = 100
rotationX = PI / 4
rotationY = PI / 4
}
box(100, 100, 100)
y = 300
color(Colors.red)
}
composite(CompositeType.SUBTRACT) {
box(100, 100, 100) {
z = 100
rotationX = PI / 4
rotationY = PI / 4
}
box(100, 100, 100)
y = -300
color(Colors.blue)
}
} }
} }

View File

@ -14,8 +14,10 @@ import hep.dataforge.output.Output
import hep.dataforge.output.OutputManager import hep.dataforge.output.OutputManager
import hep.dataforge.vis.common.DisplayGroup import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayObject import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.spatial.ThreeOutput
import hep.dataforge.vis.spatial.render import hep.dataforge.vis.spatial.render
import hep.dataforge.vis.spatial.three.ThreeOutput
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import kotlinx.html.dom.append import kotlinx.html.dom.append
import kotlinx.html.dom.create import kotlinx.html.dom.create
import kotlinx.html.h2 import kotlinx.html.h2
@ -33,6 +35,8 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
private val gridRoot = document.create.div("row") private val gridRoot = document.create.div("row")
private val outputs: MutableMap<Name, ThreeOutput> = HashMap() private val outputs: MutableMap<Name, ThreeOutput> = HashMap()
override fun dependsOn(): List<PluginFactory<*>> = listOf(ThreePlugin)
override fun attach(context: Context) { override fun attach(context: Context) {
super.attach(context) super.attach(context)
val elementId = meta["elementID"].string ?: "canvas" val elementId = meta["elementID"].string ?: "canvas"
@ -42,9 +46,11 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
} }
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> { override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> {
val three = context.plugins.get<ThreePlugin>()!!
return outputs.getOrPut(name) { return outputs.getOrPut(name) {
if (type != DisplayObject::class) error("Supports only DisplayObject") if (type != DisplayObject::class) error("Supports only DisplayObject")
val output = ThreeOutput.build(context, meta) { val output = three.output(meta) {
"axis" to { "axis" to {
"size" to 500 "size" to 500
} }
@ -52,8 +58,8 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
//TODO calculate cell width here using jquery //TODO calculate cell width here using jquery
gridRoot.append { gridRoot.append {
span("border") { span("border") {
div("col-4") { div("col-6") {
output.attach(div { id = "output-$name" }){ 300} output.attach(div { id = "output-$name" }) { 500 }
hr() hr()
h2 { +(meta["title"].string ?: name.toString()) } h2 { +(meta["title"].string ?: name.toString()) }
} }

View File

@ -5,8 +5,10 @@ import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag import hep.dataforge.context.PluginTag
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.vis.spatial.ThreePlugin import hep.dataforge.vis.spatial.three.ThreeFactory
import hep.dataforge.vis.spatial.three.ThreePlugin
import kotlin.reflect.KClass import kotlin.reflect.KClass
class GDMLPlugin : AbstractPlugin() { class GDMLPlugin : AbstractPlugin() {
@ -14,21 +16,13 @@ class GDMLPlugin : AbstractPlugin() {
override fun dependsOn() = listOf(ThreePlugin) override fun dependsOn() = listOf(ThreePlugin)
override fun attach(context: Context) { override fun provideTop(target: String): Map<Name, Any> {
super.attach(context) return when(target){
context.plugins.get<ThreePlugin>()?.factories?.apply { ThreeFactory.TYPE-> mapOf("gdml".toName() to ThreeGDMLFactory)
this["gdml".toName()] = ThreeGDMLFactory else -> emptyMap()
} }
} }
override fun detach() {
// context.plugins.get<ThreePlugin>()?.factories?.apply {
// remove("jsRoot.geometry".toName())
// remove("jsRoot.object".toName())
// }
super.detach()
}
companion object : PluginFactory<GDMLPlugin> { companion object : PluginFactory<GDMLPlugin> {
override val tag = PluginTag("vis.gdml", "hep.dataforge") override val tag = PluginTag("vis.gdml", "hep.dataforge")
override val type: KClass<GDMLPlugin> = GDMLPlugin::class override val type: KClass<GDMLPlugin> = GDMLPlugin::class

View File

@ -7,7 +7,7 @@ import hep.dataforge.meta.values
import hep.dataforge.vis.common.DisplayLeaf import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.int import hep.dataforge.vis.common.int
import hep.dataforge.vis.spatial.MeshThreeFactory import hep.dataforge.vis.spatial.three.MeshThreeFactory
import hep.dataforge.vis.spatial.jsroot.createCubeBuffer import hep.dataforge.vis.spatial.jsroot.createCubeBuffer
import hep.dataforge.vis.spatial.jsroot.createGeometry import hep.dataforge.vis.spatial.jsroot.createGeometry
import hep.dataforge.vis.spatial.jsroot.createTubeBuffer import hep.dataforge.vis.spatial.jsroot.createTubeBuffer

View File

@ -2,9 +2,11 @@ package hep.dataforge.vis.spatial.jsroot
import hep.dataforge.context.Global import hep.dataforge.context.Global
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.vis.spatial.ThreeOutput import hep.dataforge.vis.spatial.three.ThreeOutput
import hep.dataforge.vis.spatial.demo.ApplicationBase import hep.dataforge.vis.spatial.demo.ApplicationBase
import hep.dataforge.vis.spatial.render import hep.dataforge.vis.spatial.render
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import org.w3c.dom.HTMLDivElement import org.w3c.dom.HTMLDivElement
import org.w3c.dom.events.Event import org.w3c.dom.events.Event
import org.w3c.files.FileList import org.w3c.files.FileList
@ -59,7 +61,7 @@ class JSRootDemoApp : ApplicationBase() {
FileReader().apply { FileReader().apply {
onload = { onload = {
val string = result as String val string = result as String
val renderer = ThreeOutput(Global) val renderer = Global.plugins.fetch(ThreePlugin).output()
val canvas = document.getElementById("canvas")!! val canvas = document.getElementById("canvas")!!
canvas.clear() canvas.clear()
renderer.attach(canvas) renderer.attach(canvas)

View File

@ -5,7 +5,7 @@ import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.toDynamic import hep.dataforge.meta.toDynamic
import hep.dataforge.vis.common.* import hep.dataforge.vis.common.*
import hep.dataforge.vis.spatial.MeshThreeFactory import hep.dataforge.vis.spatial.three.MeshThreeFactory
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
class JSRootGeometry(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta) { class JSRootGeometry(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta) {

View File

@ -7,7 +7,7 @@ import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayLeaf import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.node import hep.dataforge.vis.common.node
import hep.dataforge.vis.spatial.ThreeFactory import hep.dataforge.vis.spatial.three.ThreeFactory
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
class JSRootObject(parent: DisplayObject?, meta: Meta, val data: dynamic) : DisplayLeaf(parent, meta) { class JSRootObject(parent: DisplayObject?, meta: Meta, val data: dynamic) : DisplayLeaf(parent, meta) {

View File

@ -1,34 +1,29 @@
package hep.dataforge.vis.spatial.jsroot package hep.dataforge.vis.spatial.jsroot
import hep.dataforge.context.AbstractPlugin import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag import hep.dataforge.context.PluginTag
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.toName import hep.dataforge.names.toName
import hep.dataforge.vis.spatial.ThreePlugin import hep.dataforge.vis.spatial.three.ThreeFactory
import hep.dataforge.vis.spatial.three.ThreePlugin
class JSRootPlugin : AbstractPlugin() { class JSRootPlugin : AbstractPlugin() {
override val tag: PluginTag get() = JSRootPlugin.tag override val tag: PluginTag get() = JSRootPlugin.tag
override fun dependsOn() = listOf(ThreePlugin) override fun dependsOn() = listOf(ThreePlugin)
override fun attach(context: Context) { override fun provideTop(target: String): Map<Name, Any> {
super.attach(context) return when(target){
context.plugins.get<ThreePlugin>()?.factories?.apply { ThreeFactory.TYPE -> mapOf(
this["jsRoot.geometry".toName()] = ThreeJSRootGeometryFactory "jsRoot.geometry".toName() to ThreeJSRootGeometryFactory,
this["jsRoot.object".toName()] = ThreeJSRootObjectFactory "jsRoot.object".toName() to ThreeJSRootObjectFactory
)
else -> emptyMap()
} }
} }
override fun detach() {
context.plugins.get<ThreePlugin>()?.factories?.apply {
remove("jsRoot.geometry".toName())
remove("jsRoot.object".toName())
}
super.detach()
}
companion object: PluginFactory<JSRootPlugin> { companion object: PluginFactory<JSRootPlugin> {
override val tag = PluginTag("vis.jsroot", "hep.dataforge") override val tag = PluginTag("vis.jsroot", "hep.dataforge")
override val type = JSRootPlugin::class override val type = JSRootPlugin::class

View File

@ -1,11 +0,0 @@
@file:JsModule("three-full")
@file:JsNonModule
package hep.dataforge.vis.spatial.three
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Vector3
external class ConvexGeometry(points: Array<Vector3>) : Geometry
external class ConvexBufferGeometry(points: Array<Vector3>) : BufferGeometry

View File

@ -1,11 +0,0 @@
@file:JsModule("three-full")
@file:JsNonModule
package hep.dataforge.vis.spatial.three
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class EdgesGeometry(geometry: Geometry, thresholdAngle: Int = definedExternally) : BufferGeometry {
constructor(geometry: BufferGeometry, thresholdAngle: Int = definedExternally)
}

View File

@ -1,4 +1,4 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.double import hep.dataforge.meta.double

View File

@ -0,0 +1,31 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Composite
import hep.dataforge.vis.spatial.CompositeType
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.objects.Mesh
/**
* This should be inner, becaulse it uses object builder
*/
class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) {
override fun buildGeometry(obj: Composite): BufferGeometry {
val first = three.buildObject3D(obj.first) as? Mesh ?: error("First part of composite is not a mesh")
first.updateMatrix()
val second = three.buildObject3D(obj.second) as? Mesh ?: error("Second part of composite is not a mesh")
second.updateMatrix()
val firstCSG = CSG.fromMesh(first)
val secondCSG = CSG.fromMesh(second)
val resultCSG = when (obj.type) {
CompositeType.UNION -> firstCSG.union(secondCSG)
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG)
CompositeType.SUBTRACT -> firstCSG.subtract(secondCSG)
}
val mesh = CSG.toMesh(resultCSG, second.matrix)
return (mesh.geometry as Geometry).toBufferGeometry()
}
}

View File

@ -1,4 +1,4 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean import hep.dataforge.meta.boolean
import hep.dataforge.names.startsWith import hep.dataforge.names.startsWith
@ -7,14 +7,14 @@ import hep.dataforge.provider.Type
import hep.dataforge.vis.common.DisplayObject import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.getProperty import hep.dataforge.vis.common.getProperty
import hep.dataforge.vis.common.onChange import hep.dataforge.vis.common.onChange
import hep.dataforge.vis.spatial.ThreeFactory.Companion.TYPE import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.ThreeFactory.Companion.buildMesh import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
import hep.dataforge.vis.spatial.three.ConvexBufferGeometry import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.buildMesh
import hep.dataforge.vis.spatial.three.EdgesGeometry
import hep.dataforge.vis.spatial.three.euler
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.external.geometries.ConvexBufferGeometry
import info.laht.threekt.geometries.BoxBufferGeometry import info.laht.threekt.geometries.BoxBufferGeometry
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
@ -35,7 +35,12 @@ interface ThreeFactory<T : DisplayObject> {
companion object { companion object {
const val TYPE = "threeFactory" const val TYPE = "threeFactory"
internal fun buildMesh(obj: DisplayObject, geometry: BufferGeometry): Mesh { fun <T : DisplayObject> buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh {
val geometry = geometryBuilder(obj)
//JS sometimes tries to pass Geometry as BufferGeometry
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
val mesh = Mesh(geometry, obj.material) val mesh = Mesh(geometry, obj.material)
//inherited edges definition, enabled by default //inherited edges definition, enabled by default
@ -49,6 +54,29 @@ interface ThreeFactory<T : DisplayObject> {
val material = obj.getProperty("edges.material")?.material() ?: Materials.DEFAULT val material = obj.getProperty("edges.material")?.material() ?: Materials.DEFAULT
mesh.add(LineSegments(WireframeGeometry(mesh.geometry as BufferGeometry), material)) mesh.add(LineSegments(WireframeGeometry(mesh.geometry as BufferGeometry), material))
} }
//set position for meseh
mesh.updatePosition(obj)
//add listener to object properties
obj.onChange(this) { name, _, _ ->
if (name.toString() == "material") {
//updated material
mesh.material = obj.material
} else if (
name.startsWith("pos".toName()) ||
name.startsWith("scale".toName()) ||
name.startsWith("rotation".toName()) ||
name.toString() == "visible"
) {
//update position of mesh using this object
mesh.updatePosition(obj)
} else {
//full update
mesh.geometry = geometryBuilder(obj)
mesh.material = obj.material
}
}
return mesh return mesh
} }
} }
@ -78,7 +106,8 @@ operator fun <T : DisplayObject> ThreeFactory<T>.invoke(obj: Any): Object3D {
/** /**
* Basic geometry-based factory * Basic geometry-based factory
*/ */
abstract class MeshThreeFactory<T : DisplayObject>(override val type: KClass<out T>) : ThreeFactory<T> { abstract class MeshThreeFactory<T : DisplayObject>(override val type: KClass<out T>) :
ThreeFactory<T> {
/** /**
* Build a geometry for an object * Build a geometry for an object
*/ */
@ -86,36 +115,8 @@ abstract class MeshThreeFactory<T : DisplayObject>(override val type: KClass<out
override fun invoke(obj: T): Mesh { override fun invoke(obj: T): Mesh {
val geometry = buildGeometry(obj)
//JS sometimes tries to pass Geometry as BufferGeometry
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
//create mesh from geometry //create mesh from geometry
val mesh = buildMesh(obj, geometry) val mesh = buildMesh(obj, ::buildGeometry)
//set position for meseh
mesh.updatePosition(obj)
//add listener to object properties
obj.onChange(this) { name, _, _ ->
if (name.toString() == "material") {
//updated material
mesh.material = obj.material
} else if (
name.startsWith("pos".toName()) ||
name.startsWith("scale".toName()) ||
name.startsWith("rotation".toName()) ||
name.toString() == "visible"
) {
//update position of mesh using this object
mesh.updatePosition(obj)
} else {
//full update
mesh.geometry = buildGeometry(obj)
mesh.material = obj.material
}
}
return mesh return mesh
} }
} }

View File

@ -1,13 +1,13 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.int import hep.dataforge.meta.int
import hep.dataforge.vis.spatial.three.toBufferGeometry import hep.dataforge.vis.spatial.GeometryBuilder
import hep.dataforge.vis.spatial.Point3D
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Face3 import info.laht.threekt.core.Face3
import info.laht.threekt.core.Geometry import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Color
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
// TODO use unsafe cast instead // TODO use unsafe cast instead
@ -32,24 +32,18 @@ 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 materialIndex = meta["materialIndex"].int ?: 0 val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal?.asVector() ?: Vector3(0, 0, 0))
val color = meta["color"]?.color() ?: Color() meta["materialIndex"].int?.let { face.materialIndex = it }
faces.add( meta["color"]?.color()?.let { face.color = it }
Face3( faces.add(face)
append(vertex1),
append(vertex2),
append(vertex3),
normal?.asVector() ?: Vector3(0, 0, 0),
color,
materialIndex
)
)
} }
override fun build(): BufferGeometry { override fun build(): BufferGeometry {
return Geometry().apply { return Geometry().apply {
vertices = this@ThreeGeometryBuilder.vertices.map { it.asVector() }.toTypedArray() vertices = this@ThreeGeometryBuilder.vertices.map { it.asVector() }.toTypedArray()
faces = this@ThreeGeometryBuilder.faces.toTypedArray() faces = this@ThreeGeometryBuilder.faces.toTypedArray()
computeBoundingSphere()
computeFaceNormals()
}.toBufferGeometry() }.toBufferGeometry()
} }
} }

View File

@ -0,0 +1,67 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.context.Context
import hep.dataforge.meta.*
import hep.dataforge.output.Output
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.spatial.demo.require
import info.laht.threekt.WebGLRenderer
import info.laht.threekt.helpers.AxesHelper
import info.laht.threekt.lights.AmbientLight
import info.laht.threekt.scenes.Scene
import org.w3c.dom.Element
import kotlin.browser.window
private val elementResizeEvent = require("element-resize-event")
class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output<DisplayObject> {
override val context: Context get() = three.context
val scene: Scene = Scene().apply {
add(AmbientLight())
if (meta["axis"] != null) {
val axesHelper = AxesHelper(meta["axis.size"].int ?: 1)
add(axesHelper)
}
}
private val camera = three.buildCamera(meta["camera"].node ?: EmptyMeta)
fun attach(element: Element, computeWidth: Element.() -> Int = { element.clientWidth }) {
val width by meta.number(computeWidth(element)).int
val height: Int = (width / camera.aspect).toInt()
val renderer = WebGLRenderer { antialias = true }.apply {
setClearColor(Colors.skyblue, 1)
setSize(width, height)
}
three.addControls(camera,renderer.domElement, meta["controls"].node?:EmptyMeta)
fun animate() {
window.requestAnimationFrame {
animate()
}
renderer.render(scene, camera)
}
elementResizeEvent(element) {
camera.updateProjectionMatrix()
val newWidth = computeWidth(element)
renderer.setSize(newWidth, (newWidth / camera.aspect).toInt())
}
element.replaceWith(renderer.domElement)
animate()
}
override fun render(obj: DisplayObject, meta: Meta) {
scene.add(three.buildObject3D(obj))
}
}
fun ThreePlugin.output(meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit = {}) =
ThreeOutput(this, buildMeta(meta, override))

View File

@ -0,0 +1,81 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.context.content
import hep.dataforge.meta.*
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.spatial.*
import info.laht.threekt.cameras.Camera
import info.laht.threekt.cameras.PerspectiveCamera
import info.laht.threekt.core.Object3D
import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.external.controls.TrackballControls
import org.w3c.dom.Node
import kotlin.collections.set
import kotlin.reflect.KClass
class ThreePlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag
private val objectFactories = HashMap<KClass<out DisplayObject>, ThreeFactory<*>>()
private val compositeFactory = ThreeCompositeFactory(this)
init {
//Add specialized factories here
objectFactories[Box::class] = ThreeBoxFactory
objectFactories[Convex::class] = ThreeConvexFactory
}
private fun findObjectFactory(type: KClass<out DisplayObject>): ThreeFactory<*>? {
return objectFactories[type]
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type }
}
fun buildObject3D(obj: DisplayObject): Object3D {
return when (obj) {
is DisplayGroup -> Group(obj.map { buildObject3D(it) }).apply {
updatePosition(obj)
}
is Composite -> compositeFactory(obj)
else -> {
//find specialized factory for this type if it is present
val factory = findObjectFactory(obj::class)
when {
factory != null -> factory(obj)
obj is Shape -> ThreeShapeFactory(obj)
else -> error("Renderer for ${obj::class} not found")
}
}
}
}
fun buildCamera(meta: Meta) = PerspectiveCamera(
meta["fov"].int ?: 75,
meta["aspect"].double ?: 1.0,
meta["nearClip"].double ?: World.CAMERA_NEAR_CLIP,
meta["farClip"].double ?: World.CAMERA_FAR_CLIP
).apply {
position.setZ(World.CAMERA_INITIAL_DISTANCE)
rotation.set(
World.CAMERA_INITIAL_X_ANGLE,
World.CAMERA_INITIAL_Y_ANGLE,
World.CAMERA_INITIAL_Z_ANGLE
)
}
fun addControls(camera: Camera, element: Node, meta: Meta) {
when (meta["type"].string) {
"trackball" -> TrackballControls(camera, element)
else -> OrbitControls(camera, element)
}
}
companion object : PluginFactory<ThreePlugin> {
override val tag = PluginTag("vis.three", "hep.dataforge")
override val type = ThreePlugin::class
override fun invoke(meta: Meta) = ThreePlugin()
}
}

View File

@ -0,0 +1,95 @@
@file:JsModule("@hi-level/three-csg")
@file:JsNonModule
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS",
"EXTERNAL_DELEGATION",
"NESTED_CLASS_IN_EXTERNAL_INTERFACE"
)
package hep.dataforge.vis.spatial.three
import info.laht.threekt.math.Matrix4
import info.laht.threekt.objects.Mesh
open external class CSG {
open var polygons: Any
open fun clone(): CSG
open fun toPolygons(): Array<Polygon>
open fun union(csg: CSG): CSG
open fun subtract(csg: CSG): CSG
open fun intersect(csg: CSG): CSG
open fun inverse(): CSG
companion object {
fun fromPolygons(polygons: Array<Polygon>): CSG
fun fromGeometry(geom: Any): CSG
fun fromMesh(mesh: Mesh): CSG
fun toMesh(csg: CSG, toMatrix: Matrix4): Mesh
fun iEval(tokens: Mesh, index: Number? = definedExternally /* null */): Unit
fun eval(tokens: Mesh, doRemove: Boolean): Mesh
var _tmpm3: Any
var doRemove: Any
var currentOp: Any
var currentPrim: Any
var nextPrim: Any
var sourceMesh: Any
}
}
open external class Vector(x: Number, y: Number, z: Number) {
open fun clone(): Any
open fun negated(): Vector
open fun plus(a: Vector): Vector
open fun minus(a: Vector): Vector
open fun times(a: Number): Vector
open fun dividedBy(a: Number): Vector
open fun lerp(a: Vector, t: Number): Any
open fun unit(): Vector
open fun cross(a: Vector): Any
}
external interface IVector {
var x: Number
var y: Number
var z: Number
}
open external class Vertex(pos: IVector, normal: IVector, uv: IVector) {
open var pos: Vector
open var normal: Vector
open var uv: Vector
open fun clone(): Vertex
open fun flip(): Unit
open fun interpolate(other: Vertex, t: Number): Vertex
}
open external class Plane(normal: Vector, w: Number) {
open var normal: Vector
open var w: Number
open fun clone(): Plane
open fun flip(): Unit
open fun splitPolygon(
polygon: Polygon,
coplanarFront: Array<Polygon>,
coplanarBack: Array<Polygon>,
front: Array<Polygon>,
back: Array<Polygon>
): Unit
companion object {
fun fromPoints(a: Vector, b: Vector, c: Vector): Plane
var EPSILON: Any
}
}
open external class Polygon(vertices: Array<Vertex>, shared: Any? = definedExternally /* null */) {
open var plane: Plane
open var vertices: Array<Vertex>
open var shared: Any
open fun clone(): Polygon
open fun flip(): Unit
}

View File

@ -1,5 +1,5 @@
import * as JSROOT from "JSRootUtils" import * as JSROOT from "JSRootUtils"
import * as THREE from "three-full" import * as THREE from "three"
import * as ThreeBSP from "ThreeCSG" import * as ThreeBSP from "ThreeCSG"
// Holder of all TGeo-related functions and classes // Holder of all TGeo-related functions and classes

View File

@ -1,4 +1,4 @@
import * as THREE from "three-full" import * as THREE from "three"
const EPSILON = 1e-5, const EPSILON = 1e-5,
COPLANAR = 0, COPLANAR = 0,

View File

@ -22,9 +22,9 @@
</div> </div>
<div class="container" id="canvas"></div> <div class="container" id="canvas"></div>
<!--<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"--> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
<!-- integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"--> integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
<!-- crossorigin="anonymous"></script>--> crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>

View File

@ -26,12 +26,12 @@ class Box(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta), Shape
val node6 = Point3D(dx, -dy, dz) val node6 = Point3D(dx, -dy, dz)
val node7 = Point3D(dx, dy, dz) val node7 = Point3D(dx, dy, dz)
val node8 = Point3D(-dx, dy, dz) val node8 = Point3D(-dx, dy, dz)
geometryBuilder.face4(node1, node4, node3, node2, Point3D(0, 0, -1)) geometryBuilder.face4(node1, node4, node3, node2)
geometryBuilder.face4(node1, node2, node6, node5, Point3D(0, -1, 0)) geometryBuilder.face4(node1, node2, node6, node5)
geometryBuilder.face4(node2, node3, node7, node6, Point3D(1, 0, 0)) geometryBuilder.face4(node2, node3, node7, node6)
geometryBuilder.face4(node4, node8, node7, node3, Point3D(0, 1, 0)) geometryBuilder.face4(node4, node8, node7, node3)
geometryBuilder.face4(node1, node5, node8, node4, Point3D(-1, 0, 0)) geometryBuilder.face4(node1, node5, node8, node4)
geometryBuilder.face4(node8, node5, node6, node7, Point3D(0, 0, 1)) geometryBuilder.face4(node8, node5, node6, node7)
} }
companion object { companion object {
@ -40,4 +40,11 @@ class Box(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta), Shape
} }
fun DisplayGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) = fun DisplayGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) =
Box(this, meta).apply(action).also { add(it) } Box(this, meta).apply(action).also { add(it) }
fun DisplayGroup.box(xSize: Number, ySize: Number, zSize: Number, meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) =
Box(this, meta).apply(action).apply{
this.xSize = xSize.toDouble()
this.ySize = ySize.toDouble()
this.zSize = zSize.toDouble()
}.also { add(it) }

View File

@ -2,12 +2,37 @@ package hep.dataforge.vis.spatial
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.seal
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayLeaf import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject import hep.dataforge.vis.common.DisplayObject
enum class CompositeType {
UNION,
INTERSECT,
SUBTRACT
}
class Composite( class Composite(
parent: DisplayObject?, parent: DisplayObject?,
val first: DisplayObject, val first: DisplayObject,
val second: DisplayObject, val second: DisplayObject,
val type: CompositeType = CompositeType.UNION,
meta: Meta = EmptyMeta meta: Meta = EmptyMeta
) : DisplayLeaf(parent,meta) ) : DisplayLeaf(parent, meta)
fun DisplayGroup.composite(type: CompositeType, builder: DisplayGroup.() -> Unit): Composite {
val group = DisplayGroup().apply(builder)
val children = group.toList()
if (children.size != 2) error("Composite requires exactly two children")
return Composite(this, children[0], children[1], type, group.properties.seal()).also { add(it) }
}
fun DisplayGroup.union(builder: DisplayGroup.() -> Unit) =
composite(CompositeType.UNION,builder)
fun DisplayGroup.subtract(builder: DisplayGroup.() -> Unit) =
composite(CompositeType.SUBTRACT,builder)
fun DisplayGroup.intersect(builder: DisplayGroup.() -> Unit) =
composite(CompositeType.INTERSECT,builder)

View File

@ -27,6 +27,8 @@ data class Point3D(val x: Number, val y: Number, val z: Number) : MetaRepr {
fun from(meta: Meta): Point3D{ fun from(meta: Meta): Point3D{
return Point3D(meta["x"].number ?: 0, meta["y"].number ?: 0, meta["y"].number ?: 0) return Point3D(meta["x"].number ?: 0, meta["y"].number ?: 0, meta["y"].number ?: 0)
} }
val zero = Point3D(0,0,0)
} }
} }
@ -41,7 +43,7 @@ interface GeometryBuilder<T : Any> {
* @param normal optional external normal to the face * @param normal optional external normal to the face
* @param meta optional additional platform-specific parameters like color or texture index * @param meta optional additional platform-specific parameters like color or texture index
*/ */
fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D? = null, meta: Meta = EmptyMeta) fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta = EmptyMeta)
fun build(): T fun build(): T
} }