FX backend in progress
This commit is contained in:
parent
fd43ea4843
commit
0164534004
@ -1,7 +1,5 @@
|
|||||||
package hep.dataforge.vis.common
|
package hep.dataforge.vis.common
|
||||||
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
|
* Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
|
||||||
*/
|
*/
|
||||||
@ -177,11 +175,6 @@ object Colors {
|
|||||||
const val yellow = 0xFFFF00
|
const val yellow = 0xFFFF00
|
||||||
const val yellowgreen = 0x9ACD32
|
const val yellowgreen = 0x9ACD32
|
||||||
|
|
||||||
fun rgbToString(rgb: Int): String {
|
|
||||||
val string = rgb.toString(16).padStart(6, '0')
|
|
||||||
return "#" + string.substring(max(0, string.length - 6))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun rgbToString(red: UByte, green: UByte, blue: UByte): String {
|
fun rgbToString(red: UByte, green: UByte, blue: UByte): String {
|
||||||
fun colorToString(color: UByte): String{
|
fun colorToString(color: UByte): String{
|
||||||
return color.toString(16).padStart(2,'0')
|
return color.toString(16).padStart(2,'0')
|
||||||
|
@ -5,7 +5,6 @@ import hep.dataforge.meta.MetaBuilder
|
|||||||
import hep.dataforge.meta.buildMeta
|
import hep.dataforge.meta.buildMeta
|
||||||
import hep.dataforge.names.Name
|
import hep.dataforge.names.Name
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
import hep.dataforge.vis.common.Colors
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.common.VisualObject
|
||||||
import hep.dataforge.vis.common.applyStyle
|
import hep.dataforge.vis.common.applyStyle
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
||||||
@ -53,7 +52,7 @@ class GDMLTransformer(val root: GDML) {
|
|||||||
val styleName = "material[${material.name}]"
|
val styleName = "material[${material.name}]"
|
||||||
|
|
||||||
obj.useStyle(styleName){
|
obj.useStyle(styleName){
|
||||||
COLOR_KEY to Colors.rgbToString(random.nextInt(0, Int.MAX_VALUE))
|
COLOR_KEY to random.nextInt(0, Int.MAX_VALUE)
|
||||||
"gdml.material" put material.name
|
"gdml.material" put material.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ kotlin {
|
|||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
api("org.fxyz3d:fxyz3d:0.5.2")
|
api("org.fxyz3d:fxyz3d:0.5.2")
|
||||||
|
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}")
|
||||||
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7")
|
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package hep.dataforge.vis.spatial
|
|||||||
import hep.dataforge.meta.*
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.names.asName
|
import hep.dataforge.names.asName
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.vis.common.Colors
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
import hep.dataforge.vis.common.VisualObject
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
||||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
||||||
@ -20,6 +19,7 @@ class Material3D(override val config: Config) : Specific {
|
|||||||
|
|
||||||
val MATERIAL_KEY = "material".asName()
|
val MATERIAL_KEY = "material".asName()
|
||||||
val COLOR_KEY = MATERIAL_KEY + "color"
|
val COLOR_KEY = MATERIAL_KEY + "color"
|
||||||
|
val SPECULAR_COLOR = MATERIAL_KEY + "specularColor"
|
||||||
val OPACITY_KEY = MATERIAL_KEY + "opacity"
|
val OPACITY_KEY = MATERIAL_KEY + "opacity"
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -29,9 +29,18 @@ fun VisualObject.color(rgb: String) {
|
|||||||
setProperty(COLOR_KEY, rgb)
|
setProperty(COLOR_KEY, rgb)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun VisualObject.color(rgb: Int) = color(Colors.rgbToString(rgb))
|
fun VisualObject.color(rgb: Int) {
|
||||||
|
setProperty(COLOR_KEY, rgb)
|
||||||
|
}
|
||||||
|
|
||||||
fun VisualObject.color(r: UByte, g: UByte, b: UByte) = color(Colors.rgbToString(r, g, b))
|
fun VisualObject.color(r: UByte, g: UByte, b: UByte) = setProperty(
|
||||||
|
COLOR_KEY,
|
||||||
|
buildMeta {
|
||||||
|
"red" put r.toInt()
|
||||||
|
"green" put g.toInt()
|
||||||
|
"blue" put b.toInt()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
var VisualObject.color: String?
|
var VisualObject.color: String?
|
||||||
get() = getProperty(COLOR_KEY).string
|
get() = getProperty(COLOR_KEY).string
|
||||||
|
@ -26,7 +26,16 @@ class Sphere(
|
|||||||
override var scale: Point3D? = null
|
override var scale: Point3D? = null
|
||||||
|
|
||||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
TODO("not implemented")
|
||||||
|
// val segments = this.detail ?: 8
|
||||||
|
// require(segments >= 4) { "The detail for sphere must be >= 4" }
|
||||||
|
// val phiStep = phi / segments
|
||||||
|
// val thetaStep = theta / segments
|
||||||
|
// for (i in 1 until segments - 1) {
|
||||||
|
// for (j in 0 until segments - 1) {
|
||||||
|
// val point1 = Point3D()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ import info.laht.threekt.materials.MeshBasicMaterial
|
|||||||
import info.laht.threekt.materials.MeshPhongMaterial
|
import info.laht.threekt.materials.MeshPhongMaterial
|
||||||
import info.laht.threekt.math.Color
|
import info.laht.threekt.math.Color
|
||||||
import info.laht.threekt.objects.Mesh
|
import info.laht.threekt.objects.Mesh
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
|
||||||
object ThreeMaterials {
|
object ThreeMaterials {
|
||||||
@ -46,6 +47,11 @@ object ThreeMaterials {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun rgbToString(rgb: Int): String {
|
||||||
|
val string = rgb.toString(16).padStart(6, '0')
|
||||||
|
return "#" + string.substring(max(0, string.length - 6))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,11 +59,11 @@ object ThreeMaterials {
|
|||||||
*/
|
*/
|
||||||
fun MetaItem<*>.color(): Color {
|
fun MetaItem<*>.color(): Color {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||||
Color(this.value.string)
|
|
||||||
} else {
|
|
||||||
val int = value.number.toInt()
|
val int = value.number.toInt()
|
||||||
Color(int)
|
Color(int)
|
||||||
|
} else {
|
||||||
|
Color(this.value.string)
|
||||||
}
|
}
|
||||||
is MetaItem.NodeItem -> {
|
is MetaItem.NodeItem -> {
|
||||||
Color(
|
Color(
|
||||||
|
@ -0,0 +1,211 @@
|
|||||||
|
package hep.dataforge.vis.spatial.demo
|
||||||
|
|
||||||
|
import hep.dataforge.vis.common.Colors
|
||||||
|
import hep.dataforge.vis.spatial.*
|
||||||
|
import javafx.stage.Stage
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.javafx.JavaFx
|
||||||
|
import tornadofx.*
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
class FXDemoApp : App(FXDemoGrid::class) {
|
||||||
|
|
||||||
|
val view: FXDemoGrid by inject()
|
||||||
|
|
||||||
|
override fun start(stage: Stage) {
|
||||||
|
super.start(stage)
|
||||||
|
view.run {
|
||||||
|
demo("shapes", "Basic shapes") {
|
||||||
|
box(100.0, 100.0, 100.0) {
|
||||||
|
z = 110.0
|
||||||
|
}
|
||||||
|
sphere(50.0) {
|
||||||
|
x = 110
|
||||||
|
detail = 16
|
||||||
|
}
|
||||||
|
tube(50, height = 10, innerRadius = 25, angle = PI) {
|
||||||
|
y = 110
|
||||||
|
detail = 16
|
||||||
|
rotationX = PI / 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
demo("dynamic", "Dynamic properties") {
|
||||||
|
val group = group {
|
||||||
|
box(100, 100, 100) {
|
||||||
|
z = 110.0
|
||||||
|
}
|
||||||
|
|
||||||
|
box(100, 100, 100) {
|
||||||
|
visible = false
|
||||||
|
x = 110.0
|
||||||
|
//override color for this cube
|
||||||
|
color(1530)
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.JavaFx) {
|
||||||
|
while (isActive) {
|
||||||
|
delay(500)
|
||||||
|
visible = !(visible ?: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.JavaFx) {
|
||||||
|
val random = Random(111)
|
||||||
|
while (isActive) {
|
||||||
|
delay(1000)
|
||||||
|
group.color(random.nextInt(0, Int.MAX_VALUE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
demo("rotation", "Rotations") {
|
||||||
|
box(100, 100, 100)
|
||||||
|
group {
|
||||||
|
x = 200
|
||||||
|
rotationY = PI / 4
|
||||||
|
box(100, 100, 100) {
|
||||||
|
rotationZ = PI / 4
|
||||||
|
color(Colors.red)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
demo("extrude", "extruded shape") {
|
||||||
|
extrude {
|
||||||
|
shape {
|
||||||
|
polygon(8, 50)
|
||||||
|
}
|
||||||
|
for (i in 0..100) {
|
||||||
|
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
|
||||||
|
}
|
||||||
|
color(Colors.teal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// demo("CSG.simple", "CSG operations") {
|
||||||
|
// composite(CompositeType.UNION) {
|
||||||
|
// box(100, 100, 100) {
|
||||||
|
// z = 50
|
||||||
|
// }
|
||||||
|
// sphere(50)
|
||||||
|
// material {
|
||||||
|
// color(Colors.lightgreen)
|
||||||
|
// opacity = 0.3f
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// composite(CompositeType.INTERSECT) {
|
||||||
|
// y = 300
|
||||||
|
// box(100, 100, 100) {
|
||||||
|
// z = 50
|
||||||
|
// }
|
||||||
|
// sphere(50)
|
||||||
|
// color(Colors.red)
|
||||||
|
// }
|
||||||
|
// composite(CompositeType.SUBTRACT) {
|
||||||
|
// y = -300
|
||||||
|
// box(100, 100, 100) {
|
||||||
|
// z = 50
|
||||||
|
// }
|
||||||
|
// sphere(50)
|
||||||
|
// color(Colors.blue)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// demo("CSG.custom", "CSG with manually created object") {
|
||||||
|
// intersect {
|
||||||
|
// box(100, 100, 100)
|
||||||
|
// tube(60, 10) {
|
||||||
|
// detail = 180
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// demo("dynamicBox", "Dancing boxes") {
|
||||||
|
// val boxes = (-10..10).flatMap { i ->
|
||||||
|
// (-10..10).map { j ->
|
||||||
|
// varBox(10, 10, 0, name = "cell_${i}_${j}") {
|
||||||
|
// x = i * 10
|
||||||
|
// y = j * 10
|
||||||
|
// value = 128
|
||||||
|
// setProperty(EDGES_ENABLED_KEY, false)
|
||||||
|
// setProperty(WIREFRAME_ENABLED_KEY, false)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// GlobalScope.launch {
|
||||||
|
// while (isActive) {
|
||||||
|
// delay(200)
|
||||||
|
// boxes.forEach { box ->
|
||||||
|
// box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//class SpatialDemoView : View() {
|
||||||
|
// private val plugin = Global.plugins.fetch(FX3DPlugin)
|
||||||
|
// private val canvas = FXCanvas3D(plugin)
|
||||||
|
//
|
||||||
|
// override val root: Parent = borderpane {
|
||||||
|
// center = canvas.root
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// lateinit var group: VisualGroup3D
|
||||||
|
//
|
||||||
|
// init {
|
||||||
|
// canvas.render {
|
||||||
|
// group = group {
|
||||||
|
// box(100, 100, 100)
|
||||||
|
// box(100, 100, 100) {
|
||||||
|
// x = 110.0
|
||||||
|
// color(Colors.blue)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// //var color by group.config.number(1530)
|
||||||
|
//
|
||||||
|
// GlobalScope.launch {
|
||||||
|
// val random = Random(111)
|
||||||
|
// while (isActive) {
|
||||||
|
// delay(1000)
|
||||||
|
// group.color(random.nextInt(0, Int.MAX_VALUE))
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//// canvas.apply {
|
||||||
|
//// angleY = -30.0
|
||||||
|
//// angleX = -15.0
|
||||||
|
//// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
launch<FXDemoApp>()
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package hep.dataforge.vis.spatial.demo
|
||||||
|
|
||||||
|
import hep.dataforge.context.Global
|
||||||
|
import hep.dataforge.meta.Meta
|
||||||
|
import hep.dataforge.meta.buildMeta
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
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.fx.FX3DPlugin
|
||||||
|
import hep.dataforge.vis.spatial.fx.FXCanvas3D
|
||||||
|
import hep.dataforge.vis.spatial.render
|
||||||
|
import javafx.collections.FXCollections
|
||||||
|
import javafx.scene.Parent
|
||||||
|
import javafx.scene.control.Tab
|
||||||
|
import tornadofx.*
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
class FXDemoGrid : View(), OutputManager {
|
||||||
|
private val outputs = FXCollections.observableHashMap<Name, FXCanvas3D>()
|
||||||
|
|
||||||
|
override val root: Parent = borderpane {
|
||||||
|
center = tabpane {
|
||||||
|
tabs.bind(outputs) { key: Name, value: FXCanvas3D ->
|
||||||
|
Tab(key.toString(), value.root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val fx3d = Global.plugins.fetch(FX3DPlugin)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
|
||||||
|
return outputs.getOrPut(name) {
|
||||||
|
if (type != VisualObject::class) kotlin.error("Supports only DisplayObject")
|
||||||
|
val customMeta = buildMeta(meta) {
|
||||||
|
"minSize" put 500
|
||||||
|
"axis" put {
|
||||||
|
"size" put 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val output = FXCanvas3D(fx3d, customMeta)
|
||||||
|
|
||||||
|
output
|
||||||
|
} as Renderer<T>
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FXDemoGrid.demo(name: String, title: String = name, block: VisualGroup3D.() -> Unit) {
|
||||||
|
val meta = buildMeta {
|
||||||
|
"title" put title
|
||||||
|
}
|
||||||
|
val output = get(VisualObject::class, name.toName(), meta = meta)
|
||||||
|
output.render(action = block)
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.demo
|
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
|
||||||
import hep.dataforge.meta.number
|
|
||||||
import hep.dataforge.vis.spatial.*
|
|
||||||
import hep.dataforge.vis.spatial.fx.Canvas3D
|
|
||||||
import hep.dataforge.vis.spatial.fx.FX3DPlugin
|
|
||||||
import javafx.scene.Parent
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import tornadofx.*
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
class SpatialDemoApp: App(SpatialDemoView::class)
|
|
||||||
|
|
||||||
class SpatialDemoView: View(){
|
|
||||||
private val plugin = Global.plugins.fetch(FX3DPlugin)
|
|
||||||
private val canvas = Canvas3D(plugin)
|
|
||||||
|
|
||||||
override val root: Parent = borderpane {
|
|
||||||
center = canvas.root
|
|
||||||
}
|
|
||||||
|
|
||||||
lateinit var group: VisualGroup3D
|
|
||||||
|
|
||||||
init {
|
|
||||||
canvas.render {
|
|
||||||
box(100,100,100)
|
|
||||||
group = group {
|
|
||||||
box(100,100,100)
|
|
||||||
box(100,100,100) {
|
|
||||||
x = 110.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var color by group.config.number(1530)
|
|
||||||
|
|
||||||
GlobalScope.launch {
|
|
||||||
val random = Random(111)
|
|
||||||
while (isActive) {
|
|
||||||
delay(1000)
|
|
||||||
color = random.nextInt(0, Int.MAX_VALUE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.apply {
|
|
||||||
angleY = -30.0
|
|
||||||
angleX = -15.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
launch<SpatialDemoApp>()
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial.fx
|
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import hep.dataforge.vis.common.VisualObject
|
|
||||||
import javafx.beans.binding.ObjectBinding
|
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A caching binding collection for [VisualObject] properties
|
|
||||||
*/
|
|
||||||
class DisplayObjectFXBinding(val obj: VisualObject) {
|
|
||||||
private val binndings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
|
|
||||||
|
|
||||||
init {
|
|
||||||
obj.onPropertyChange(this) { name, _, _ ->
|
|
||||||
binndings[name]?.invalidate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
|
||||||
return binndings.getOrPut(key) {
|
|
||||||
object : ObjectBinding<MetaItem<*>?>() {
|
|
||||||
override fun computeValue(): MetaItem<*>? = obj.getProperty(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
operator fun get(key: String) = get(key.toName())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.value() = this.objectBinding { it.value }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.string() = this.stringBinding { it.string }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.number() = this.objectBinding { it.number }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.double() = this.objectBinding { it.double }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.float() = this.objectBinding { it.number?.toFloat() }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.int() = this.objectBinding { it.int }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.long() = this.objectBinding { it.long }
|
|
||||||
fun ObjectBinding<MetaItem<*>?>.node() = this.objectBinding { it.node }
|
|
||||||
|
|
||||||
fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = this.objectBinding { it?.let(transform) }
|
|
@ -12,6 +12,8 @@ import javafx.scene.shape.Shape3D
|
|||||||
import javafx.scene.transform.Rotate
|
import javafx.scene.transform.Rotate
|
||||||
import org.fxyz3d.shapes.composites.PolyLine3D
|
import org.fxyz3d.shapes.composites.PolyLine3D
|
||||||
import org.fxyz3d.shapes.primitives.CuboidMesh
|
import org.fxyz3d.shapes.primitives.CuboidMesh
|
||||||
|
import org.fxyz3d.shapes.primitives.SpheroidMesh
|
||||||
|
import kotlin.math.PI
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FX3DPlugin : AbstractPlugin() {
|
class FX3DPlugin : AbstractPlugin() {
|
||||||
@ -33,13 +35,27 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
as FX3DFactory<VisualObject3D>?
|
as FX3DFactory<VisualObject3D>?
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildNode(obj: VisualObject3D): Node? {
|
fun buildNode(obj: VisualObject3D): Node {
|
||||||
val binding = DisplayObjectFXBinding(obj)
|
val binding = VisualObjectFXBinding(obj)
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
is Proxy -> proxyFactory(obj, binding)
|
is Proxy -> proxyFactory(obj, binding)
|
||||||
is VisualGroup3D -> Group(obj.filterIsInstance<VisualObject3D>().map { buildNode(it) })
|
is VisualGroup3D -> {
|
||||||
|
Group(obj.children.mapNotNull { (token, obj) ->
|
||||||
|
(obj as? VisualObject3D)?.let {
|
||||||
|
buildNode(it).apply {
|
||||||
|
properties["name"] = token.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
is Composite -> compositeFactory(obj, binding)
|
is Composite -> compositeFactory(obj, binding)
|
||||||
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble())
|
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble())
|
||||||
|
is Sphere -> if (obj.phi == PI2 && obj.theta == PI.toFloat()) {
|
||||||
|
//use sphere for orb
|
||||||
|
SpheroidMesh(obj.detail ?: 16, obj.radius.toDouble(), obj.radius.toDouble())
|
||||||
|
} else {
|
||||||
|
FXShapeFactory(obj, binding)
|
||||||
|
}
|
||||||
is PolyLine -> PolyLine3D(
|
is PolyLine -> PolyLine3D(
|
||||||
obj.points.map { it.point },
|
obj.points.map { it.point },
|
||||||
obj.thickness.toFloat(),
|
obj.thickness.toFloat(),
|
||||||
@ -55,23 +71,23 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
translateXProperty().bind(binding[VisualObject3D.xPos].float())
|
translateXProperty().bind(binding[VisualObject3D.xPos].float(obj.x.toFloat()))
|
||||||
translateYProperty().bind(binding[VisualObject3D.yPos].float())
|
translateYProperty().bind(binding[VisualObject3D.yPos].float(obj.y.toFloat()))
|
||||||
translateZProperty().bind(binding[VisualObject3D.zPos].float())
|
translateZProperty().bind(binding[VisualObject3D.zPos].float(obj.z.toFloat()))
|
||||||
scaleXProperty().bind(binding[VisualObject3D.xScale].float())
|
scaleXProperty().bind(binding[VisualObject3D.xScale].float(obj.scaleX.toFloat()))
|
||||||
scaleYProperty().bind(binding[VisualObject3D.yScale].float())
|
scaleYProperty().bind(binding[VisualObject3D.yScale].float(obj.scaleY.toFloat()))
|
||||||
scaleZProperty().bind(binding[VisualObject3D.zScale].float())
|
scaleZProperty().bind(binding[VisualObject3D.zScale].float(obj.scaleZ.toFloat()))
|
||||||
|
|
||||||
val rotateX = Rotate(0.0, Rotate.X_AXIS).apply {
|
val rotateX = Rotate(0.0, Rotate.X_AXIS).apply {
|
||||||
angleProperty().bind(binding[VisualObject3D.xRotation].float())
|
angleProperty().bind(binding[VisualObject3D.xRotation].float(obj.rotationX.toFloat()))
|
||||||
}
|
}
|
||||||
|
|
||||||
val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply {
|
val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply {
|
||||||
angleProperty().bind(binding[VisualObject3D.yRotation].float())
|
angleProperty().bind(binding[VisualObject3D.yRotation].float(obj.rotationY.toFloat()))
|
||||||
}
|
}
|
||||||
|
|
||||||
val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply {
|
val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply {
|
||||||
angleProperty().bind(binding[VisualObject3D.zRotation].float())
|
angleProperty().bind(binding[VisualObject3D.zRotation].float(obj.rotationZ.toFloat()))
|
||||||
}
|
}
|
||||||
|
|
||||||
when (obj.rotationOrder) {
|
when (obj.rotationOrder) {
|
||||||
@ -84,7 +100,9 @@ class FX3DPlugin : AbstractPlugin() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this is Shape3D) {
|
if (this is Shape3D) {
|
||||||
materialProperty().bind(binding[Material3D.MATERIAL_KEY].transform { it.material() })
|
materialProperty().bind(binding[Material3D.MATERIAL_KEY].transform {
|
||||||
|
it.material()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +122,7 @@ interface FX3DFactory<in T : VisualObject3D> {
|
|||||||
|
|
||||||
val type: KClass<in T>
|
val type: KClass<in T>
|
||||||
|
|
||||||
operator fun invoke(obj: T, binding: DisplayObjectFXBinding): Node
|
operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TYPE = "fx3DFactory"
|
const val TYPE = "fx3DFactory"
|
||||||
|
@ -2,15 +2,13 @@ package hep.dataforge.vis.spatial.fx
|
|||||||
|
|
||||||
import hep.dataforge.context.Context
|
import hep.dataforge.context.Context
|
||||||
import hep.dataforge.context.ContextAware
|
import hep.dataforge.context.ContextAware
|
||||||
import hep.dataforge.meta.EmptyMeta
|
import hep.dataforge.meta.*
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.output.Renderer
|
import hep.dataforge.output.Renderer
|
||||||
import hep.dataforge.vis.spatial.VisualObject3D
|
import hep.dataforge.vis.spatial.VisualObject3D
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_FAR_CLIP
|
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_DISTANCE
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_X_ANGLE
|
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_X_ANGLE
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Y_ANGLE
|
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Y_ANGLE
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Z_ANGLE
|
|
||||||
import hep.dataforge.vis.spatial.World.CAMERA_NEAR_CLIP
|
import hep.dataforge.vis.spatial.World.CAMERA_NEAR_CLIP
|
||||||
import javafx.event.EventHandler
|
import javafx.event.EventHandler
|
||||||
import javafx.scene.*
|
import javafx.scene.*
|
||||||
@ -20,19 +18,22 @@ import javafx.scene.input.MouseEvent
|
|||||||
import javafx.scene.input.ScrollEvent
|
import javafx.scene.input.ScrollEvent
|
||||||
import javafx.scene.paint.Color
|
import javafx.scene.paint.Color
|
||||||
import org.fxyz3d.scene.Axes
|
import org.fxyz3d.scene.Axes
|
||||||
import org.fxyz3d.scene.CubeWorld
|
|
||||||
import org.fxyz3d.utils.CameraTransformer
|
import org.fxyz3d.utils.CameraTransformer
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
||||||
Fragment(), Renderer<VisualObject3D>, ContextAware {
|
Fragment(), Renderer<VisualObject3D>, ContextAware {
|
||||||
|
|
||||||
override val context: Context get() = plugin.context
|
override val context: Context get() = plugin.context
|
||||||
val world = CubeWorld(true)
|
|
||||||
|
val world = Group()
|
||||||
|
|
||||||
val axes = Axes().also {
|
val axes = Axes().also {
|
||||||
it.setHeight(AXIS_LENGTH)
|
it.setHeight(meta["axis.size"].double ?: AXIS_LENGTH)
|
||||||
it.setRadius(LINE_WIDTH)
|
it.setRadius(meta["axis.width"].double ?: LINE_WIDTH)
|
||||||
|
it.isVisible = meta["axis.visible"].boolean ?: (meta["axis"] != null)
|
||||||
world.add(it)
|
world.add(it)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val camera = PerspectiveCamera().apply {
|
private val camera = PerspectiveCamera().apply {
|
||||||
@ -41,43 +42,32 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
|||||||
translateZ = CAMERA_INITIAL_DISTANCE
|
translateZ = CAMERA_INITIAL_DISTANCE
|
||||||
}
|
}
|
||||||
|
|
||||||
private val cameraShift = CameraTransformer().apply {
|
val cameraTransform = CameraTransformer().also {
|
||||||
val cameraFlip = CameraTransformer()
|
it.add(camera)
|
||||||
cameraFlip.children.add(camera)
|
|
||||||
cameraFlip.setRotateZ(180.0)
|
|
||||||
children.add(cameraFlip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val translationXProperty get() = cameraShift.t.xProperty()
|
val translationXProperty get() = cameraTransform.t.xProperty()
|
||||||
var translateX by translationXProperty
|
var translateX by translationXProperty
|
||||||
val translationYProperty get() = cameraShift.t.yProperty()
|
val translationYProperty get() = cameraTransform.t.yProperty()
|
||||||
var translateY by translationYProperty
|
var translateY by translationYProperty
|
||||||
val translationZProperty get() = cameraShift.t.zProperty()
|
val translationZProperty get() = cameraTransform.t.zProperty()
|
||||||
var translateZ by translationZProperty
|
var translateZ by translationZProperty
|
||||||
|
|
||||||
private val cameraRotation = CameraTransformer().apply {
|
val rotationXProperty get() = cameraTransform.rx.angleProperty()
|
||||||
children.add(cameraShift)
|
|
||||||
ry.angle = CAMERA_INITIAL_Y_ANGLE
|
|
||||||
rx.angle = CAMERA_INITIAL_X_ANGLE
|
|
||||||
rz.angle = CAMERA_INITIAL_Z_ANGLE
|
|
||||||
}
|
|
||||||
|
|
||||||
val rotationXProperty get() = cameraRotation.rx.angleProperty()
|
|
||||||
var angleX by rotationXProperty
|
var angleX by rotationXProperty
|
||||||
val rotationYProperty get() = cameraRotation.ry.angleProperty()
|
val rotationYProperty get() = cameraTransform.ry.angleProperty()
|
||||||
var angleY by rotationYProperty
|
var angleY by rotationYProperty
|
||||||
val rotationZProperty get() = cameraRotation.rz.angleProperty()
|
val rotationZProperty get() = cameraTransform.rz.angleProperty()
|
||||||
var angleZ by rotationZProperty
|
var angleZ by rotationZProperty
|
||||||
|
|
||||||
|
|
||||||
override val root = borderpane {
|
override val root = borderpane {
|
||||||
center = SubScene(
|
center = SubScene(
|
||||||
Group(world, cameraRotation).apply { DepthTest.ENABLE },
|
Group(world, cameraTransform).apply { DepthTest.ENABLE },
|
||||||
1024.0,
|
1024.0,
|
||||||
768.0,
|
768.0,
|
||||||
true,
|
true,
|
||||||
SceneAntialiasing.BALANCED
|
SceneAntialiasing.BALANCED
|
||||||
).also {scene->
|
).also { scene ->
|
||||||
scene.fill = Color.GREY
|
scene.fill = Color.GREY
|
||||||
scene.camera = camera
|
scene.camera = camera
|
||||||
id = "canvas"
|
id = "canvas"
|
||||||
@ -92,11 +82,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
|||||||
if (event.isControlDown) {
|
if (event.isControlDown) {
|
||||||
when (event.code) {
|
when (event.code) {
|
||||||
KeyCode.Z -> {
|
KeyCode.Z -> {
|
||||||
cameraShift.t.x = 0.0
|
translateX = 0.0
|
||||||
cameraShift.t.y = 0.0
|
translateY = 0.0
|
||||||
camera.translateZ = CAMERA_INITIAL_DISTANCE
|
camera.translateZ = CAMERA_INITIAL_DISTANCE
|
||||||
cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE
|
angleY = CAMERA_INITIAL_Y_ANGLE
|
||||||
cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE
|
angleX = CAMERA_INITIAL_X_ANGLE
|
||||||
}
|
}
|
||||||
KeyCode.X -> axes.isVisible = !axes.isVisible
|
KeyCode.X -> axes.isVisible = !axes.isVisible
|
||||||
// KeyCode.S -> snapshot()
|
// KeyCode.S -> snapshot()
|
||||||
@ -153,13 +143,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (me.isPrimaryButtonDown) {
|
if (me.isPrimaryButtonDown) {
|
||||||
cameraRotation.ry.angle =
|
angleY += mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||||
cameraRotation.ry.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
|
angleX += mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||||
cameraRotation.rx.angle =
|
|
||||||
cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
|
|
||||||
} else if (me.isSecondaryButtonDown) {
|
} else if (me.isSecondaryButtonDown) {
|
||||||
cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
|
translateX -= mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||||
cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
|
translateY -= mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scene.onScroll = EventHandler<ScrollEvent> { event ->
|
scene.onScroll = EventHandler<ScrollEvent> { event ->
|
||||||
@ -171,11 +159,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
|||||||
|
|
||||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||||
val node = plugin.buildNode(obj) ?: kotlin.error("Can't render FX node for object $obj")
|
val node = plugin.buildNode(obj) ?: kotlin.error("Can't render FX node for object $obj")
|
||||||
world.add(node)
|
world.children.add(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val AXIS_LENGTH = 2000.0
|
private const val AXIS_LENGTH = 400.0
|
||||||
private const val CONTROL_MULTIPLIER = 0.1
|
private const val CONTROL_MULTIPLIER = 0.1
|
||||||
private const val SHIFT_MULTIPLIER = 10.0
|
private const val SHIFT_MULTIPLIER = 10.0
|
||||||
private const val MOUSE_SPEED = 0.1
|
private const val MOUSE_SPEED = 0.1
|
@ -14,7 +14,7 @@ class FXCompositeFactory(val plugin: FX3DPlugin) :
|
|||||||
override val type: KClass<in Composite>
|
override val type: KClass<in Composite>
|
||||||
get() = Composite::class
|
get() = Composite::class
|
||||||
|
|
||||||
override fun invoke(obj: Composite, binding: DisplayObjectFXBinding): Node {
|
override fun invoke(obj: Composite, binding: VisualObjectFXBinding): Node {
|
||||||
val first = plugin.buildNode(obj.first) as? MeshView ?: error("Can't build 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 second = plugin.buildNode(obj.second) as? MeshView ?: error("Can't build node")
|
||||||
val firstCSG = MeshUtils.mesh2CSG(first)
|
val firstCSG = MeshUtils.mesh2CSG(first)
|
||||||
|
@ -11,7 +11,7 @@ import kotlin.reflect.KClass
|
|||||||
object FXConvexFactory : FX3DFactory<Convex> {
|
object FXConvexFactory : FX3DFactory<Convex> {
|
||||||
override val type: KClass<in Convex> get() = Convex::class
|
override val type: KClass<in Convex> get() = Convex::class
|
||||||
|
|
||||||
override fun invoke(obj: Convex, binding: DisplayObjectFXBinding): Node {
|
override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node {
|
||||||
val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage())
|
val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage())
|
||||||
return hull.toNode()
|
return hull.toNode()
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import javafx.scene.paint.PhongMaterial
|
|||||||
object FXMaterials {
|
object FXMaterials {
|
||||||
val RED = PhongMaterial().apply {
|
val RED = PhongMaterial().apply {
|
||||||
diffuseColor = Color.DARKRED
|
diffuseColor = Color.DARKRED
|
||||||
specularColor = Color.RED
|
specularColor = Color.WHITE
|
||||||
}
|
}
|
||||||
|
|
||||||
val WHITE = PhongMaterial().apply {
|
val WHITE = PhongMaterial().apply {
|
||||||
@ -22,7 +22,7 @@ object FXMaterials {
|
|||||||
|
|
||||||
val GREY = PhongMaterial().apply {
|
val GREY = PhongMaterial().apply {
|
||||||
diffuseColor = Color.DARKGREY
|
diffuseColor = Color.DARKGREY
|
||||||
specularColor = Color.GREY
|
specularColor = Color.WHITE
|
||||||
}
|
}
|
||||||
|
|
||||||
val BLUE = PhongMaterial(Color.BLUE)
|
val BLUE = PhongMaterial(Color.BLUE)
|
||||||
@ -34,14 +34,14 @@ object FXMaterials {
|
|||||||
*/
|
*/
|
||||||
fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||||
Color.web(this.value.string)
|
|
||||||
} else {
|
|
||||||
val int = value.number.toInt()
|
val int = value.number.toInt()
|
||||||
val red = int and 0x00ff0000 shr 16
|
val red = int and 0x00ff0000 shr 16
|
||||||
val green = int and 0x0000ff00 shr 8
|
val green = int and 0x0000ff00 shr 8
|
||||||
val blue = int and 0x000000ff
|
val blue = int and 0x000000ff
|
||||||
Color.rgb(red, green, blue)
|
Color.rgb(red, green, blue)
|
||||||
|
} else {
|
||||||
|
Color.web(this.value.string)
|
||||||
}
|
}
|
||||||
is MetaItem.NodeItem -> {
|
is MetaItem.NodeItem -> {
|
||||||
Color.rgb(
|
Color.rgb(
|
||||||
@ -63,9 +63,8 @@ fun MetaItem<*>?.material(): Material {
|
|||||||
is MetaItem.ValueItem -> PhongMaterial(color())
|
is MetaItem.ValueItem -> PhongMaterial(color())
|
||||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
is MetaItem.NodeItem -> PhongMaterial().apply {
|
||||||
val opacity = node["opacity"].double ?: 1.0
|
val opacity = node["opacity"].double ?: 1.0
|
||||||
(node["color"] ?: this@material).let { diffuseColor = it.color(opacity) }
|
diffuseColor = node["color"]?.color(opacity) ?: Color.DARKGREY
|
||||||
node["specularColor"]?.let { specularColor = it.color(opacity) }
|
specularColor = node["specularColor"]?.color(opacity) ?: Color.WHITE
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,50 @@
|
|||||||
package hep.dataforge.vis.spatial.fx
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
|
import hep.dataforge.names.startsWith
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import hep.dataforge.vis.spatial.Material3D
|
||||||
import hep.dataforge.vis.spatial.Proxy
|
import hep.dataforge.vis.spatial.Proxy
|
||||||
|
import javafx.scene.Group
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.shape.Shape3D
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
class FXProxyFactory(val plugin: FX3DPlugin) :
|
class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory<Proxy> {
|
||||||
FX3DFactory<Proxy> {
|
|
||||||
override val type: KClass<in Proxy> get() = Proxy::class
|
override val type: KClass<in Proxy> get() = Proxy::class
|
||||||
|
|
||||||
override fun invoke(obj: Proxy, binding: DisplayObjectFXBinding): Node {
|
override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node {
|
||||||
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
|
val template = obj.prototype
|
||||||
}
|
val node = plugin.buildNode(template)
|
||||||
|
|
||||||
|
obj.onPropertyChange(this) { name, _, _ ->
|
||||||
|
if (name.first()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) {
|
||||||
|
val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
|
||||||
|
val propertyName = name.cutFirst()
|
||||||
|
val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found")
|
||||||
|
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||||
|
child.updateProperty(proxyChild, propertyName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Node.findChild(name: Name): Node? {
|
||||||
|
return if (name.isEmpty()) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
(this as? Group)
|
||||||
|
?.children
|
||||||
|
?.find { it.properties["name"] as String == name.first()?.toString() }
|
||||||
|
?.findChild(name.cutFirst())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Node.updateProperty(obj: VisualObject, propertyName: Name) {
|
||||||
|
if (propertyName.startsWith(Material3D.MATERIAL_KEY)) {
|
||||||
|
(this as? Shape3D)?.let { it.material = obj.getProperty(Material3D.MATERIAL_KEY).material() }
|
||||||
|
}
|
||||||
}
|
}
|
@ -13,7 +13,7 @@ import kotlin.reflect.KClass
|
|||||||
object FXShapeFactory : FX3DFactory<Shape> {
|
object FXShapeFactory : FX3DFactory<Shape> {
|
||||||
override val type: KClass<in Shape> get() = Shape::class
|
override val type: KClass<in Shape> get() = Shape::class
|
||||||
|
|
||||||
override fun invoke(obj: Shape, binding: DisplayObjectFXBinding): MeshView {
|
override fun invoke(obj: Shape, binding: VisualObjectFXBinding): MeshView {
|
||||||
val mesh = FXGeometryBuilder().apply { obj.toGeometry(this) }.build()
|
val mesh = FXGeometryBuilder().apply { obj.toGeometry(this) }.build()
|
||||||
return MeshView(mesh)
|
return MeshView(mesh)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
package hep.dataforge.vis.spatial.fx
|
||||||
|
|
||||||
|
import hep.dataforge.meta.*
|
||||||
|
import hep.dataforge.names.Name
|
||||||
|
import hep.dataforge.names.isEmpty
|
||||||
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.vis.common.VisualObject
|
||||||
|
import javafx.beans.binding.ObjectBinding
|
||||||
|
import tornadofx.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A caching binding collection for [VisualObject] properties
|
||||||
|
*/
|
||||||
|
class VisualObjectFXBinding(val obj: VisualObject) {
|
||||||
|
private val bindings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
obj.onPropertyChange(this) { name, _, _ ->
|
||||||
|
var currentName = name
|
||||||
|
while(!currentName.isEmpty()) {
|
||||||
|
//recursively update all upper level bindings
|
||||||
|
bindings[currentName]?.invalidate()
|
||||||
|
currentName = currentName.cutLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
||||||
|
return bindings.getOrPut(key) {
|
||||||
|
object : ObjectBinding<MetaItem<*>?>() {
|
||||||
|
override fun computeValue(): MetaItem<*>? = obj.getProperty(key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun get(key: String) = get(key.toName())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.value() = objectBinding { it.value }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.string() = stringBinding { it.string }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.number() = objectBinding { it.number }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.double() = objectBinding { it.double }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.float() = objectBinding { it.float }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.int() = objectBinding { it.int }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.long() = objectBinding { it.long }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.node() = objectBinding { it.node }
|
||||||
|
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.string(default: String) = stringBinding { it.string ?: default }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.double(default: Double) = objectBinding { it.double ?: default }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.float(default: Float) = objectBinding { it.float ?: default }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.int(default: Int) = objectBinding { it.int ?: default }
|
||||||
|
fun ObjectBinding<MetaItem<*>?>.long(default: Long) = objectBinding { it.long ?:default }
|
||||||
|
|
||||||
|
fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = objectBinding { it?.let(transform) }
|
Loading…
Reference in New Issue
Block a user