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{
kotlin("jvm") version "1.3.40" apply false

View File

@ -17,19 +17,15 @@ class DisplayGroup(
private val namedChildren = HashMap<Name, 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 fun iterator(): Iterator<DisplayObject> = (namedChildren.values + unnamedChildren).iterator()
override fun listNames(target: String): Sequence<Name> =
namedChildren.keys.asSequence()
override fun provideTop(target: String, name: Name): Any? {
return if (target == defaultTarget) {
namedChildren[name]
} else {
null
override fun provideTop(target: String): Map<Name, Any> {
return when(target){
DisplayObject.TYPE -> namedChildren
else -> emptyMap()
}
}
@ -55,14 +51,18 @@ class DisplayGroup(
/**
*
*/
operator fun set(key: String, child: DisplayObject?) {
val name = key.toName()
if (child == null) {
namedChildren.remove(name)
operator fun set(key: String?, child: DisplayObject?) {
if(key == null){
} 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.names.Name
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.TAGS_KEY
import hep.dataforge.vis.common.DisplayObject.Companion.TYPE
/**
* 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.
@ -18,8 +21,12 @@ interface DisplayObject {
val properties: Styled
override fun toMeta(): Meta = buildMeta(properties) {
"type" to this::class
}
companion object {
const val TARGET = "display"
const val TYPE = "display"
const val DEFAULT_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) {
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 {
implementation(project(":dataforge-vis-spatial"))
//implementation("ch.viseon.threejs:wrapper:105.0.0")
implementation("info.laht.threekt:threejs-wrapper:0.88-npm-2")
implementation("info.laht.threekt:threejs-wrapper:0.106-npm-2")
testCompile(kotlin("test-js"))
}
@ -22,7 +21,7 @@ configure<KotlinFrontendExtension> {
downloadNodeJsVersion = "latest"
configure<NpmExtension> {
dependency("three-full")
dependency("three","0.106.2")
dependency("@hi-level/three-csg")
dependency("style-loader")
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.meta.number
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.jsroot.JSRootPlugin
import hep.dataforge.vis.spatial.jsroot.jsRootGeometry
@ -28,7 +29,7 @@ class ThreeDemoApp : ApplicationBase() {
}.build()
context.plugins.load(ThreeDemoGrid()).run {
demo("group", "Group demo") {
demo("dynamic", "Dynamic properties demo") {
val group = group {
box {
z = 110.0
@ -42,6 +43,7 @@ class ThreeDemoApp : ApplicationBase() {
xSize = 100.0
ySize = 100.0
zSize = 100.0
//override color for this cube
color(1530)
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 {
val random = Random(111)
while (isActive) {
delay(1000)
color = random.nextInt(0, Int.MAX_VALUE)
material = random.nextInt(0, Int.MAX_VALUE)
}
}
}
@ -68,7 +70,9 @@ class ThreeDemoApp : ApplicationBase() {
jsRootGeometry {
y = 110.0
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))
}
}
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.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.spatial.ThreeOutput
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.create
import kotlinx.html.h2
@ -33,6 +35,8 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
private val gridRoot = document.create.div("row")
private val outputs: MutableMap<Name, ThreeOutput> = HashMap()
override fun dependsOn(): List<PluginFactory<*>> = listOf(ThreePlugin)
override fun attach(context: Context) {
super.attach(context)
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> {
val three = context.plugins.get<ThreePlugin>()!!
return outputs.getOrPut(name) {
if (type != DisplayObject::class) error("Supports only DisplayObject")
val output = ThreeOutput.build(context, meta) {
val output = three.output(meta) {
"axis" to {
"size" to 500
}
@ -52,8 +58,8 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
//TODO calculate cell width here using jquery
gridRoot.append {
span("border") {
div("col-4") {
output.attach(div { id = "output-$name" }){ 300}
div("col-6") {
output.attach(div { id = "output-$name" }) { 500 }
hr()
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.PluginTag
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
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
class GDMLPlugin : AbstractPlugin() {
@ -14,21 +16,13 @@ class GDMLPlugin : AbstractPlugin() {
override fun dependsOn() = listOf(ThreePlugin)
override fun attach(context: Context) {
super.attach(context)
context.plugins.get<ThreePlugin>()?.factories?.apply {
this["gdml".toName()] = ThreeGDMLFactory
override fun provideTop(target: String): Map<Name, Any> {
return when(target){
ThreeFactory.TYPE-> mapOf("gdml".toName() to 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> {
override val tag = PluginTag("vis.gdml", "hep.dataforge")
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.DisplayObject
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.createGeometry
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.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.render
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import org.w3c.dom.HTMLDivElement
import org.w3c.dom.events.Event
import org.w3c.files.FileList
@ -59,7 +61,7 @@ class JSRootDemoApp : ApplicationBase() {
FileReader().apply {
onload = {
val string = result as String
val renderer = ThreeOutput(Global)
val renderer = Global.plugins.fetch(ThreePlugin).output()
val canvas = document.getElementById("canvas")!!
canvas.clear()
renderer.attach(canvas)

View File

@ -5,7 +5,7 @@ import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.toDynamic
import hep.dataforge.vis.common.*
import hep.dataforge.vis.spatial.MeshThreeFactory
import hep.dataforge.vis.spatial.three.MeshThreeFactory
import info.laht.threekt.core.BufferGeometry
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.DisplayObject
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
class JSRootObject(parent: DisplayObject?, meta: Meta, val data: dynamic) : DisplayLeaf(parent, meta) {

View File

@ -1,34 +1,29 @@
package hep.dataforge.vis.spatial.jsroot
import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
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() {
override val tag: PluginTag get() = JSRootPlugin.tag
override fun dependsOn() = listOf(ThreePlugin)
override fun attach(context: Context) {
super.attach(context)
context.plugins.get<ThreePlugin>()?.factories?.apply {
this["jsRoot.geometry".toName()] = ThreeJSRootGeometryFactory
this["jsRoot.object".toName()] = ThreeJSRootObjectFactory
override fun provideTop(target: String): Map<Name, Any> {
return when(target){
ThreeFactory.TYPE -> mapOf(
"jsRoot.geometry".toName() to ThreeJSRootGeometryFactory,
"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> {
override val tag = PluginTag("vis.jsroot", "hep.dataforge")
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.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.names.startsWith
@ -7,14 +7,14 @@ import hep.dataforge.provider.Type
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.getProperty
import hep.dataforge.vis.common.onChange
import hep.dataforge.vis.spatial.ThreeFactory.Companion.TYPE
import hep.dataforge.vis.spatial.ThreeFactory.Companion.buildMesh
import hep.dataforge.vis.spatial.three.ConvexBufferGeometry
import hep.dataforge.vis.spatial.three.EdgesGeometry
import hep.dataforge.vis.spatial.three.euler
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.buildMesh
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.external.geometries.ConvexBufferGeometry
import info.laht.threekt.geometries.BoxBufferGeometry
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
@ -35,7 +35,12 @@ interface ThreeFactory<T : DisplayObject> {
companion object {
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)
//inherited edges definition, enabled by default
@ -49,6 +54,29 @@ interface ThreeFactory<T : DisplayObject> {
val material = obj.getProperty("edges.material")?.material() ?: Materials.DEFAULT
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
}
}
@ -78,7 +106,8 @@ operator fun <T : DisplayObject> ThreeFactory<T>.invoke(obj: Any): Object3D {
/**
* 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
*/
@ -86,36 +115,8 @@ abstract class MeshThreeFactory<T : DisplayObject>(override val type: KClass<out
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
val mesh = buildMesh(obj, geometry)
//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
}
}
val mesh = buildMesh(obj, ::buildGeometry)
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.get
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.Face3
import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Color
import info.laht.threekt.math.Vector3
// 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) {
val materialIndex = meta["materialIndex"].int ?: 0
val color = meta["color"]?.color() ?: Color()
faces.add(
Face3(
append(vertex1),
append(vertex2),
append(vertex3),
normal?.asVector() ?: Vector3(0, 0, 0),
color,
materialIndex
)
)
val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal?.asVector() ?: Vector3(0, 0, 0))
meta["materialIndex"].int?.let { face.materialIndex = it }
meta["color"]?.color()?.let { face.color = it }
faces.add(face)
}
override fun build(): BufferGeometry {
return Geometry().apply {
vertices = this@ThreeGeometryBuilder.vertices.map { it.asVector() }.toTypedArray()
faces = this@ThreeGeometryBuilder.faces.toTypedArray()
computeBoundingSphere()
computeFaceNormals()
}.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 THREE from "three-full"
import * as THREE from "three"
import * as ThreeBSP from "ThreeCSG"
// 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,
COPLANAR = 0,

View File

@ -22,9 +22,9 @@
</div>
<div class="container" id="canvas"></div>
<!--<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"-->
<!-- integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"-->
<!-- crossorigin="anonymous"></script>-->
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
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 node7 = Point3D(dx, dy, dz)
val node8 = Point3D(-dx, dy, dz)
geometryBuilder.face4(node1, node4, node3, node2, Point3D(0, 0, -1))
geometryBuilder.face4(node1, node2, node6, node5, Point3D(0, -1, 0))
geometryBuilder.face4(node2, node3, node7, node6, Point3D(1, 0, 0))
geometryBuilder.face4(node4, node8, node7, node3, Point3D(0, 1, 0))
geometryBuilder.face4(node1, node5, node8, node4, Point3D(-1, 0, 0))
geometryBuilder.face4(node8, node5, node6, node7, Point3D(0, 0, 1))
geometryBuilder.face4(node1, node4, node3, node2)
geometryBuilder.face4(node1, node2, node6, node5)
geometryBuilder.face4(node2, node3, node7, node6)
geometryBuilder.face4(node4, node8, node7, node3)
geometryBuilder.face4(node1, node5, node8, node4)
geometryBuilder.face4(node8, node5, node6, node7)
}
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 = {}) =
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.Meta
import hep.dataforge.meta.seal
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject
enum class CompositeType {
UNION,
INTERSECT,
SUBTRACT
}
class Composite(
parent: DisplayObject?,
val first: DisplayObject,
val second: DisplayObject,
val type: CompositeType = CompositeType.UNION,
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{
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 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
}