Added caching for geometries

This commit is contained in:
Alexander Nozik 2019-07-31 16:04:48 +03:00
parent 997a5a8e60
commit cf5a4fd7f6
29 changed files with 562 additions and 444 deletions

View File

@ -1,4 +1,4 @@
val dataforgeVersion by extra("0.1.3-dev-9")
val dataforgeVersion by extra("0.1.3-dev-10")
plugins{
val kotlinVersion = "1.3.50-eap-5"

View File

@ -1,91 +0,0 @@
package hep.dataforge.vis.common
import hep.dataforge.meta.Config
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.provider.Provider
import kotlin.collections.set
/**
* A display group which allows both named and unnamed children
*/
open class VisualGroup(
override val parent: VisualObject? = null, tagRefs: Array<out Meta> = emptyArray()
) : VisualObject, Iterable<VisualObject>, Provider {
private val namedChildren = HashMap<Name, VisualObject>()
private val unnamedChildren = ArrayList<VisualObject>()
override val defaultTarget: String get() = VisualObject.TYPE
override val config = Config()
override val properties: Laminate by lazy {
combineProperties(parent, config, tagRefs)
}
override fun iterator(): Iterator<VisualObject> = (namedChildren.values + unnamedChildren).iterator()
override fun provideTop(target: String): Map<Name, Any> {
return when (target) {
VisualObject.TYPE -> namedChildren
else -> emptyMap()
}
}
private data class Listener(val owner: Any?, val callback: (Name?, VisualObject?) -> Unit)
private val listeners = HashSet<Listener>()
/**
* Add listener for children change
*/
fun onChildrenChange(owner: Any?, action: (Name?, VisualObject?) -> Unit) {
listeners.add(Listener(owner, action))
}
/**
* Remove children change listener
*/
fun removeChildrenChangeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
/**
* Add named or unnamed child to the group. If key is [null] the child is considered unnamed. Both key and value are not
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
*/
operator fun set(name: Name?, child: VisualObject?) {
when {
name != null -> {
if (child == null) {
namedChildren.remove(name)
} else {
namedChildren[name] = child
}
listeners.forEach { it.callback(name, child) }
}
child != null -> unnamedChildren.add(child)
else -> error("Both key and child element are empty")
}
}
operator fun set(key: String?, child: VisualObject?) = set(key?.asName(), child)
/**
* Append unnamed child
*/
fun add(child: VisualObject) {
unnamedChildren.add(child)
listeners.forEach { it.callback(null, child) }
}
/**
* remove unnamed child
*/
fun remove(child: VisualObject) {
unnamedChildren.remove(child)
listeners.forEach { it.callback(null, null) }
}
}

View File

@ -0,0 +1,29 @@
package hep.dataforge.vis.common
import hep.dataforge.meta.*
import hep.dataforge.names.Name
/**
* Basic [VisualObject] leaf element
*/
open class VisualLeaf(
parent: VisualObject? = null,
meta: Meta = EmptyMeta
) : AbstractVisualObject(parent), Configurable {
val properties = Styled(meta)
override val config: Config = properties.style
override fun setProperty(name: Name, value: Any?) {
config[name] = value
}
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
properties[name] ?: parent?.getProperty(name, inherit)
} else {
properties[name]
}
}
}

View File

@ -2,10 +2,11 @@ package hep.dataforge.vis.common
import hep.dataforge.meta.*
import hep.dataforge.names.Name
import hep.dataforge.names.get
import hep.dataforge.provider.Provider
import hep.dataforge.provider.Type
import hep.dataforge.vis.common.VisualObject.Companion.META_KEY
import hep.dataforge.vis.common.VisualObject.Companion.TAGS_KEY
import hep.dataforge.vis.common.VisualObject.Companion.TYPE
import kotlin.collections.set
private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
@ -16,88 +17,190 @@ private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
@Type(TYPE)
interface VisualObject : MetaRepr, Configurable {
val type: String get() = this::class.simpleName ?: TYPE
/**
* The parent object of this one. If null, this one is a root.
*/
val parent: VisualObject?
/**
* Individual properties configurator
* Set property for this object
*/
override val config: Config
fun setProperty(name: Name, value: Any?)
/**
* All properties including inherited ones
* Get property including or excluding parent properties
*/
val properties: Laminate
fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
override fun toMeta(): Meta = buildMeta {
"type" to this::class
"properties" to properties
}
/**
* Manually trigger property changed event. If [name] is empty, notify that the whole object is changed
*/
fun propertyChanged(name: Name, before: MetaItem<*>? = null, after: MetaItem<*>? = null): Unit
/**
* Add listener triggering on property change
*/
fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit): Unit
/**
* Remove change listeners with given owner.
*/
fun removeChangeListener(owner: Any?)
companion object {
const val TYPE = "visual"
const val DEFAULT_TYPE = ""
//const val TYPE_KEY = "@type"
//const val CHILDREN_KEY = "@children"
const val META_KEY = "@meta"
const val TAGS_KEY = "@tags"
//const val META_KEY = "@meta"
//const val TAGS_KEY = "@tags"
}
}
/**
* A change listener for [VisualObject] configuration.
*/
fun VisualObject.onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
config.onChange(owner, action)
parent?.onChange(owner, action)
internal data class MetaListener(
val owner: Any? = null,
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
)
abstract class AbstractVisualObject(override val parent: VisualObject?) : VisualObject {
private val listeners = HashSet<MetaListener>()
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
for (l in listeners) {
l.action(name, before, after)
}
}
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
listeners.add(MetaListener(owner, action))
}
override fun removeChangeListener(owner: Any?) {
listeners.removeAll { it.owner == owner }
}
private var _config: Config? = null
override val config: Config get() = _config ?: Config().also { _config = it }
override fun setProperty(name: Name, value: Any?) {
config[name] = value
}
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
_config?.get(name) ?: parent?.getProperty(name, inherit)
} else {
_config?.get(name)
}
}
protected open fun MetaBuilder.updateMeta() {}
override fun toMeta(): Meta = buildMeta {
"type" to type
"properties" to _config
updateMeta()
}
}
open class VisualGroup<T : VisualObject>(parent: VisualObject?) : AbstractVisualObject(parent), Iterable<T>, Provider {
protected val namedChildren = HashMap<Name, T>()
protected val unnamedChildren = ArrayList<T>()
override val defaultTarget: String get() = VisualObject.TYPE
override fun iterator(): Iterator<T> = (namedChildren.values + unnamedChildren).iterator()
override fun provideTop(target: String): Map<Name, Any> {
return when (target) {
TYPE -> namedChildren
else -> emptyMap()
}
}
private data class Listener<T : VisualObject>(val owner: Any?, val callback: (Name?, T?) -> Unit)
private val listeners = HashSet<Listener<T>>()
/**
* Remove all meta listeners with matching owners
* Add listener for children change
*/
fun VisualObject.removeChangeListener(owner: Any?) {
config.removeListener(owner)
parent?.removeChangeListener(owner)
fun onChildrenChange(owner: Any?, action: (Name?, T?) -> Unit) {
listeners.add(Listener(owner, action))
}
/**
* Additional meta not relevant to display
* Remove children change listener
*/
val VisualObject.meta: Meta get() = config[META_KEY]?.node ?: EmptyMeta
val VisualObject.tags: List<String> get() = config[TAGS_KEY].stringList
fun removeChildrenChangeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
}
/**
* Basic [VisualObject] leaf element
* Add named or unnamed child to the group. If key is [null] the child is considered unnamed. Both key and value are not
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
*/
open class VisualLeaf(
final override val parent: VisualObject?,
tagRefs: Array<out Meta>
) : VisualObject {
override val config = Config()
override val properties: Laminate by lazy {
combineProperties(parent, config, tagRefs)
operator fun set(name: Name?, child: T?) {
when {
name != null -> {
if (child == null) {
namedChildren.remove(name)
} else {
namedChildren[name] = child
}
listeners.forEach { it.callback(name, child) }
}
child != null -> unnamedChildren.add(child)
else -> error("Both key and child element are empty")
}
}
internal fun combineProperties(parent: VisualObject?, config: Config, tagRefs: Array<out Meta>): Laminate {
val list = ArrayList<Meta>(tagRefs.size + 2)
list += config
list.addAll(tagRefs)
parent?.properties?.let { list.add(it) }
return Laminate(list)
operator fun set(key: String?, child: T?) = set(key?.asName(), child)
/**
* Get named child by name
*/
operator fun get(name: Name): T? = namedChildren[name]
/**
* Get named child by string
*/
operator fun get(key: String): T? = namedChildren[key]
/**
* Get an unnamed child
*/
operator fun get(index: Int): T? = unnamedChildren[index]
/**
* Append unnamed child
*/
fun add(child: T) {
unnamedChildren.add(child)
listeners.forEach { it.callback(null, child) }
}
///**
// * A group that could contain both named and unnamed children. Unnamed children could be accessed only via
// */
//interface VisualGroup : DisplayObject, Iterable<DisplayObject>, Provider {
// override val defaultTarget: String get() = DisplayObject.TARGET
//
// val children
//}
/**
* remove unnamed child
*/
fun remove(child: VisualObject) {
unnamedChildren.remove(child)
listeners.forEach { it.callback(null, null) }
}
protected fun MetaBuilder.updateChildren() {
//adding unnamed children
"children" to unnamedChildren.map { it.toMeta() }
//adding named children
namedChildren.forEach {
"children[${it.key}" to it.value.toMeta()
}
}
override fun MetaBuilder.updateMeta() {
updateChildren()
}
}

View File

@ -23,7 +23,7 @@ class DisplayObjectDelegate(
override fun getValue(thisRef: VisualObject, property: KProperty<*>): MetaItem<*>? {
val name = key ?: property.name.asName()
return if (inherited) {
thisRef.properties[name]
thisRef.getProperty(name)
} else {
thisRef.config[name]
} ?: default
@ -48,7 +48,7 @@ class DisplayObjectDelegateWrapper<T>(
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
val name = key ?: property.name.asName()
return if (inherited) {
read(thisRef.properties[name])
read(thisRef.getProperty(name))
} else {
read(thisRef.config[name])
} ?: default

View File

@ -3,7 +3,7 @@ package hep.dataforge.vis.spatial
import hep.dataforge.context.Context
import hep.dataforge.meta.Meta
import hep.dataforge.output.Output
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualNode
import hep.dataforge.vis.common.VisualObject
import javafx.scene.Group
import javafx.scene.Node
@ -27,7 +27,7 @@ class FX3DOutput(override val context: Context) : Output<VisualObject> {
org.fxyz3d.geometry.Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f)
}
return when (obj) {
is VisualGroup -> Group(obj.map { buildNode(it) }).apply {
is VisualNode -> Group(obj.map { buildNode(it) }).apply {
this.translateXProperty().bind(x)
this.translateYProperty().bind(y)
this.translateZProperty().bind(z)

View File

@ -2,7 +2,6 @@ package hep.dataforge.vis.spatial
import hep.dataforge.context.Global
import hep.dataforge.meta.number
import hep.dataforge.vis.common.VisualGroup
import javafx.scene.Parent
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
@ -21,7 +20,7 @@ class RendererDemoView : View() {
center = renderer.canvas.root
}
lateinit var group: VisualGroup
lateinit var group: VisualGroup3D
init {

View File

@ -0,0 +1,60 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.builder
import hep.dataforge.vis.spatial.VisualGroup3D
import scientifik.gdml.GDML
import scientifik.gdml.GDMLGroup
import scientifik.gdml.GDMLMaterial
import scientifik.gdml.GDMLSolid
import kotlin.random.Random
class GDMLTransformer(val root: GDML) {
private val materialCache = HashMap<GDMLMaterial, Meta>()
private val random = Random(111)
/**
* A special group for local templates
*/
val templates by lazy { VisualGroup3D() }
var lUnit: LUnit = LUnit.MM
var resolveColor: ColorResolver = { material, _ ->
val materialColor = materialCache.getOrPut(material) {
buildMeta {
"color" to random.nextInt(0, Int.MAX_VALUE)
}
}
if (this?.physVolumes?.isEmpty() != false) {
materialColor
} else {
materialColor.builder().apply { "opacity" to 0.5 }
}
}
var acceptSolid: (GDMLSolid) -> Boolean = { true }
var acceptGroup: (GDMLGroup) -> Boolean = { true }
fun printStatistics() {
println("Solids:")
solidCounter.entries.sortedByDescending { it.value }.forEach {
println("\t$it")
}
println(println("Solids total: ${solidCounter.values.sum()}"))
}
private val solidCounter = HashMap<String, Int>()
internal fun solidAdded(solid: GDMLSolid) {
solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
}
var onFinish: GDMLTransformer.() -> Unit = {}
internal fun finished() {
onFinish(this)
}
}

View File

@ -1,58 +1,10 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.Meta
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.builder
import hep.dataforge.vis.spatial.*
import scientifik.gdml.*
import kotlin.math.cos
import kotlin.math.sin
import kotlin.random.Random
class GDMLTransformer(val root: GDML) {
private val cache = HashMap<GDMLMaterial, Meta>()
private val random = Random(111)
var lUnit: LUnit = LUnit.MM
var resolveColor: ColorResolver = { material, _ ->
val materialColor = cache.getOrPut(material) {
buildMeta {
"color" to random.nextInt(0, Int.MAX_VALUE)
}
}
if (this?.physVolumes?.isEmpty() != false) {
materialColor
} else {
materialColor.builder().apply { "opacity" to 0.5 }
}
}
var acceptSolid: (GDMLSolid) -> Boolean = { true }
var acceptGroup: (GDMLGroup) -> Boolean = { true }
fun printStatistics() {
println("Solids:")
solidCounter.entries.sortedByDescending { it.value }.forEach {
println("\t$it")
}
println(println("Solids total: ${solidCounter.values.sum()}"))
}
private val solidCounter = HashMap<String, Int>()
internal fun solidAdded(solid: GDMLSolid) {
solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
}
var onFinish: GDMLTransformer.() -> Unit = {}
internal fun finished(){
onFinish(this)
}
}
private fun VisualObject3D.withPosition(
@ -82,7 +34,6 @@ private fun VisualObject3D.withPosition(
private inline operator fun Number.times(d: Double) = toDouble() * d
private inline operator fun Number.times(f: Float) = toFloat() * f
private fun VisualGroup3D.addSolid(
context: GDMLTransformer,
solid: GDMLSolid,
@ -238,9 +189,12 @@ private fun volume(
val material = group.materialref.resolve(context.root) ?: GDMLElement(group.materialref.ref)
if (context.acceptSolid(solid)) {
addSolid(context, solid, solid.name) {
val cachedSolid = context.templates[solid.name]
?: context.templates.addSolid(context, solid, solid.name) {
this.material = context.resolveColor(group, material, solid)
}
val wrapper = Proxy3D(this,cachedSolid)
add(wrapper)
}
when (val vol = group.placement) {

View File

@ -90,11 +90,11 @@ private class GDMLDemoApp : ApplicationBase() {
launch { message("Converting GDML into DF-VIS format") }
val visual = gdml.toVisual {
lUnit = LUnit.CM
acceptSolid = { solid ->
!solid.name.startsWith("ecal")
&& !solid.name.startsWith("V")
&& !solid.name.startsWith("U")
}
// acceptSolid = { solid ->
// !solid.name.startsWith("ecal")
// && !solid.name.startsWith("V")
// && !solid.name.startsWith("U")
// }
}
launch { message("Rendering") }
val output = three.output(canvas)

View File

@ -18,7 +18,7 @@ fun main() {
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
xml.toVisual {
lUnit = LUnit.CM
acceptSolid = { solid -> !solid.name.startsWith("ecal") && !solid.name.startsWith("V") }
//acceptSolid = { solid -> !solid.name.startsWith("ecal") && !solid.name.startsWith("V") }
onFinish = { printStatistics() }
}
readLine()

View File

@ -40,6 +40,8 @@ fun MetaItem<*>.color(): Color {
}
}
private val materialCache = HashMap<Meta, Material>()
/**
* Infer Three material based on meta item
*/
@ -47,7 +49,8 @@ fun Meta?.jsMaterial(): Material {
return if (this == null) {
Materials.DEFAULT
} else
//TODO add more oprions for material
//TODO add more options for material
return materialCache.getOrPut(this) {
MeshBasicMaterial().apply {
color = get("color")?.color() ?: Materials.DEFAULT_COLOR
opacity = get("opacity")?.double ?: 1.0
@ -55,6 +58,6 @@ fun Meta?.jsMaterial(): Material {
//node["specularColor"]?.let { specular = it.color() }
side = 2
}
}
}

View File

@ -18,7 +18,7 @@ class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory<Composite
second.updateMatrix()
val firstCSG = CSG.fromMesh(first)
val secondCSG = CSG.fromMesh(second)
val resultCSG = when (obj.type) {
val resultCSG = when (obj.compositeType) {
CompositeType.UNION -> firstCSG.union(secondCSG)
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG)
CompositeType.SUBTRACT -> firstCSG.subtract(secondCSG)

View File

@ -0,0 +1,11 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Convex
import info.laht.threekt.external.geometries.ConvexBufferGeometry
object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
val vectors = obj.points.map { it.asVector() }.toTypedArray()
return ConvexBufferGeometry(vectors)
}
}

View File

@ -1,18 +1,16 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean
import hep.dataforge.meta.get
import hep.dataforge.meta.int
import hep.dataforge.meta.node
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
import hep.dataforge.provider.Type
import hep.dataforge.vis.common.onChange
import hep.dataforge.vis.common.asName
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.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
@ -31,55 +29,6 @@ interface ThreeFactory<T : VisualObject3D> {
companion object {
const val TYPE = "threeFactory"
fun <T : VisualObject3D> 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.jsMaterial())
//inherited edges definition, enabled by default
if (obj.properties["edges.enabled"].boolean != false) {
val material = obj.properties["edges.material"].node?.jsMaterial() ?: Materials.DEFAULT
mesh.add(LineSegments(EdgesGeometry(mesh.geometry as BufferGeometry), material))
}
//inherited wireframe definition, disabled by default
if (obj.properties["wireframe.enabled"].boolean == true) {
val material = obj.properties["wireframe.material"].node?.jsMaterial() ?: Materials.DEFAULT
mesh.add(LineSegments(WireframeGeometry(mesh.geometry as BufferGeometry), material))
}
//set position for mesh
mesh.updatePosition(obj)
obj.config["layer"].int?.let {
mesh.layers.set(it)
}
//add listener to object properties
obj.onChange(this) { name, _, _ ->
if (name.startsWith(VisualObject3D.materialKey)) {
//updated material
mesh.material = obj.material.jsMaterial()
} else if (
name.startsWith(VisualObject3D.position) ||
name.startsWith(VisualObject3D.rotation) ||
name.startsWith(VisualObject3D.scale) ||
name == VisualObject3D.visibleKey
) {
//update position of mesh using this object
mesh.updatePosition(obj)
} else {
//full update
mesh.geometry = geometryBuilder(obj)
mesh.material = obj.material.jsMaterial()
}
}
return mesh
}
}
}
@ -90,7 +39,30 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) {
position.set(obj.x, obj.y, obj.z)
setRotationFromEuler(obj.euler)
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
visible = obj.visible ?: true
updateMatrix()
}
internal fun <T : VisualObject3D> Mesh.updateFrom(obj: T) {
matrixAutoUpdate = false
//inherited edges definition, enabled by default
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
val material = obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
add(LineSegments(EdgesGeometry(geometry as BufferGeometry), material))
}
//inherited wireframe definition, disabled by default
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
val material = obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
add(LineSegments(WireframeGeometry(geometry as BufferGeometry), material))
}
//set position for mesh
updatePosition(obj)
obj.getProperty(MeshThreeFactory.LAYER_KEY).int?.let {
layers.set(it)
}
}
/**
@ -118,6 +90,51 @@ abstract class MeshThreeFactory<T : VisualObject3D>(override val type: KClass<ou
//create mesh from geometry
return buildMesh<T>(obj) { buildGeometry(it) }
}
companion object {
val EDGES_KEY = "edges".asName()
val WIREFRAME_KEY = "wireframe".asName()
val ENABLED_KEY = "enabled".asName()
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
val EDGES_MATERIAL_KEY = EDGES_KEY + VisualObject3D.MATERIAL_KEY
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + VisualObject3D.MATERIAL_KEY
val LAYER_KEY = "layer".asName()
fun <T : VisualObject3D> buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh {
//TODO add caching for geometries using templates
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.jsMaterial())
mesh.updateFrom(obj)
//add listener to object properties
obj.onPropertyChange(this) { name, _, _ ->
if (name.startsWith(VisualObject3D.MATERIAL_KEY)) {
//updated material
mesh.material = obj.material.jsMaterial()
} else if (
name.startsWith(VisualObject3D.position) ||
name.startsWith(VisualObject3D.rotation) ||
name.startsWith(VisualObject3D.scale)
) {
//update position of mesh using this object
mesh.updatePosition(obj)
} else if (name == VisualObject3D.VISIBLE_KEY) {
obj.visible = obj.visible ?: true
} else {
//full update
mesh.geometry = geometryBuilder(obj)
mesh.material = obj.material.jsMaterial()
}
}
return mesh
}
}
}
/**
@ -130,11 +147,3 @@ object ThreeShapeFactory : MeshThreeFactory<Shape>(Shape::class) {
}
}
}
//FIXME not functional yet
object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
val vectors = obj.points.map { it.asVector() }.toTypedArray()
return ConvexBufferGeometry(vectors)
}
}

View File

@ -20,6 +20,7 @@ class ThreePlugin : AbstractPlugin() {
private val objectFactories = HashMap<KClass<out VisualObject3D>, ThreeFactory<*>>()
private val compositeFactory = ThreeCompositeFactory(this)
private val proxyFactory = ThreeProxyFactory(this)
init {
//Add specialized factories here
@ -38,7 +39,6 @@ class ThreePlugin : AbstractPlugin() {
return when (obj) {
is VisualGroup3D -> Group(obj.mapNotNull {
try {
it as VisualObject3D
buildObject3D(it)
} catch (ex: Throwable) {
console.error(ex)
@ -49,6 +49,7 @@ class ThreePlugin : AbstractPlugin() {
updatePosition(obj)
}
is Composite -> compositeFactory(obj)
is Proxy3D -> proxyFactory(obj)
else -> {
//find specialized factory for this type if it is present
val factory = findObjectFactory(obj::class)

View File

@ -0,0 +1,25 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Proxy3D
import hep.dataforge.vis.spatial.VisualObject3D
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh
class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy3D> {
private val cache = HashMap<VisualObject3D, Mesh>()
override val type = Proxy3D::class
override fun invoke(obj: Proxy3D): Object3D {
val templateMesh = cache.getOrPut(obj.template) {
three.buildObject3D(obj.template) as Mesh
}
val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material)
//val mesh = templateMesh.clone()
mesh.updatePosition(obj)
return mesh
}
}

View File

@ -1,11 +1,9 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize: Number, meta: Array<out Meta>) :
VisualLeaf3D(parent, meta), Shape {
class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize: Number) :
VisualLeaf3D(parent), Shape {
//TODO add helper for color configuration
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
@ -34,14 +32,10 @@ class Box(parent: VisualObject?, val xSize: Number, val ySize: Number, val zSize
}
}
//fun VisualGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) =
// Box(this, meta).apply(action).also { add(it) }
inline fun VisualGroup.box(
inline fun VisualGroup3D.box(
xSize: Number,
ySize: Number,
zSize: Number,
name: String? = null,
vararg meta: Meta,
action: Box.() -> Unit = {}
) = Box(this, xSize, ySize, zSize, meta).apply(action).also { set(name, it) }
) = Box(this, xSize, ySize, zSize).apply(action).also { set(name, it) }

View File

@ -1,9 +1,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.Meta
import hep.dataforge.meta.isEmpty
import hep.dataforge.meta.update
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
enum class CompositeType {
@ -16,20 +14,18 @@ open class Composite(
parent: VisualObject?,
val first: VisualObject3D,
val second: VisualObject3D,
val type: CompositeType = CompositeType.UNION,
meta: Array<out Meta>
) : VisualLeaf3D(parent, meta)
val compositeType: CompositeType = CompositeType.UNION
) : VisualLeaf3D(parent)
fun VisualGroup.composite(
fun VisualGroup3D.composite(
type: CompositeType,
name: String? = null,
vararg meta: Meta,
builder: VisualGroup3D.() -> Unit
): Composite {
val group = VisualGroup3D().apply(builder)
val children = group.filterIsInstance<VisualObject3D>()
if (children.size != 2) error("Composite requires exactly two children")
return Composite(this, children[0], children[1], type, meta).also {
return Composite(this, children[0], children[1], type).also {
if (!group.config.isEmpty()) {
it.config.update(group.config)
}
@ -41,11 +37,11 @@ fun VisualGroup.composite(
}
}
fun VisualGroup3D.union(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) =
composite(CompositeType.UNION, name, *meta, builder = builder)
fun VisualGroup3D.union(name: String? = null, builder: VisualGroup3D.() -> Unit) =
composite(CompositeType.UNION, name, builder = builder)
fun VisualGroup3D.subtract(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) =
composite(CompositeType.SUBTRACT, name, *meta, builder = builder)
fun VisualGroup3D.subtract(name: String? = null, builder: VisualGroup3D.() -> Unit) =
composite(CompositeType.SUBTRACT, name, builder = builder)
fun VisualGroup3D.intersect(name: String? = null, vararg meta: Meta, builder: VisualGroup3D.() -> Unit) =
composite(CompositeType.INTERSECT, name, *meta, builder = builder)
fun VisualGroup3D.intersect(name: String? = null, builder: VisualGroup3D.() -> Unit) =
composite(CompositeType.INTERSECT, name, builder = builder)

View File

@ -1,10 +1,8 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
class Convex(parent: VisualObject?, val points: List<Point3D>, meta: Array<out Meta>) : VisualLeaf3D(parent, meta) {
class Convex(parent: VisualObject?, val points: List<Point3D>) : VisualLeaf3D(parent) {
companion object {
@ -12,8 +10,8 @@ class Convex(parent: VisualObject?, val points: List<Point3D>, meta: Array<out M
}
}
fun VisualGroup.convex(vararg meta: Meta, action: ConvexBuilder.() -> Unit = {}) =
ConvexBuilder().apply(action).build(this, meta).also { add(it) }
fun VisualGroup3D.convex(action: ConvexBuilder.() -> Unit = {}) =
ConvexBuilder().apply(action).build(this).also { add(it) }
class ConvexBuilder {
private val points = ArrayList<Point3D>()
@ -22,7 +20,7 @@ class ConvexBuilder {
points.add(Point3D(x, y, z))
}
fun build(parent: VisualObject?, meta: Array<out Meta>): Convex {
return Convex(parent, points, meta)
fun build(parent: VisualObject?): Convex {
return Convex(parent, points)
}
}

View File

@ -1,7 +1,5 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.number
import kotlin.math.PI
@ -9,21 +7,19 @@ import kotlin.math.PI
/**
* A cylinder or cut cone segment
*/
class Cylinder(parent: VisualObject?, var radius: Number, var height: Number, meta: Array<out Meta>) :
VisualLeaf3D(parent, meta) {
class Cylinder(parent: VisualObject?, var radius: Number, var height: Number) : VisualLeaf3D(parent) {
var upperRadius by number(radius)
var startAngle by number(0.0)
var angle by number(2 * PI)
}
fun VisualGroup.cylinder(
fun VisualGroup3D.cylinder(
r: Number,
height: Number,
name: String? = null,
vararg meta: Meta,
block: Cylinder.() -> Unit = {}
): Cylinder {
val cylinder = Cylinder(this, r, height, meta)
val cylinder = Cylinder(this, r, height)
cylinder.apply(block)
return cylinder.also { set(name, it) }
}

View File

@ -1,7 +1,5 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import kotlin.math.PI
import kotlin.math.cos
@ -32,7 +30,7 @@ fun Shape2DBuilder.polygon(vertices: Int, radius: Number) {
data class Layer(var x: Number, var y: Number, var z: Number, var scale: Number)
class Extruded(parent: VisualObject?, meta: Array<out Meta>) : VisualLeaf3D(parent, meta), Shape {
class Extruded(parent: VisualObject?) : VisualLeaf3D(parent), Shape {
var shape: List<Point2D> = ArrayList()
@ -112,5 +110,5 @@ class Extruded(parent: VisualObject?, meta: Array<out Meta>) : VisualLeaf3D(pare
}
}
fun VisualGroup.extrude(name: String? = null, vararg meta: Meta, action: Extruded.() -> Unit = {}) =
Extruded(this, meta).apply(action).also { set(name, it) }
fun VisualGroup3D.extrude(name: String? = null, action: Extruded.() -> Unit = {}) =
Extruded(this).apply(action).also { set(name, it) }

View File

@ -0,0 +1,34 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.VisualObject
/**
* A proxy [VisualObject3D] to reuse a [template] object
*/
class Proxy3D(parent: VisualObject?, val template: VisualObject3D) : AbstractVisualObject(parent), VisualObject3D {
override var position: Value3 = Value3()
override var rotation: Value3 = Value3()
override var scale: Value3 = Value3(1f, 1f, 1f)
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
super.getProperty(name, false) ?: template.getProperty(name, false) ?: parent?.getProperty(name, inherit)
} else {
super.getProperty(name, false) ?: template.getProperty(name, false)
}
}
override fun MetaBuilder.updateMeta() {
updatePosition()
}
}
inline fun VisualGroup3D.proxy(
template: VisualObject3D,
name: String? = null,
action: Proxy3D.() -> Unit = {}
) = Proxy3D(this, template).apply(action).also { set(name, it) }

View File

@ -1,26 +1,23 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.number
import kotlin.math.PI
class Sphere(parent: VisualObject?, var radius: Number, meta: Array<out Meta>) : VisualLeaf3D(parent, meta) {
class Sphere(parent: VisualObject?, var radius: Number) : VisualLeaf3D(parent) {
var phiStart by number(0.0)
var phi by number(2 * PI)
var thetaStart by number(0.0)
var theta by number(PI)
}
fun VisualGroup.sphere(
fun VisualGroup3D.sphere(
radius: Number,
phi: Number = 2 * PI,
theta: Number = PI,
name: String? = null,
vararg meta: Meta,
action: Sphere.() -> Unit = {}
) = Sphere(this, radius, meta).apply(action).apply {
) = Sphere(this, radius).apply(action).apply {
this.phi = phi.toDouble()
this.theta = theta.toDouble()
}.also { set(name, it) }

View File

@ -1,16 +1,15 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.*
import hep.dataforge.names.Name
import hep.dataforge.names.plus
import hep.dataforge.output.Output
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualLeaf
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.asName
import hep.dataforge.vis.spatial.VisualObject3D.Companion.detailKey
import hep.dataforge.vis.spatial.VisualObject3D.Companion.materialKey
import hep.dataforge.vis.spatial.VisualObject3D.Companion.visibleKey
import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.MATERIAL_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
data class Value3(var x: Float = 0f, var y: Float = 0f, var z: Float = 0f)
@ -19,13 +18,22 @@ interface VisualObject3D : VisualObject {
var rotation: Value3
var scale: Value3
fun setProperty(name: Name, value: Any?)
fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
fun MetaBuilder.updatePosition() {
xPos to position.x
yPos to position.y
zPos to position.z
xRotation to rotation.x
yRotation to rotation.y
zRotation to rotation.z
xScale to scale.x
yScale to scale.y
zScale to scale.z
}
companion object {
val materialKey = "material".asName()
val visibleKey = "visible".asName()
val detailKey = "detail".asName()
val MATERIAL_KEY = "material".asName()
val VISIBLE_KEY = "visible".asName()
val DETAIL_KEY = "detail".asName()
val x = "x".asName()
val y = "y".asName()
@ -53,64 +61,32 @@ interface VisualObject3D : VisualObject {
}
}
open class VisualLeaf3D(parent: VisualObject?, tagRefs: Array<out Meta>) : VisualLeaf(parent, tagRefs), VisualObject3D {
abstract class VisualLeaf3D(parent: VisualObject?) : AbstractVisualObject(parent), VisualObject3D, Configurable {
override var position: Value3 = Value3()
override var rotation: Value3 = Value3()
override var scale: Value3 = Value3(1f, 1f, 1f)
private var _config: Config? = null
override val config: Config get() = _config ?: Config().also { _config = it }
override fun setProperty(name: Name, value: Any?) {
config[name] = value
}
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
config[name] ?: (parent as? VisualObject3D)?.getProperty(name, inherit) ?: parent?.properties[name]
} else {
_config?.get(name)
}
}
}
class VisualGroup3D(
parent: VisualObject? = null,
tagRefs: Array<out Meta> = emptyArray()
) : VisualGroup(parent, tagRefs), VisualObject3D {
class VisualGroup3D(parent: VisualObject? = null) : VisualGroup<VisualObject3D>(parent), VisualObject3D, Configurable {
override var position: Value3 = Value3()
override var rotation: Value3 = Value3()
override var scale: Value3 = Value3(1f, 1f, 1f)
private var _config: Config? = null
override val config: Config get() = _config ?: Config().also { _config = it }
override fun setProperty(name: Name, value: Any?) {
config[name] = value
}
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
config[name] ?: (parent as? VisualObject3D)?.getProperty(name, inherit) ?: parent?.properties[name]
} else {
_config?.get(name)
}
override fun MetaBuilder.updateMeta() {
updatePosition()
updateChildren()
}
}
fun VisualGroup3D.group(key: String? = null, action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
VisualGroup3D(this).apply(action).also { set(key, it) }
fun VisualGroup.group(key: String? = null, vararg meta: Meta, action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
VisualGroup3D(this, meta).apply(action).also { set(key, it) }
fun Output<VisualObject>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
fun Output<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
render(VisualGroup3D().apply(action), meta)
// Common properties
enum class RotationOrder {
XYZ,
YZX,
@ -132,16 +108,16 @@ var VisualObject3D.rotationOrder: RotationOrder
* Preferred number of polygons for displaying the object. If not defined, uses shape or renderer default
*/
var VisualObject3D.detail: Int?
get() = getProperty(detailKey).int
set(value) = setProperty(detailKey, value)
get() = getProperty(DETAIL_KEY).int
set(value) = setProperty(DETAIL_KEY, value)
var VisualObject3D.material: Meta?
get() = getProperty(materialKey).node
set(value) = setProperty(materialKey, value)
get() = getProperty(MATERIAL_KEY).node
set(value) = setProperty(MATERIAL_KEY, value)
var VisualObject3D.visible: Boolean?
get() = getProperty(visibleKey).boolean
set(value) = setProperty(visibleKey, value)
get() = getProperty(VISIBLE_KEY).boolean
set(value) = setProperty(VISIBLE_KEY, value)
fun VisualObject3D.color(rgb: Int) {
material = buildMeta { "color" to rgb }
@ -157,11 +133,66 @@ fun VisualObject3D.color(r: Int, g: Int, b: Int) = material {
"blue" to b
}
object World {
const val CAMERA_INITIAL_DISTANCE = -500.0
const val CAMERA_INITIAL_X_ANGLE = -50.0
const val CAMERA_INITIAL_Y_ANGLE = 0.0
const val CAMERA_INITIAL_Z_ANGLE = -210.0
const val CAMERA_NEAR_CLIP = 0.1
const val CAMERA_FAR_CLIP = 10000.0
var VisualObject3D.x: Number
get() = position.x
set(value) {
position.x = value.toFloat()
propertyChanged(VisualObject3D.xPos)
}
var VisualObject3D.y: Number
get() = position.y
set(value) {
position.y = value.toFloat()
propertyChanged(VisualObject3D.yPos)
}
var VisualObject3D.z: Number
get() = position.z
set(value) {
position.z = value.toFloat()
propertyChanged(VisualObject3D.zPos)
}
var VisualObject3D.rotationX: Number
get() = rotation.x
set(value) {
rotation.x = value.toFloat()
propertyChanged(VisualObject3D.xRotation)
}
var VisualObject3D.rotationY: Number
get() = rotation.y
set(value) {
rotation.y = value.toFloat()
propertyChanged(VisualObject3D.xRotation)
}
var VisualObject3D.rotationZ: Number
get() = rotation.z
set(value) {
rotation.z = value.toFloat()
propertyChanged(VisualObject3D.zRotation)
}
var VisualObject3D.scaleX: Number
get() = scale.x
set(value) {
scale.x = value.toFloat()
propertyChanged(VisualObject3D.xScale)
}
var VisualObject3D.scaleY: Number
get() = scale.y
set(value) {
scale.y = value.toFloat()
propertyChanged(VisualObject3D.yScale)
}
var VisualObject3D.scaleZ: Number
get() = scale.z
set(value) {
scale.z = value.toFloat()
propertyChanged(VisualObject3D.zScale)
}

View File

@ -0,0 +1,10 @@
package hep.dataforge.vis.spatial
object World {
const val CAMERA_INITIAL_DISTANCE = -500.0
const val CAMERA_INITIAL_X_ANGLE = -50.0
const val CAMERA_INITIAL_Y_ANGLE = 0.0
const val CAMERA_INITIAL_Z_ANGLE = -210.0
const val CAMERA_NEAR_CLIP = 0.1
const val CAMERA_FAR_CLIP = 10000.0
}

View File

@ -1,37 +0,0 @@
package hep.dataforge.vis.spatial
var VisualObject3D.x: Number
get() = position.x
set(value) {position.x = value.toFloat()}
var VisualObject3D.y: Number
get() = position.y
set(value) {position.y = value.toFloat()}
var VisualObject3D.z: Number
get() = position.z
set(value) {position.z = value.toFloat()}
var VisualObject3D.rotationX: Number
get() = rotation.x
set(value) {rotation.x = value.toFloat()}
var VisualObject3D.rotationY: Number
get() = rotation.y
set(value) {rotation.y = value.toFloat()}
var VisualObject3D.rotationZ: Number
get() = rotation.z
set(value) {rotation.z = value.toFloat()}
var VisualObject3D.scaleX: Number
get() = scale.x
set(value) {scale.x = value.toFloat()}
var VisualObject3D.scaleY: Number
get() = scale.y
set(value) {scale.y = value.toFloat()}
var VisualObject3D.scaleZ: Number
get() = scale.z
set(value) {scale.z = value.toFloat()}

View File

@ -4,14 +4,13 @@ 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.VisualGroup
import kotlin.test.Test
import kotlin.test.assertEquals
class ConvexTest {
@Test
fun testConvexBuilder() {
val group = VisualGroup().apply {
val group = VisualNode().apply {
convex {
point(50, 50, -50)
point(50, -50, -50)

View File

@ -1,7 +1,6 @@
package hep.dataforge.vis.spatial
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.color
import kotlin.math.PI
import kotlin.test.Test
@ -10,7 +9,7 @@ import kotlin.test.assertEquals
class GroupTest {
@Test
fun testGroupWithComposite(){
val group = VisualGroup().apply{
val group = VisualNode().apply{
union {
box(100, 100, 100) {
z = 100