FX backend in progress

This commit is contained in:
Alexander Nozik 2019-12-14 21:45:41 +03:00
parent c964f80d73
commit cef1a1ee6d
32 changed files with 406 additions and 146 deletions

View File

@ -32,8 +32,7 @@ kotlin {
jsMain{
dependencies {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
api("kotlin.js.externals:kotlin-js-jquery:3.2.0-0")
api(npm("bootstrap","4.3.1"))
api(npm("bootstrap","4.4.1"))
}
}
}

View File

@ -1,6 +1,9 @@
package hep.dataforge.vis.common
import hep.dataforge.meta.*
import hep.dataforge.meta.Config
import hep.dataforge.meta.Configurable
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.toName
@ -15,7 +18,7 @@ import kotlinx.serialization.Transient
* A root type for display hierarchy
*/
@Type(TYPE)
interface VisualObject : MetaRepr, Configurable {
interface VisualObject : Configurable {
/**
* The parent object of this one. If null, this one is a root.
@ -54,7 +57,7 @@ interface VisualObject : MetaRepr, Configurable {
fun removeChangeListener(owner: Any?)
/**
* List of names of styles applied to this object
* List of names of styles applied to this object. Order matters.
*/
var styles: List<Name>

View File

@ -21,7 +21,8 @@ kotlin {
}
jvmMain {
dependencies {
implementation("org.fxyz3d:fxyz3d:0.5.2")
api("org.fxyz3d:fxyz3d:0.5.2")
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7")
}
}
jsMain {

View File

@ -3,7 +3,6 @@ package hep.dataforge.vis.spatial
import hep.dataforge.context.Context
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.meta.float
@ -50,8 +49,6 @@ class Box(
geometryBuilder.face4(node8, node5, node6, node7)
}
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
companion object : VisualFactory<Box> {
const val TYPE = "geometry.3d.box"

View File

@ -2,9 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.meta.update
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
@ -34,8 +32,6 @@ class Composite(
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
}
inline fun VisualGroup3D.composite(

View File

@ -3,12 +3,12 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlin.math.cos
import kotlin.math.sin
/**
* A cylinder or cut cone segment
@ -20,7 +20,7 @@ class ConeSegment(
var upperRadius: Float,
var startAngle: Float = 0f,
var angle: Float = PI2
) : AbstractVisualObject(), VisualObject3D {
) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
@ -29,7 +29,48 @@ class ConeSegment(
override var rotation: Point3D? = null
override var scale: Point3D? = null
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val segments = detail ?: 8
require(segments >= 4) { "The number of segments in cone segment is too small" }
val angleStep = angle / (segments - 1)
fun shape(r: Float, z: Float): List<Point3D> {
return (0 until segments).map { i ->
Point3D(r * cos(startAngle + angleStep * i), r * sin(startAngle + angleStep * i), z)
}
}
geometryBuilder.apply {
//creating shape in x-y plane with z = 0
val bottomOuterPoints = shape(upperRadius, -height / 2)
val upperOuterPoints = shape(radius, height / 2)
//outer face
(1 until segments).forEach {
face4(bottomOuterPoints[it - 1], bottomOuterPoints[it], upperOuterPoints[it], upperOuterPoints[it - 1])
}
if (angle == PI2) {
face4(bottomOuterPoints.last(), bottomOuterPoints[0], upperOuterPoints[0], upperOuterPoints.last())
}
val zeroBottom = Point3D(0f, 0f, 0f)
val zeroTop = Point3D(0f, 0f, height)
(1 until segments).forEach {
face(bottomOuterPoints[it - 1], zeroBottom, bottomOuterPoints[it])
face(upperOuterPoints[it - 1], upperOuterPoints[it], zeroTop)
}
if (angle == PI2) {
face(bottomOuterPoints.last(), zeroBottom, bottomOuterPoints[0])
face(upperOuterPoints.last(), upperOuterPoints[0], zeroTop)
} else {
face4(zeroTop, zeroBottom, bottomOuterPoints[0], upperOuterPoints[0])
face4(zeroTop, zeroBottom, bottomOuterPoints.last(), upperOuterPoints.last())
}
}
}
}
inline fun VisualGroup3D.cylinder(

View File

@ -3,9 +3,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@ -20,8 +18,6 @@ class Convex(val points: List<Point3D>) : AbstractVisualObject(), VisualObject3D
override var rotation: Point3D? = null
override var scale: Point3D? = null
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
companion object {
const val TYPE = "geometry.3d.convex"
}

View File

@ -2,9 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@ -107,8 +105,6 @@ class Extruded(
geometryBuilder.cap(layers.last())
}
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
companion object {
const val TYPE = "geometry.3d.extruded"
}

View File

@ -3,9 +3,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.common.number
import kotlinx.serialization.Serializable
@ -23,7 +21,6 @@ class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject
//var lineType by string()
var thickness by number(1.0, key = "material.thickness")
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
}
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =

View File

@ -4,7 +4,6 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaItem
@ -64,8 +63,6 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
}
}
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
override val children: Map<NameToken, ProxyChild>
get() = (prototype as? MutableVisualGroup)?.children
?.filter { !it.key.toString().startsWith("@") }
@ -144,8 +141,6 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
}
}
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
}
companion object {

View File

@ -3,9 +3,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@ -18,7 +16,7 @@ class Sphere(
var phi: Float = PI2,
var thetaStart: Float = 0f,
var theta: Float = PI.toFloat()
) : AbstractVisualObject(), VisualObject3D {
) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
@ -27,7 +25,9 @@ class Sphere(
override var rotation: Point3D? = null
override var scale: Point3D? = null
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
inline fun VisualGroup3D.sphere(

View File

@ -0,0 +1,23 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@Serializable
class Text3D(var text: String, var fontSize: Int) : 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
}
fun VisualGroup3D.text(text: String, fontSize: Int, name: String = "", action: Text3D.() -> Unit = {}) =
Text3D(text, fontSize).apply(action).also { set(name, it) }

View File

@ -2,9 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@ -126,7 +124,6 @@ class Tube(
}
}
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
}
inline fun VisualGroup3D.tube(

View File

@ -11,7 +11,6 @@ package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
@ -97,8 +96,6 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
}
}
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
companion object {
const val PROTOTYPES_KEY = "templates"
}

View File

@ -1,8 +1,8 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.get
import hep.dataforge.io.toMeta
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.getIndexed
import hep.dataforge.meta.node
import kotlin.test.Test
import kotlin.test.assertEquals
@ -25,12 +25,11 @@ class ConvexTest {
val convex = group.first() as Convex
val meta = convex.toMeta()
val json = Visual3DPlugin.json.toJson(Convex.serializer(), convex)
val meta = json.toMeta()
val pointsNode = convex.toMeta()["points"].node
assertEquals(8, pointsNode?.items?.count())
val points = pointsNode?.getIndexed("points")
val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D()}
assertEquals(8, points.count())
assertEquals(8, convex.points.size)
}

View File

@ -8,15 +8,15 @@ import kotlin.test.assertEquals
class SerializationTest {
@ImplicitReflectionSerializer
@Test
fun testCubeSerialization(){
val cube = Box(100f,100f,100f).apply{
fun testCubeSerialization() {
val cube = Box(100f, 100f, 100f).apply {
color(222)
x = 100
z = -100
}
val string = json.stringify(Box.serializer(),cube)
val string = json.stringify(Box.serializer(), cube)
println(string)
val newCube = json.parse(Box.serializer(),string)
assertEquals(cube.toMeta(),newCube.toMeta())
val newCube = json.parse(Box.serializer(), string)
assertEquals(cube.config, newCube.config)
}
}

View File

@ -1,6 +1,6 @@
package hep.dataforge.vis.spatial.editor
import hep.dataforge.vis.spatial.three.ThreeOutput
import hep.dataforge.vis.spatial.three.ThreeCanvas
import kotlinx.html.InputType
import kotlinx.html.dom.append
import kotlinx.html.js.div
@ -9,7 +9,7 @@ import kotlinx.html.js.label
import org.w3c.dom.Element
import kotlin.dom.clear
fun Element.threeOutputConfig(output: ThreeOutput) {
fun Element.threeOutputConfig(canvas: ThreeCanvas) {
clear()
append {
card("Layers"){
@ -23,9 +23,9 @@ fun Element.threeOutputConfig(output: ThreeOutput) {
}
onchange = {
if (checked) {
output.camera.layers.enable(layer)
canvas.camera.layers.enable(layer)
} else {
output.camera.layers.disable(layer)
canvas.camera.layers.disable(layer)
}
}
}

View File

@ -19,7 +19,7 @@ import kotlin.browser.window
import kotlin.dom.clear
import kotlin.math.max
class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Renderer<VisualObject3D> {
class ThreeCanvas(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Renderer<VisualObject3D> {
override val context: Context get() = three.context
@ -95,7 +95,7 @@ class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Renderer
}
fun ThreePlugin.output(element: HTMLElement? = null, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit = {}) =
ThreeOutput(this, buildMeta(meta, override)).apply {
ThreeCanvas(this, buildMeta(meta, override)).apply {
if (element != null) {
attach(element)
}

View File

@ -38,17 +38,17 @@ fun Object3D.updatePosition(obj: VisualObject3D) {
updateMatrix()
}
/**
* Unsafe invocation of a factory
*/
operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
if (type.isInstance(obj)) {
@Suppress("UNCHECKED_CAST")
return invoke(obj as T)
} else {
error("The object of type ${obj::class} could not be rendered by this factory")
}
}
///**
// * Unsafe invocation of a factory
// */
//operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
// if (type.isInstance(obj)) {
// @Suppress("UNCHECKED_CAST")
// return invoke(obj as T)
// } else {
// error("The object of type ${obj::class} could not be rendered by this factory")
// }
//}
/**
* Update non-position non-geometry property

View File

@ -16,7 +16,7 @@ import info.laht.threekt.objects.Group as ThreeGroup
class ThreePlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag
private val objectFactories = HashMap<KClass<out VisualObject>, ThreeFactory<*>>()
private val objectFactories = HashMap<KClass<out VisualObject3D>, ThreeFactory<*>>()
private val compositeFactory = ThreeCompositeFactory(this)
private val proxyFactory = ThreeProxyFactory(this)
@ -29,9 +29,11 @@ class ThreePlugin : AbstractPlugin() {
objectFactories[PolyLine::class] = ThreeLineFactory
}
private fun findObjectFactory(type: KClass<out VisualObject>): ThreeFactory<*>? {
return objectFactories[type]
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type }
@Suppress("UNCHECKED_CAST")
private fun findObjectFactory(type: KClass<out VisualObject>): ThreeFactory<VisualObject3D>? {
return (objectFactories[type]
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type })
as ThreeFactory<VisualObject3D>?
}
fun buildObject3D(obj: VisualObject3D): Object3D {
@ -73,7 +75,7 @@ class ThreePlugin : AbstractPlugin() {
is Composite -> compositeFactory(obj)
else -> {
//find specialized factory for this type if it is present
val factory = findObjectFactory(obj::class)
val factory: ThreeFactory<VisualObject3D>? = findObjectFactory(obj::class)
when {
factory != null -> factory(obj)
obj is Shape -> ThreeShapeFactory(obj)

View File

@ -3,13 +3,10 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.toMeta
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.spatial.Point3D
import hep.dataforge.vis.spatial.Point3DSerializer
import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualObject3D
import info.laht.threekt.core.Object3D
import kotlinx.serialization.Serializable
@ -32,8 +29,6 @@ class CustomThreeVisualObject(val threeFactory: ThreeFactory<VisualObject3D>) :
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
override fun toMeta(): Meta = Visual3DPlugin.json.toJson(serializer(), this).toMeta()
override fun toObject3D(): Object3D = threeFactory(this)
}

View File

@ -1,5 +1,11 @@
package hep.dataforge.vis.spatial.fx
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware
import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta
import hep.dataforge.output.Renderer
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.World.CAMERA_FAR_CLIP
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_DISTANCE
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_X_ANGLE
@ -16,7 +22,9 @@ import javafx.scene.paint.Color
import org.fxyz3d.utils.CameraTransformer
import tornadofx.*
class Canvas3D : Fragment() {
class Canvas3D(val plugin: FX3DPlugin, val meta: Meta = EmptyMeta) : Fragment(), Renderer<VisualObject3D>, ContextAware {
override val context: Context get() = plugin.context
val world: Group = Group()
private val camera = PerspectiveCamera().apply {
@ -153,6 +161,10 @@ class Canvas3D : Fragment() {
}
}
override fun render(obj: VisualObject3D, meta: Meta) {
plugin.buildNode(obj)?.let { world.children.add(it) }
}
companion object {
private const val AXIS_LENGTH = 2000.0
private const val CONTROL_MULTIPLIER = 0.1

View File

@ -10,7 +10,7 @@ import tornadofx.*
/**
* A caching binding collection for [VisualObject] properties
*/
class DisplayObjectFXListener(val obj: VisualObject) {
class DisplayObjectFXBinding(val obj: VisualObject) {
private val binndings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
init {

View File

@ -1,50 +0,0 @@
package hep.dataforge.vis.spatial.fx
import hep.dataforge.context.Context
import hep.dataforge.meta.Meta
import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.Box
import hep.dataforge.vis.spatial.VisualGroup3D
import javafx.scene.Group
import javafx.scene.Node
import org.fxyz3d.shapes.primitives.CuboidMesh
import tornadofx.*
/**
* https://github.com/miho/JCSG for operations
*
*/
class FX3DOutput(override val context: Context) : Renderer<VisualObject> {
val canvas by lazy { Canvas3D() }
private fun buildNode(obj: VisualObject): Node? {
val listener = DisplayObjectFXListener(obj)
val x = listener["pos.x"].float()
val y = listener["pos.y"].float()
val z = listener["pos.z"].float()
val center = objectBinding(x, y, z) {
org.fxyz3d.geometry.Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f)
}
return when (obj) {
is VisualGroup3D -> Group(obj.map { buildNode(it) }).apply {
this.translateXProperty().bind(x)
this.translateYProperty().bind(y)
this.translateZProperty().bind(z)
}
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble()).apply {
this.centerProperty().bind(center)
this.materialProperty().bind(listener["color"].transform { it.material() })
}
else -> {
logger.error { "No renderer defined for ${obj::class}" }
null
}
}
}
override fun render(obj: VisualObject, meta: Meta) {
buildNode(obj)?.let { canvas.world.children.add(it) }
}
}

View File

@ -0,0 +1,113 @@
package hep.dataforge.vis.spatial.fx
import hep.dataforge.context.*
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.provider.Type
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.fx.FX3DFactory.Companion.TYPE
import javafx.scene.Group
import javafx.scene.Node
import javafx.scene.shape.Shape3D
import javafx.scene.transform.Rotate
import org.fxyz3d.shapes.composites.PolyLine3D
import org.fxyz3d.shapes.primitives.CuboidMesh
import kotlin.reflect.KClass
class FX3DPlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag
private val objectFactories = HashMap<KClass<out VisualObject3D>, FX3DFactory<*>>()
private val compositeFactory = FXCompositeFactory(this)
private val proxyFactory = FXProxyFactory(this)
init {
//Add specialized factories here
objectFactories[Convex::class] = FXConvexFactory
}
@Suppress("UNCHECKED_CAST")
private fun findObjectFactory(type: KClass<out VisualObject3D>): FX3DFactory<VisualObject3D>? {
return (objectFactories[type] ?: context.content<FX3DFactory<*>>(TYPE).values.find { it.type == type })
as FX3DFactory<VisualObject3D>?
}
fun buildNode(obj: VisualObject3D): Node? {
val binding = DisplayObjectFXBinding(obj)
return when (obj) {
is Proxy -> proxyFactory(obj, binding)
is VisualGroup3D -> Group(obj.filterIsInstance<VisualObject3D>().map { buildNode(it) })
is Composite -> compositeFactory(obj, binding)
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble())
is PolyLine -> PolyLine3D(
obj.points.map { it.point },
obj.thickness.toFloat(),
obj.material?.get("color")?.color()
)
else -> {
//find specialized factory for this type if it is present
val factory: FX3DFactory<VisualObject3D>? = findObjectFactory(obj::class)
when {
factory != null -> factory(obj, binding)
obj is Shape -> FXShapeFactory(obj, binding)
else -> error("Renderer for ${obj::class} not found")
}
}
}.apply {
translateXProperty().bind(binding[VisualObject3D.xPos].float())
translateYProperty().bind(binding[VisualObject3D.yPos].float())
translateZProperty().bind(binding[VisualObject3D.zPos].float())
scaleXProperty().bind(binding[VisualObject3D.xScale].float())
scaleYProperty().bind(binding[VisualObject3D.yScale].float())
scaleZProperty().bind(binding[VisualObject3D.zScale].float())
val rotateX = Rotate(0.0, Rotate.X_AXIS).apply {
angleProperty().bind(binding[VisualObject3D.xRotation].float())
}
val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply {
angleProperty().bind(binding[VisualObject3D.yRotation].float())
}
val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply {
angleProperty().bind(binding[VisualObject3D.zRotation].float())
}
when (obj.rotationOrder) {
RotationOrder.ZYX -> transforms.addAll(rotateZ, rotateY, rotateX)
RotationOrder.XZY -> transforms.addAll(rotateY, rotateZ, rotateX)
RotationOrder.YXZ -> transforms.addAll(rotateZ, rotateX, rotateY)
RotationOrder.YZX -> transforms.addAll(rotateX, rotateZ, rotateY)
RotationOrder.ZXY -> transforms.addAll(rotateY, rotateX, rotateZ)
RotationOrder.XYZ -> transforms.addAll(rotateZ, rotateY, rotateX)
}
if (this is Shape3D) {
materialProperty().bind(binding["color"].transform { it.material() })
}
}
}
companion object : PluginFactory<FX3DPlugin> {
override val tag = PluginTag("visual.fx3D", PluginTag.DATAFORGE_GROUP)
override val type = FX3DPlugin::class
override fun invoke(meta: Meta, context: Context) = FX3DPlugin()
}
}
/**
* Builder and updater for three.js object
*/
@Type(TYPE)
interface FX3DFactory<in T : VisualObject3D> {
val type: KClass<in T>
operator fun invoke(obj: T, binding: DisplayObjectFXBinding): Node
companion object {
const val TYPE = "fx3DFactory"
}
}

View File

@ -0,0 +1,38 @@
package hep.dataforge.vis.spatial.fx
import eu.mihosoft.jcsg.CSG
import hep.dataforge.vis.spatial.Composite
import hep.dataforge.vis.spatial.CompositeType
import javafx.scene.Group
import javafx.scene.Node
import javafx.scene.shape.MeshView
import org.fxyz3d.utils.MeshUtils
import kotlin.reflect.KClass
class FXCompositeFactory(val plugin: FX3DPlugin) :
FX3DFactory<Composite> {
override val type: KClass<in Composite>
get() = Composite::class
override fun invoke(obj: Composite, binding: DisplayObjectFXBinding): Node {
val first = plugin.buildNode(obj.first) as? MeshView ?: error("Can't build node")
val second = plugin.buildNode(obj.second) as? MeshView ?: error("Can't build node")
val firstCSG = MeshUtils.mesh2CSG(first)
val secondCSG = MeshUtils.mesh2CSG(second)
val resultCSG = when(obj.compositeType){
CompositeType.UNION -> firstCSG.union(secondCSG)
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG)
CompositeType.SUBTRACT -> firstCSG.difference(secondCSG)
}
return resultCSG.toNode()
}
}
internal fun CSG.toNode(): Node{
val meshes = toJavaFXMesh().asMeshViews
return if(meshes.size == 1){
meshes.first()
} else {
Group(meshes.map { it })
}
}

View File

@ -0,0 +1,19 @@
package hep.dataforge.vis.spatial.fx
import eu.mihosoft.jcsg.PropertyStorage
import eu.mihosoft.jcsg.ext.quickhull3d.HullUtil
import eu.mihosoft.vvecmath.Vector3d
import hep.dataforge.vis.spatial.Convex
import javafx.scene.Node
import kotlin.reflect.KClass
object FXConvexFactory : FX3DFactory<Convex> {
override val type: KClass<in Convex> get() = Convex::class
override fun invoke(obj: Convex, binding: DisplayObjectFXBinding): Node {
val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage())
return hull.toNode()
}
}

View File

@ -0,0 +1,15 @@
package hep.dataforge.vis.spatial.fx
import hep.dataforge.vis.spatial.Proxy
import javafx.scene.Node
import kotlin.reflect.KClass
class FXProxyFactory(val plugin: FX3DPlugin) :
FX3DFactory<Proxy> {
override val type: KClass<in Proxy> get() = Proxy::class
override fun invoke(obj: Proxy, binding: DisplayObjectFXBinding): Node {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}

View File

@ -0,0 +1,58 @@
package hep.dataforge.vis.spatial.fx
import hep.dataforge.meta.Meta
import hep.dataforge.vis.spatial.GeometryBuilder
import hep.dataforge.vis.spatial.Point3D
import hep.dataforge.vis.spatial.Shape
import javafx.scene.shape.Mesh
import javafx.scene.shape.MeshView
import javafx.scene.shape.TriangleMesh
import kotlin.reflect.KClass
object FXShapeFactory : FX3DFactory<Shape> {
override val type: KClass<in Shape> get() = Shape::class
override fun invoke(obj: Shape, binding: DisplayObjectFXBinding): MeshView {
val mesh = FXGeometryBuilder().apply { obj.toGeometry(this) }.build()
return MeshView(mesh)
}
}
private typealias Face = IntArray
private class FXGeometryBuilder : GeometryBuilder<Mesh> {
val vertices = ArrayList<Point3D>()
val faces = ArrayList<Face>()
private val vertexCache = HashMap<Point3D, Int>()
private fun append(vertex: Point3D): Int {
val index = vertexCache[vertex] ?: -1//vertices.indexOf(vertex)
return if (index > 0) {
index
} else {
vertices.add(vertex)
vertexCache[vertex] = vertices.size - 1
vertices.size - 1
}
}
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
//adding vertices
val face: Face = intArrayOf(append(vertex1), append(vertex2), append(vertex3))
faces.add(face)
}
override fun build(): Mesh {
val mesh = TriangleMesh()
vertices.forEach {
//TODO optimize copy
mesh.points.addAll(it.x.toFloat(), it.y.toFloat(), it.z.toFloat())
}
faces.forEach {
mesh.faces.addAll(it[0], it[1], it[2])
}
return mesh
}
}

View File

@ -16,9 +16,10 @@ class RendererDemoApp : App(RendererDemoView::class)
class RendererDemoView : View() {
val renderer = FX3DOutput(Global)
val plugin = Global.plugins.fetch(FX3DPlugin)
val renderer = Canvas3D(plugin)
override val root: Parent = borderpane {
center = renderer.canvas.root
center = renderer.root
}
lateinit var group: VisualGroup3D
@ -44,7 +45,7 @@ class RendererDemoView : View() {
}
}
renderer.canvas.apply {
renderer.apply {
angleY = -30.0
angleX = -15.0
}

View File

@ -5,8 +5,28 @@ actual class Point2D actual constructor(x: Number, y: Number) {
actual var y = y.toDouble()
}
actual class Point3D actual constructor(x: Number, y: Number, z: Number) {
actual var x = x.toDouble()
actual var y = y.toDouble()
actual var z = z.toDouble()
actual class Point3D(val point: org.fxyz3d.geometry.Point3D) {
actual constructor(x: Number, y: Number, z: Number) : this(
org.fxyz3d.geometry.Point3D(
x.toFloat(),
y.toFloat(),
z.toFloat()
)
)
actual var x: Double
inline get() = point.x.toDouble()
inline set(value) {
point.x = value.toFloat()
}
actual var y: Double
inline get() = point.y.toDouble()
inline set(value) {
point.y = value.toFloat()
}
actual var z: Double
inline get() = point.z.toDouble()
inline set(value) {
point.z = value.toFloat()
}
}

View File

@ -10,12 +10,12 @@ import hep.dataforge.meta.get
import hep.dataforge.meta.string
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.output.Output
import hep.dataforge.output.OutputManager
import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.render
import hep.dataforge.vis.spatial.three.ThreeOutput
import hep.dataforge.vis.spatial.three.ThreeCanvas
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import kotlinx.html.dom.append
@ -33,7 +33,7 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
override val tag: PluginTag get() = Companion.tag
private val gridRoot = document.create.div("row")
private val outputs: MutableMap<Name, ThreeOutput> = HashMap()
private val outputs: MutableMap<Name, ThreeCanvas> = HashMap()
init {
require(ThreePlugin)
@ -48,7 +48,7 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
}
@Suppress("UNCHECKED_CAST")
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Output<T> {
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
val three = context.plugins.get<ThreePlugin>()!!
return outputs.getOrPut(name) {
@ -73,7 +73,7 @@ class ThreeDemoGrid(meta: Meta) : AbstractPlugin(meta), OutputManager {
}
output
} as Output<T>
} as Renderer<T>
}
companion object : PluginFactory<ThreeDemoGrid> {