A major change in DisplayGroup implementation

This commit is contained in:
Alexander Nozik 2019-07-03 16:02:26 +03:00
parent 4274dfff88
commit 697e7908a7
23 changed files with 237 additions and 223 deletions

View File

@ -14,6 +14,7 @@ allprojects {
jcenter()
maven("https://kotlin.bintray.com/kotlinx")
maven("http://npm.mipt.ru:8081/artifactory/gradle-dev-local")
maven("https://kotlin.bintray.com/js-externals")
}
group = "hep.dataforge"

View File

@ -0,0 +1,83 @@
package hep.dataforge.vis.common
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.Styled
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.provider.Provider
/**
* A display group which allows both named and unnamed children
*/
class DisplayGroup(
override val parent: DisplayObject? = null, meta: Meta = EmptyMeta
) : DisplayObject, Iterable<DisplayObject>, Provider {
private val namedChildren = HashMap<Name, DisplayObject>()
private val unnamedChildren = ArrayList<DisplayObject>()
override val defaultTarget: String get() = DisplayObject.TARGET
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
}
}
private data class Listener(val owner: Any?, val callback: (Name?, DisplayObject?) -> Unit)
private val listeners = HashSet<Listener>()
/**
* Add listener for children change
*/
fun onChildrenChange(owner: Any?, action: (Name?, DisplayObject?) -> Unit) {
listeners.add(Listener(owner, action))
}
/**
* Remove children change listener
*/
fun removeChildrenChangeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
/**
*
*/
operator fun set(key: String, child: DisplayObject?) {
val name = key.toName()
if (child == null) {
namedChildren.remove(name)
} else {
namedChildren[name] = child
}
listeners.forEach { it.callback(name, child) }
}
/**
* Append unnamed child
*/
fun add(child: DisplayObject) {
unnamedChildren.add(child)
listeners.forEach { it.callback(null, child) }
}
/**
* remove unnamed child
*/
fun remove(child: DisplayObject) {
unnamedChildren.remove(child)
listeners.forEach { it.callback(null, null) }
}
}

View File

@ -1,67 +0,0 @@
package hep.dataforge.vis.common
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.Styled
internal data class InvalidationListener(
val owner: Any?,
val action: () -> Unit
)
/**
* A [DisplayGroup] containing ordered list of elements
*/
class DisplayObjectList(
override val parent: DisplayObject? = null,
// override val type: String = DisplayObject.DEFAULT_TYPE,
meta: Meta = EmptyMeta
) : DisplayGroup {
private val _children = ArrayList<DisplayObject>()
/**
* An ordered list of direct descendants
*/
val children: List<DisplayObject> get() = _children
override fun iterator(): Iterator<DisplayObject> = children.iterator()
override val properties = Styled(meta)
private val listeners = HashSet<InvalidationListener>()
/**
* Add a child object and notify listeners
*/
fun addChild(obj: DisplayObject) {
_children.add(obj)
listeners.forEach { it.action() }
}
/**
* Remove a specific child and notify listeners
*/
fun removeChild(obj: DisplayObject) {
if (_children.remove(obj)) {
listeners.forEach { it.action }
}
}
/**
* Add listener for children change
* TODO add detailed information into change listener
*/
fun onChildrenChange(owner: Any?, action: () -> Unit) {
listeners.add(InvalidationListener(owner, action))
}
/**
* Remove children change listener
*/
fun removeChildrenChangeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
}

View File

@ -16,14 +16,11 @@ interface DisplayObject {
*/
val parent: DisplayObject?
// /**
// * The type of this object. Uses `.` notation. Empty type means untyped group
// */
// val type: String
val properties: Styled
companion object {
const val TARGET = "display"
const val DEFAULT_TYPE = ""
//const val TYPE_KEY = "@type"
//const val CHILDREN_KEY = "@children"
@ -73,4 +70,11 @@ open class DisplayLeaf(
final override val properties = Styled(meta)
}
interface DisplayGroup: DisplayObject, Iterable<DisplayObject>
///**
// * A group that could contain both named and unnamed children. Unnamed children could be accessed only via
// */
//interface DisplayGroup : DisplayObject, Iterable<DisplayObject>, Provider {
// override val defaultTarget: String get() = DisplayObject.TARGET
//
// val children
//}

View File

@ -1,43 +0,0 @@
package hep.dataforge.vis.common
import hep.dataforge.names.Name
import hep.dataforge.names.NameToken
/**
* A navigable hierarchical display node
*/
interface DisplayTree : DisplayGroup {
operator fun get(nameToken: NameToken): DisplayObject?
}
interface MutableDisplayTree : DisplayTree {
operator fun set(nameToken: NameToken, group: DisplayObject)
}
/**
* Recursively get a child
*/
tailrec operator fun DisplayTree.get(name: Name): DisplayObject? = when (name.length) {
0 -> this
1 -> this[name[0]]
else -> name.first()?.let { this[it] as? DisplayTree }?.get(name.cutFirst())
}
/**
* Set given object creating intermediate empty groups if needed
* @param name - the full name of a child
* @param objFactory - a function that creates child object from parent (to avoid mutable parent parameter)
*/
fun MutableDisplayTree.set(name: Name, objFactory: (parent: DisplayObject) -> DisplayObject): Unit =
when (name.length) {
0 -> error("Can't set object with empty name")
1 -> set(name[0], objFactory(this))
else -> (this[name.first()!!] ?: DisplayObjectList(this)).run {
if (this is MutableDisplayTree) {
this.set(name.cutFirst(), objFactory)
} else {
error("Can't assign child to a leaf element $this")
}
}
}

View File

@ -9,7 +9,6 @@ plugins {
id("org.jetbrains.kotlin.frontend")
}
val kotlinVersion: String by rootProject.extra
dependencies {
@ -24,6 +23,7 @@ configure<KotlinFrontendExtension> {
configure<NpmExtension> {
dependency("three-full")
dependency("@hi-level/three-csg")
dependency("style-loader")
dependency("element-resize-event")
devDependency("karma")

View File

@ -1,13 +1,14 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.boolean
import hep.dataforge.names.startsWith
import hep.dataforge.names.toName
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.ThreeFactory.Companion.updateMesh
import hep.dataforge.vis.spatial.three.ConvexBufferGeometry
import hep.dataforge.vis.spatial.three.EdgesGeometry
import hep.dataforge.vis.spatial.three.euler
@ -15,12 +16,11 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxBufferGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.math.Vector3
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass
internal val DisplayObject.material get() = getProperty("color").material()
internal val DisplayObject.material get() = getProperty("material").material()
/**
* Builder and updater for three.js object
@ -35,39 +35,38 @@ interface ThreeFactory<T : DisplayObject> {
companion object {
const val TYPE = "threeFactory"
/**
* Update position, rotation and visibility
*/
internal fun updatePosition(obj: DisplayObject, target: Object3D) {
target.apply {
position.set(obj.x, obj.y, obj.z)
setRotationFromEuler(obj.euler)
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
visible = obj.visible
}
}
internal fun buildMesh(obj: DisplayObject, geometry: BufferGeometry): Mesh {
val mesh = Mesh(geometry, obj.material)
if (obj.getProperty("edges.enabled")?.boolean != false) {
//inherited edges definition, enabled by default
if (obj.getProperty("edges.enabled").boolean != false) {
val material = obj.getProperty("edges.material")?.material() ?: Materials.DEFAULT
mesh.add(LineSegments(EdgesGeometry(mesh.geometry as BufferGeometry), material))
}
if (obj.getProperty("wireframe.enabled")?.boolean == true) {
//inherited wireframe definition, disabled by default
if (obj.getProperty("wireframe.enabled").boolean == true) {
val material = obj.getProperty("edges.material")?.material() ?: Materials.DEFAULT
mesh.add(LineSegments(WireframeGeometry(mesh.geometry as BufferGeometry), material))
}
return mesh
}
internal fun updateMesh(obj: DisplayObject, geometry: BufferGeometry, mesh: Mesh) {
mesh.geometry = geometry
mesh.material = obj.material
}
}
}
/**
* Update position, rotation and visibility
*/
internal fun Object3D.updatePosition(obj: DisplayObject) {
position.set(obj.x, obj.y, obj.z)
setRotationFromEuler(obj.euler)
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
visible = obj.visible
}
/**
* Unsafe invocation of a factory
*/
operator fun <T : DisplayObject> ThreeFactory<T>.invoke(obj: Any): Object3D {
if (type.isInstance(obj)) {
return invoke(obj as T)
@ -76,9 +75,12 @@ 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> {
/**
* Build an object
* Build a geometry for an object
*/
abstract fun buildGeometry(obj: T): BufferGeometry
@ -86,13 +88,33 @@ 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)
ThreeFactory.updatePosition(obj, mesh)
obj.onChange(this) { _, _, _ ->
ThreeFactory.updatePosition(obj, mesh)
updateMesh(obj, buildGeometry(obj), mesh)
//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
}
@ -104,7 +126,7 @@ abstract class MeshThreeFactory<T : DisplayObject>(override val type: KClass<out
object ThreeShapeFactory : MeshThreeFactory<Shape>(Shape::class) {
override fun buildGeometry(obj: Shape): BufferGeometry {
return obj.run {
ThreeGeometryBuilder().apply { buildGeometry() }.build()
ThreeGeometryBuilder().apply { toGeometry(this) }.build()
}
}
}
@ -114,8 +136,7 @@ object ThreeBoxFactory : MeshThreeFactory<Box>(Box::class) {
BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize)
}
fun Point3D.asVector(): Vector3 = Vector3(this.x, this.y, this.z)
//FIXME not functional yet
object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
val vectors = obj.points.map { it.asVector() }.toTypedArray()

View File

@ -10,6 +10,9 @@ import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Color
import info.laht.threekt.math.Vector3
// TODO use unsafe cast instead
fun Point3D.asVector(): Vector3 = Vector3(this.x, this.y, this.z)
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
private val vertices = ArrayList<Point3D>()

View File

@ -73,9 +73,12 @@ class ThreeOutput(override val context: Context, val meta: Meta = EmptyMeta) : O
}
private fun buildNode(obj: DisplayObject): Object3D? {
return if (obj is DisplayGroup) Group(obj.mapNotNull { buildNode(it) }).apply {
ThreeFactory.updatePosition(obj, this)
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)
@ -94,6 +97,6 @@ class ThreeOutput(override val context: Context, val meta: Meta = EmptyMeta) : O
companion object {
fun build(context: Context, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit) =
ThreeOutput(context, buildMeta(meta,override))
ThreeOutput(context, buildMeta(meta, override))
}
}

View File

@ -5,7 +5,7 @@ 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.names.set
class ThreePlugin : AbstractPlugin() {
override val tag: PluginTag get() = ThreePlugin.tag
@ -13,8 +13,8 @@ class ThreePlugin : AbstractPlugin() {
val factories = HashMap<Name, ThreeFactory<*>>()
init {
//factories["box".toName()] = ThreeBoxFactory
factories["convex".toName()] = ThreeConvexFactory
factories["box"] = ThreeBoxFactory
factories["convex"] = ThreeConvexFactory
}
override fun listNames(target: String): Sequence<Name> {

View File

@ -27,7 +27,7 @@ class ThreeDemoApp : ApplicationBase() {
plugin(JSRootPlugin())
}.build()
val grid = context.plugins.load(ThreeDemoGrid()).apply {
context.plugins.load(ThreeDemoGrid()).run {
demo("group", "Group demo") {
val group = group {
box {
@ -77,8 +77,8 @@ class ThreeDemoApp : ApplicationBase() {
shape {
polygon(8, 50)
}
for(i in 0..100) {
layer(i*5, 20*sin(2*PI/100*i), 20*cos(2*PI/100*i))
for (i in 0..100) {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
}
}
}

View File

@ -12,8 +12,8 @@ import hep.dataforge.names.Name
import hep.dataforge.names.toName
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.common.DisplayObjectList
import hep.dataforge.vis.spatial.ThreeOutput
import hep.dataforge.vis.spatial.render
import kotlinx.html.dom.append
@ -24,6 +24,7 @@ import kotlinx.html.id
import kotlinx.html.js.div
import kotlinx.html.span
import kotlin.browser.document
import kotlin.dom.clear
import kotlin.reflect.KClass
class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
@ -36,6 +37,7 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
super.attach(context)
val elementId = meta["elementID"].string ?: "canvas"
val element = document.getElementById(elementId) ?: error("Element with id $elementId not found on page")
element.clear()
element.append(gridRoot)
}
@ -47,10 +49,11 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
"size" to 500
}
}
//TODO calculate cell width here using jquery
gridRoot.append {
span("border") {
div("col-4") {
output.attach(div { id = "output-$name" }){300}
output.attach(div { id = "output-$name" }){ 300}
hr()
h2 { +(meta["title"].string ?: name.toString()) }
}
@ -70,7 +73,7 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
}
}
fun ThreeDemoGrid.demo(name: String, title: String = name, block: DisplayObjectList.() -> Unit) {
fun ThreeDemoGrid.demo(name: String, title: String = name, block: DisplayGroup.() -> Unit) {
val meta = buildMeta {
"title" to title
}

View File

@ -67,7 +67,7 @@ class JSRootDemoApp : ApplicationBase() {
renderer.render {
val json = parse(string)
JSRootObject(this, EmptyMeta, json).also { addChild(it) }
JSRootObject(this, EmptyMeta, json).also { add(it) }
}
}
readAsText(file)

View File

@ -4,7 +4,6 @@ import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.toDynamic
import hep.dataforge.vis.*
import hep.dataforge.vis.common.*
import hep.dataforge.vis.spatial.MeshThreeFactory
import info.laht.threekt.core.BufferGeometry
@ -54,8 +53,8 @@ class JSRootGeometry(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, m
}
}
fun DisplayObjectList.jsRootGeometry(meta: Meta = EmptyMeta, action: JSRootGeometry.() -> Unit = {}) =
JSRootGeometry(this, meta).apply(action).also { addChild(it) }
fun DisplayGroup.jsRootGeometry(meta: Meta = EmptyMeta, action: JSRootGeometry.() -> Unit = {}) =
JSRootGeometry(this, meta).apply(action).also { add(it) }
//fun Meta.toDynamic(): dynamic {
// fun MetaItem<*>.toDynamic(): dynamic = when (this) {

View File

@ -3,9 +3,9 @@ package hep.dataforge.vis.spatial.jsroot
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.toDynamic
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.DisplayObjectList
import hep.dataforge.vis.common.node
import hep.dataforge.vis.spatial.ThreeFactory
import info.laht.threekt.core.Object3D
@ -28,7 +28,7 @@ object ThreeJSRootObjectFactory : ThreeFactory<JSRootObject> {
}
}
fun DisplayObjectList.jsRootObject(str: String) {
fun DisplayGroup.jsRootObject(str: String) {
val json = JSON.parse<Any>(str)
JSRootObject(this, EmptyMeta, json).also { addChild(it) }
JSRootObject(this, EmptyMeta, json).also { add(it) }
}

View File

@ -4,18 +4,6 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Three js demo for particle physics</title>
<!-- <style>-->
<!-- body {-->
<!-- margin: 0;-->
<!-- overflow: hidden;-->
<!-- }-->
<!-- </style>-->
<!--<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>-->
<!--<script type="text/javascript" src="js/OrbitControls.js"></script>-->
<!--<script type="text/javascript" src="js/three.min.js"></script>-->
<!--<script type="text/javascript" src="js/ThreeCSG.js"></script>-->
<!--<script type="text/javascript" src="js/JSRootCore.js"></script>-->
<!--<script type="text/javascript" src="js/JSRootGeoBase.js"></script>-->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script type="text/javascript" src="main.bundle.js"></script>
@ -34,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

@ -2,9 +2,9 @@ package hep.dataforge.vis.spatial
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.DisplayObjectList
import hep.dataforge.vis.common.double
class Box(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta), Shape {
@ -14,7 +14,7 @@ class Box(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta), Shape
//TODO add helper for color configuration
override fun <T : Any> GeometryBuilder<T>.buildGeometry() {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val dx = xSize / 2
val dy = ySize / 2
val dz = zSize / 2
@ -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)
face4(node1, node4, node3, node2, Point3D(0, 0, -1))
face4(node1, node2, node6, node5, Point3D(0, -1, 0))
face4(node2, node3, node7, node6, Point3D(1, 0, 0))
face4(node4, node8, node7, node3, Point3D(0, 1, 0))
face4(node1, node5, node8, node4, Point3D(-1, 0, 0))
face4(node8, node5, node6, node7, Point3D(0, 0, 1))
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))
}
companion object {
@ -39,5 +39,5 @@ class Box(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta), Shape
}
}
fun DisplayObjectList.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) =
Box(this, meta).apply(action).also { addChild(it) }
fun DisplayGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) =
Box(this, meta).apply(action).also { add(it) }

View File

@ -0,0 +1,13 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject
class Composite(
parent: DisplayObject?,
val first: DisplayObject,
val second: DisplayObject,
meta: Meta = EmptyMeta
) : DisplayLeaf(parent,meta)

View File

@ -1,10 +1,9 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.*
import hep.dataforge.names.toName
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.DisplayObjectList
class Convex(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta) {
@ -14,15 +13,15 @@ class Convex(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta) {
const val TYPE = "geometry.3d.convex"
fun points(item: MetaItem<*>): List<Point3D> {
return item.node?.getAll("point".toName())?.map { (_, value) ->
Point3D(value.node["x"].number ?: 0, value.node["y"].number ?: 0, value.node["y"].number ?: 0)
return item.node?.getAll("point")?.map { (_, value) ->
Point3D.from(value.node?: error("Point definition is not a node"))
} ?: emptyList()
}
}
}
fun DisplayObjectList.convex(meta: Meta = EmptyMeta, action: ConvexBuilder.() -> Unit = {}) =
ConvexBuilder().apply(action).build(this, meta).also { addChild(it) }
fun DisplayGroup.convex(meta: Meta = EmptyMeta, action: ConvexBuilder.() -> Unit = {}) =
ConvexBuilder().apply(action).build(this, meta).also { add(it) }
class ConvexBuilder {
private val points = ArrayList<Point3D>()

View File

@ -1,9 +1,9 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.*
import hep.dataforge.vis.common.DisplayGroup
import hep.dataforge.vis.common.DisplayLeaf
import hep.dataforge.vis.common.DisplayObject
import hep.dataforge.vis.common.DisplayObjectList
import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.sin
@ -46,7 +46,7 @@ class Extruded(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta),
val shape
get() = properties.getAll("shape.point").map { (_, value) ->
Point2D(value.node["x"].number ?: 0, value.node["y"].number ?: 0)
Point2D.from(value.node ?: error("Point definition is not a node"))
}
fun shape(block: Shape2DBuilder.() -> Unit) {
@ -70,7 +70,7 @@ class Extruded(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta),
return layer
}
override fun <T : Any> GeometryBuilder<T>.buildGeometry() {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val shape: Shape2D = shape
if (shape.size < 3) error("Extruded shape requires more than points per layer")
@ -95,7 +95,7 @@ class Extruded(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta),
upperLayer = layers[i]
for (j in (0 until shape.size - 1)) {
//counter clockwise
face4(
geometryBuilder.face4(
lowerLayer[j],
lowerLayer[j + 1],
upperLayer[j + 1],
@ -104,7 +104,7 @@ class Extruded(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta),
}
// final face
face4(
geometryBuilder.face4(
lowerLayer[shape.size - 1],
lowerLayer[0],
upperLayer[0],
@ -119,5 +119,5 @@ class Extruded(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, meta),
}
}
fun DisplayObjectList.extrude(meta: Meta = EmptyMeta, action: Extruded.() -> Unit = {}) =
Extruded(this, meta).apply(action).also { addChild(it) }
fun DisplayGroup.extrude(meta: Meta = EmptyMeta, action: Extruded.() -> Unit = {}) =
Extruded(this, meta).apply(action).also { add(it) }

View File

@ -1,16 +1,19 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaRepr
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.*
import hep.dataforge.vis.common.DisplayObject
data class Point2D(val x: Number, val y: Number): MetaRepr{
data class Point2D(val x: Number, val y: Number) : MetaRepr {
override fun toMeta(): Meta = buildMeta {
"x" to x
"y" to y
}
companion object{
fun from(meta: Meta): Point2D{
return Point2D(meta["x"].number ?: 0, meta["y"].number ?: 0)
}
}
}
data class Point3D(val x: Number, val y: Number, val z: Number) : MetaRepr {
@ -19,12 +22,18 @@ data class Point3D(val x: Number, val y: Number, val z: Number) : MetaRepr {
"y" to y
"z" to z
}
companion object{
fun from(meta: Meta): Point3D{
return Point3D(meta["x"].number ?: 0, meta["y"].number ?: 0, meta["y"].number ?: 0)
}
}
}
/**
* @param T the type of resulting geometry
*/
interface GeometryBuilder<T: Any> {
interface GeometryBuilder<T : Any> {
/**
* Add a face to 3D model. If one of the vertices is not present in the current geometry model list of vertices,
* it is added automatically.
@ -35,7 +44,6 @@ interface GeometryBuilder<T: Any> {
fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D? = null, meta: Meta = EmptyMeta)
fun build(): T
}
fun GeometryBuilder<*>.face4(
@ -50,6 +58,6 @@ fun GeometryBuilder<*>.face4(
face(vertex1, vertex3, vertex4, normal, meta)
}
interface Shape: DisplayObject {
fun <T: Any> GeometryBuilder<T>.buildGeometry()
interface Shape : DisplayObject {
fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>)
}

View File

@ -4,15 +4,14 @@ 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.common.DisplayObjectList
import hep.dataforge.vis.common.getProperty
fun DisplayObjectList.group(meta: Meta = EmptyMeta, action: DisplayObjectList.() -> Unit = {}): DisplayGroup =
DisplayObjectList(this, meta).apply(action).also { addChild(it) }
fun DisplayGroup.group(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit = {}): DisplayGroup =
DisplayGroup(this, meta).apply(action).also { add(it) }
fun Output<DisplayObject>.render(meta: Meta = EmptyMeta, action: DisplayObjectList.() -> Unit) =
render(DisplayObjectList(null, EmptyMeta).apply(action), meta)
fun Output<DisplayObject>.render(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit) =
render(DisplayGroup(null, EmptyMeta).apply(action), meta)
//TODO replace properties by containers?
@ -133,11 +132,11 @@ var DisplayObject.scaleZ
}
fun DisplayObject.color(rgb: Int) {
this.properties["color"] = rgb
this.properties["material"] = rgb
}
fun DisplayObject.color(meta: Meta) {
this.properties["color"] = meta
this.properties["material"] = meta
}
fun DisplayObject.color(r: Int, g: Int, b: Int) = color(buildMeta {

View File

@ -4,14 +4,14 @@ import hep.dataforge.meta.get
import hep.dataforge.meta.getAll
import hep.dataforge.meta.node
import hep.dataforge.names.toName
import hep.dataforge.vis.common.DisplayObjectList
import hep.dataforge.vis.common.DisplayGroup
import kotlin.test.Test
import kotlin.test.assertEquals
class ConvexTest {
@Test
fun testConvexBuilder() {
val group = DisplayObjectList().apply {
val group = DisplayGroup().apply {
convex {
point(50, 50, -50)
point(50, -50, -50)
@ -24,7 +24,7 @@ class ConvexTest {
}
}
val convex = group.children.first() as Convex
val convex = group.first() as Convex
val pointsNode = convex.properties["points"].node