Lines implemented

This commit is contained in:
Alexander Nozik 2019-10-09 20:25:52 +03:00
parent 0fbad16be6
commit ce80ffe91b
9 changed files with 247 additions and 121 deletions

View File

@ -4,6 +4,7 @@ import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
@ -35,81 +36,79 @@ class VisualObjectDelegate(
} }
class VisualObjectDelegateWrapper<T>( class VisualObjectDelegateWrapper<T>(
val obj: VisualObject,
val key: Name?, val key: Name?,
val default: T, val default: T,
val inherited: Boolean, val inherited: Boolean,
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) }, val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
val read: (MetaItem<*>?) -> T? val read: (MetaItem<*>?) -> T?
) : ReadWriteProperty<VisualObject, T> { ) : ReadWriteProperty<Any?, T> {
//private var cachedName: Name? = null //private var cachedName: Name? = null
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T { override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
return if (inherited) { return read(obj.getProperty(name,inherited))?:default
read(thisRef.getProperty(name))
} else {
read(thisRef.config[name])
} ?: default
} }
override fun setValue(thisRef: VisualObject, property: KProperty<*>, value: T) { override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
val name = key ?: property.name.asName() val name = key ?: property.name.asName()
thisRef.config[name] = value obj.config[name] = value
} }
} }
fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.value } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.value }
fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) = fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
fun VisualObject.node(key: String? = null, inherited: Boolean = true) = fun VisualObject.node(key: String? = null, inherited: Boolean = true) =
VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it.node } VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it.node }
fun VisualObject.item(key: String? = null, inherited: Boolean = true) = fun VisualObject.item(key: String? = null, inherited: Boolean = true) =
VisualObjectDelegateWrapper(key?.asName(), null, inherited) { it } VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it }
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) } //fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
@JvmName("safeString") @JvmName("safeString")
fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) = fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.string } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
@JvmName("safeBoolean") @JvmName("safeBoolean")
fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) = fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
@JvmName("safeNumber") @JvmName("safeNumber")
fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) = fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.number } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
@JvmName("safeDouble") @JvmName("safeDouble")
fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) = fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.double } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
@JvmName("safeInt") @JvmName("safeInt")
fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) = fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int } VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
inline fun <reified E : Enum<E>> VisualObject.enum(default: E, key: String? = null, inherited: Boolean = false) = inline fun <reified E : Enum<E>> VisualObject.enum(default: E, key: String? = null, inherited: Boolean = false) =
VisualObjectDelegateWrapper( VisualObjectDelegateWrapper(
this,
key?.let { NameToken(it).asName() }, key?.let { NameToken(it).asName() },
default, default,
inherited inherited
@ -121,11 +120,11 @@ fun <T> VisualObject.merge(
key: String? = null, key: String? = null,
transformer: (Sequence<MetaItem<*>>) -> T transformer: (Sequence<MetaItem<*>>) -> T
): ReadOnlyProperty<VisualObject, T> { ): ReadOnlyProperty<VisualObject, T> {
return object : ReadOnlyProperty<VisualObject, T> { return object : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T { override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key?.asName() ?: property.name.asName() val name = key?.toName() ?: property.name.asName()
val sequence = sequence<MetaItem<*>> { val sequence = sequence<MetaItem<*>> {
var thisObj: VisualObject? = thisRef var thisObj: VisualObject? = this@merge
while (thisObj != null) { while (thisObj != null) {
thisObj.config[name]?.let { yield(it) } thisObj.config[name]?.let { yield(it) }
thisObj = thisObj.parent thisObj = thisObj.parent

View File

@ -0,0 +1,26 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.number
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@Serializable
class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
override var position: Point3D? = null
override var rotation: Point3D? = null
override var scale: Point3D? = null
//var lineType by string()
var thickness by number(1.0, key = "material.thickness")
}
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =
PolyLine(points.toList()).apply(action).also { set(name, it) }

View File

@ -4,6 +4,7 @@ import hep.dataforge.meta.*
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.Material3D import hep.dataforge.vis.spatial.Material3D
import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.materials.Material import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.materials.MeshPhongMaterial import info.laht.threekt.materials.MeshPhongMaterial
@ -13,8 +14,37 @@ import info.laht.threekt.math.Color
object Materials { object Materials {
val DEFAULT_COLOR = Color(Colors.darkgreen) val DEFAULT_COLOR = Color(Colors.darkgreen)
val DEFAULT = MeshPhongMaterial().apply { val DEFAULT = MeshPhongMaterial().apply {
this.color.set(DEFAULT_COLOR) color.set(DEFAULT_COLOR)
} }
val DEFAULT_LINE_COLOR = Color(Colors.black)
val DEFAULT_LINE = LineBasicMaterial().apply {
color.set(DEFAULT_LINE_COLOR)
}
private val materialCache = HashMap<Meta, Material>()
private val lineMaterialCache = HashMap<Meta, Material>()
fun getMaterial(meta: Meta): Material = materialCache.getOrPut(meta) {
MeshBasicMaterial().apply {
color = meta["color"]?.color() ?: DEFAULT_COLOR
opacity = meta["opacity"]?.double ?: 1.0
transparent = meta["transparent"].boolean ?: (opacity < 1.0)
//node["specularColor"]?.let { specular = it.color() }
//side = 2
}
}
fun getLineMaterial(meta: Meta): Material = lineMaterialCache.getOrPut(meta) {
LineBasicMaterial().apply {
color = meta["color"]?.color() ?: DEFAULT_LINE_COLOR
opacity = meta["opacity"].double ?: 1.0
transparent = meta["transparent"].boolean ?: (opacity < 1.0)
linewidth = meta["thickness"].double ?: 1.0
}
}
} }
/** /**
@ -41,26 +71,26 @@ fun MetaItem<*>.color(): Color {
} }
} }
private val materialCache = HashMap<Meta, Material>()
/** /**
* Infer Three material based on meta item * Infer Three material based on meta item
*/ */
fun Meta?.jsMaterial(): Material { fun Meta?.jsMaterial(): Material {
return if (this == null) { return if (this == null) {
Materials.DEFAULT Materials.DEFAULT
} else } else {
//TODO add more options for material Materials.getMaterial(this)
return materialCache.getOrPut(this) {
MeshBasicMaterial().apply {
color = get("color")?.color() ?: Materials.DEFAULT_COLOR
opacity = get("opacity")?.double ?: 1.0
transparent = get("transparent").boolean ?: (opacity < 1.0)
//node["specularColor"]?.let { specular = it.color() }
//side = 2
} }
} }
fun Meta?.jsLineMaterial(): Material {
return if (this == null) {
Materials.DEFAULT_LINE
} else{
Materials.getLineMaterial(this)
} }
}
fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial() fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial()

View File

@ -0,0 +1,99 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean
import hep.dataforge.meta.node
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.layer
import hep.dataforge.vis.spatial.material
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass
/**
* Basic geometry-based factory
*/
abstract class MeshThreeFactory<T : VisualObject3D>(
override val type: KClass<out T>
) : ThreeFactory<T> {
/**
* Build a geometry for an object
*/
abstract fun buildGeometry(obj: T): BufferGeometry
private fun Mesh.applyEdges(obj: T) {
children.find { it.name == "edges" }?.let { remove(it) }
//inherited edges definition, enabled by default
if (obj.getProperty(EDGES_ENABLED_KEY).boolean != false) {
val material = obj.getProperty(EDGES_MATERIAL_KEY).node.jsLineMaterial()
add(
LineSegments(
EdgesGeometry(geometry as BufferGeometry),
material
)
)
}
}
private fun Mesh.applyWireFrame(obj: T) {
children.find { it.name == "wireframe" }?.let { remove(it) }
//inherited wireframe definition, disabled by default
if (obj.getProperty(WIREFRAME_ENABLED_KEY).boolean == true) {
val material = obj.getProperty(WIREFRAME_MATERIAL_KEY).node.jsLineMaterial()
add(
LineSegments(
WireframeGeometry(geometry as BufferGeometry),
material
)
)
}
}
override fun invoke(obj: T): Mesh {
//TODO add caching for geometries using templates
val geometry = buildGeometry(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()).apply {
matrixAutoUpdate = false
applyEdges(obj)
applyWireFrame(obj)
//set position for mesh
updatePosition(obj)
layers.enable(obj.layer)
children.forEach {
it.layers.enable(obj.layer)
}
}
//add listener to object properties
obj.onPropertyChange(this) { name, _, _ ->
when {
name.startsWith(VisualObject3D.GEOMETRY_KEY) -> mesh.geometry = buildGeometry(obj)
name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
else -> mesh.updateProperty(obj, name)
}
}
return mesh
}
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 + Material3D.MATERIAL_KEY
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + Material3D.MATERIAL_KEY
}
}

View File

@ -1,22 +1,14 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean
import hep.dataforge.meta.node
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith import hep.dataforge.names.startsWith
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
import hep.dataforge.vis.spatial.VisualObject3D.Companion.GEOMETRY_KEY
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -46,30 +38,6 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) {
updateMatrix() 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)
layers.enable(obj.layer)
children.forEach {
it.layers.enable(obj.layer)
}
}
/** /**
* Unsafe invocation of a factory * Unsafe invocation of a factory
*/ */
@ -82,53 +50,6 @@ operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
} }
} }
/**
* Basic geometry-based factory
*/
abstract class MeshThreeFactory<T : VisualObject3D>(override val type: KClass<out T>) : ThreeFactory<T> {
/**
* Build a geometry for an object
*/
abstract fun buildGeometry(obj: T): BufferGeometry
override fun invoke(obj: T): Mesh {
//create mesh from geometry
return buildMesh(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 + MATERIAL_KEY
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + MATERIAL_KEY
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, _, _ ->
mesh.updateProperty(obj, name)
if (name.startsWith(GEOMETRY_KEY)) {
mesh.geometry = geometryBuilder(obj)
}
}
return mesh
}
}
}
fun Object3D.updateProperty(source: VisualObject, propertyName: Name) { fun Object3D.updateProperty(source: VisualObject, propertyName: Name) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
//updated material //updated material

View File

@ -0,0 +1,32 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.PolyLine
import hep.dataforge.vis.spatial.layer
import hep.dataforge.vis.spatial.material
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.LineSegments
import kotlin.reflect.KClass
object ThreeLineFactory : ThreeFactory<PolyLine> {
override val type: KClass<out PolyLine> get() = PolyLine::class
override fun invoke(obj: PolyLine): Object3D {
val geometry = Geometry().apply {
vertices = obj.points.toTypedArray()
}
val material = obj.material.jsLineMaterial()
return LineSegments(geometry, material).apply {
updatePosition(obj)
layers.enable(obj.layer)
//add listener to object properties
obj.onPropertyChange(this) { propertyName, _, _ ->
updateProperty(obj, propertyName)
}
}
}
}

View File

@ -33,6 +33,7 @@ class ThreePlugin : AbstractPlugin() {
objectFactories[Convex::class] = ThreeConvexFactory objectFactories[Convex::class] = ThreeConvexFactory
objectFactories[Sphere::class] = ThreeSphereFactory objectFactories[Sphere::class] = ThreeSphereFactory
objectFactories[ConeSegment::class] = ThreeCylinderFactory objectFactories[ConeSegment::class] = ThreeCylinderFactory
objectFactories[PolyLine::class] = ThreeLineFactory
} }
private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? { private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? {

View File

@ -28,7 +28,10 @@
package info.laht.threekt.objects package info.laht.threekt.objects
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.materials.Material import info.laht.threekt.materials.Material
open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D {
constructor(geometry: Geometry, material: Material)
}

View File

@ -130,6 +130,21 @@ private class ThreeDemoApp : ApplicationBase() {
} }
} }
} }
demo("lines", "Track / line segments") {
sphere(100) {
color(Colors.blue)
detail = 50
opacity = 0.4
}
repeat(20) {
polyline(Point3D(100, 100, 100), Point3D(-100, -100, -100)) {
thickness = 208.0
rotationX = it * PI2 / 20
color(Colors.green)
//rotationY = it * PI2 / 20
}
}
}
} }