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.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.values.Value
import kotlin.jvm.JvmName
import kotlin.properties.ReadOnlyProperty
@ -35,81 +36,79 @@ class VisualObjectDelegate(
}
class VisualObjectDelegateWrapper<T>(
val obj: VisualObject,
val key: Name?,
val default: T,
val inherited: Boolean,
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
val read: (MetaItem<*>?) -> T?
) : ReadWriteProperty<VisualObject, T> {
) : ReadWriteProperty<Any?, T> {
//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()
return if (inherited) {
read(thisRef.getProperty(name))
} else {
read(thisRef.config[name])
} ?: default
return read(obj.getProperty(name,inherited))?: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()
thisRef.config[name] = value
obj.config[name] = value
}
}
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) =
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) =
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) =
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) =
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) =
VisualObjectDelegateWrapper(key?.asName(), default, inherited) { it.int }
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
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) =
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) }
@JvmName("safeString")
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")
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")
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")
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")
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) =
VisualObjectDelegateWrapper(
this,
key?.let { NameToken(it).asName() },
default,
inherited
@ -121,11 +120,11 @@ fun <T> VisualObject.merge(
key: String? = null,
transformer: (Sequence<MetaItem<*>>) -> T
): ReadOnlyProperty<VisualObject, T> {
return object : ReadOnlyProperty<VisualObject, T> {
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
val name = key?.asName() ?: property.name.asName()
return object : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key?.toName() ?: property.name.asName()
val sequence = sequence<MetaItem<*>> {
var thisObj: VisualObject? = thisRef
var thisObj: VisualObject? = this@merge
while (thisObj != null) {
thisObj.config[name]?.let { yield(it) }
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.vis.common.Colors
import hep.dataforge.vis.spatial.Material3D
import info.laht.threekt.materials.LineBasicMaterial
import info.laht.threekt.materials.Material
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.materials.MeshPhongMaterial
@ -13,8 +14,37 @@ import info.laht.threekt.math.Color
object Materials {
val DEFAULT_COLOR = Color(Colors.darkgreen)
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
*/
fun Meta?.jsMaterial(): Material {
return if (this == null) {
Materials.DEFAULT
} else
//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
transparent = get("transparent").boolean ?: (opacity < 1.0)
//node["specularColor"]?.let { specular = it.color() }
//side = 2
}
}
} else {
Materials.getMaterial(this)
}
}
fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
fun Meta?.jsLineMaterial(): Material {
return if (this == null) {
Materials.DEFAULT_LINE
} else{
Materials.getLineMaterial(this)
}
}
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
import hep.dataforge.meta.boolean
import hep.dataforge.meta.node
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
import hep.dataforge.provider.Type
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
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 info.laht.threekt.core.BufferGeometry
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 kotlin.reflect.KClass
@ -46,30 +38,6 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) {
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
*/
@ -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) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
//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[Sphere::class] = ThreeSphereFactory
objectFactories[ConeSegment::class] = ThreeCylinderFactory
objectFactories[PolyLine::class] = ThreeLineFactory
}
private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? {

View File

@ -28,7 +28,10 @@
package info.laht.threekt.objects
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
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
}
}
}
}