FX backend in progress
This commit is contained in:
parent
fd43ea4843
commit
0164534004
@ -1,7 +1,5 @@
|
||||
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
|
||||
*/
|
||||
@ -177,11 +175,6 @@ object Colors {
|
||||
const val yellow = 0xFFFF00
|
||||
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 colorToString(color: UByte): String{
|
||||
return color.toString(16).padStart(2,'0')
|
||||
|
@ -5,7 +5,6 @@ import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.applyStyle
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
||||
@ -53,7 +52,7 @@ class GDMLTransformer(val root: GDML) {
|
||||
val styleName = "material[${material.name}]"
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -22,6 +22,7 @@ kotlin {
|
||||
jvmMain {
|
||||
dependencies {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package hep.dataforge.vis.spatial
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_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 COLOR_KEY = MATERIAL_KEY + "color"
|
||||
val SPECULAR_COLOR = MATERIAL_KEY + "specularColor"
|
||||
val OPACITY_KEY = MATERIAL_KEY + "opacity"
|
||||
|
||||
}
|
||||
@ -29,9 +29,18 @@ fun VisualObject.color(rgb: String) {
|
||||
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?
|
||||
get() = getProperty(COLOR_KEY).string
|
||||
|
@ -26,7 +26,16 @@ class Sphere(
|
||||
override var scale: Point3D? = null
|
||||
|
||||
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.math.Color
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.math.max
|
||||
|
||||
|
||||
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 {
|
||||
return when (this) {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
||||
Color(this.value.string)
|
||||
} else {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
val int = value.number.toInt()
|
||||
Color(int)
|
||||
} else {
|
||||
Color(this.value.string)
|
||||
}
|
||||
is MetaItem.NodeItem -> {
|
||||
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 org.fxyz3d.shapes.composites.PolyLine3D
|
||||
import org.fxyz3d.shapes.primitives.CuboidMesh
|
||||
import org.fxyz3d.shapes.primitives.SpheroidMesh
|
||||
import kotlin.math.PI
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FX3DPlugin : AbstractPlugin() {
|
||||
@ -33,13 +35,27 @@ class FX3DPlugin : AbstractPlugin() {
|
||||
as FX3DFactory<VisualObject3D>?
|
||||
}
|
||||
|
||||
fun buildNode(obj: VisualObject3D): Node? {
|
||||
val binding = DisplayObjectFXBinding(obj)
|
||||
fun buildNode(obj: VisualObject3D): Node {
|
||||
val binding = VisualObjectFXBinding(obj)
|
||||
return when (obj) {
|
||||
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 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(
|
||||
obj.points.map { it.point },
|
||||
obj.thickness.toFloat(),
|
||||
@ -55,23 +71,23 @@ class FX3DPlugin : AbstractPlugin() {
|
||||
}
|
||||
}
|
||||
}.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())
|
||||
translateXProperty().bind(binding[VisualObject3D.xPos].float(obj.x.toFloat()))
|
||||
translateYProperty().bind(binding[VisualObject3D.yPos].float(obj.y.toFloat()))
|
||||
translateZProperty().bind(binding[VisualObject3D.zPos].float(obj.z.toFloat()))
|
||||
scaleXProperty().bind(binding[VisualObject3D.xScale].float(obj.scaleX.toFloat()))
|
||||
scaleYProperty().bind(binding[VisualObject3D.yScale].float(obj.scaleY.toFloat()))
|
||||
scaleZProperty().bind(binding[VisualObject3D.zScale].float(obj.scaleZ.toFloat()))
|
||||
|
||||
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 {
|
||||
angleProperty().bind(binding[VisualObject3D.yRotation].float())
|
||||
angleProperty().bind(binding[VisualObject3D.yRotation].float(obj.rotationY.toFloat()))
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -84,7 +100,9 @@ class FX3DPlugin : AbstractPlugin() {
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
operator fun invoke(obj: T, binding: DisplayObjectFXBinding): Node
|
||||
operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node
|
||||
|
||||
companion object {
|
||||
const val TYPE = "fx3DFactory"
|
||||
|
@ -2,15 +2,13 @@ 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.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
|
||||
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 javafx.event.EventHandler
|
||||
import javafx.scene.*
|
||||
@ -20,19 +18,22 @@ import javafx.scene.input.MouseEvent
|
||||
import javafx.scene.input.ScrollEvent
|
||||
import javafx.scene.paint.Color
|
||||
import org.fxyz3d.scene.Axes
|
||||
import org.fxyz3d.scene.CubeWorld
|
||||
import org.fxyz3d.utils.CameraTransformer
|
||||
import tornadofx.*
|
||||
|
||||
class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
||||
class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
||||
Fragment(), Renderer<VisualObject3D>, ContextAware {
|
||||
|
||||
override val context: Context get() = plugin.context
|
||||
val world = CubeWorld(true)
|
||||
|
||||
val world = Group()
|
||||
|
||||
val axes = Axes().also {
|
||||
it.setHeight(AXIS_LENGTH)
|
||||
it.setRadius(LINE_WIDTH)
|
||||
it.setHeight(meta["axis.size"].double ?: AXIS_LENGTH)
|
||||
it.setRadius(meta["axis.width"].double ?: LINE_WIDTH)
|
||||
it.isVisible = meta["axis.visible"].boolean ?: (meta["axis"] != null)
|
||||
world.add(it)
|
||||
|
||||
}
|
||||
|
||||
private val camera = PerspectiveCamera().apply {
|
||||
@ -41,38 +42,27 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
||||
translateZ = CAMERA_INITIAL_DISTANCE
|
||||
}
|
||||
|
||||
private val cameraShift = CameraTransformer().apply {
|
||||
val cameraFlip = CameraTransformer()
|
||||
cameraFlip.children.add(camera)
|
||||
cameraFlip.setRotateZ(180.0)
|
||||
children.add(cameraFlip)
|
||||
val cameraTransform = CameraTransformer().also {
|
||||
it.add(camera)
|
||||
}
|
||||
|
||||
val translationXProperty get() = cameraShift.t.xProperty()
|
||||
val translationXProperty get() = cameraTransform.t.xProperty()
|
||||
var translateX by translationXProperty
|
||||
val translationYProperty get() = cameraShift.t.yProperty()
|
||||
val translationYProperty get() = cameraTransform.t.yProperty()
|
||||
var translateY by translationYProperty
|
||||
val translationZProperty get() = cameraShift.t.zProperty()
|
||||
val translationZProperty get() = cameraTransform.t.zProperty()
|
||||
var translateZ by translationZProperty
|
||||
|
||||
private val cameraRotation = CameraTransformer().apply {
|
||||
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()
|
||||
val rotationXProperty get() = cameraTransform.rx.angleProperty()
|
||||
var angleX by rotationXProperty
|
||||
val rotationYProperty get() = cameraRotation.ry.angleProperty()
|
||||
val rotationYProperty get() = cameraTransform.ry.angleProperty()
|
||||
var angleY by rotationYProperty
|
||||
val rotationZProperty get() = cameraRotation.rz.angleProperty()
|
||||
val rotationZProperty get() = cameraTransform.rz.angleProperty()
|
||||
var angleZ by rotationZProperty
|
||||
|
||||
|
||||
override val root = borderpane {
|
||||
center = SubScene(
|
||||
Group(world, cameraRotation).apply { DepthTest.ENABLE },
|
||||
Group(world, cameraTransform).apply { DepthTest.ENABLE },
|
||||
1024.0,
|
||||
768.0,
|
||||
true,
|
||||
@ -92,11 +82,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
||||
if (event.isControlDown) {
|
||||
when (event.code) {
|
||||
KeyCode.Z -> {
|
||||
cameraShift.t.x = 0.0
|
||||
cameraShift.t.y = 0.0
|
||||
translateX = 0.0
|
||||
translateY = 0.0
|
||||
camera.translateZ = CAMERA_INITIAL_DISTANCE
|
||||
cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE
|
||||
cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE
|
||||
angleY = CAMERA_INITIAL_Y_ANGLE
|
||||
angleX = CAMERA_INITIAL_X_ANGLE
|
||||
}
|
||||
KeyCode.X -> axes.isVisible = !axes.isVisible
|
||||
// KeyCode.S -> snapshot()
|
||||
@ -153,13 +143,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
||||
}
|
||||
|
||||
if (me.isPrimaryButtonDown) {
|
||||
cameraRotation.ry.angle =
|
||||
cameraRotation.ry.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||
cameraRotation.rx.angle =
|
||||
cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||
angleY += mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||
angleX += mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||
} else if (me.isSecondaryButtonDown) {
|
||||
cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||
cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||
translateX -= mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||
translateY -= mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||
}
|
||||
}
|
||||
scene.onScroll = EventHandler<ScrollEvent> { event ->
|
||||
@ -171,11 +159,11 @@ class Canvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
|
||||
|
||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||
val node = plugin.buildNode(obj) ?: kotlin.error("Can't render FX node for object $obj")
|
||||
world.add(node)
|
||||
world.children.add(node)
|
||||
}
|
||||
|
||||
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 SHIFT_MULTIPLIER = 10.0
|
||||
private const val MOUSE_SPEED = 0.1
|
@ -14,7 +14,7 @@ class FXCompositeFactory(val plugin: FX3DPlugin) :
|
||||
override val type: KClass<in Composite>
|
||||
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 second = plugin.buildNode(obj.second) as? MeshView ?: error("Can't build node")
|
||||
val firstCSG = MeshUtils.mesh2CSG(first)
|
||||
|
@ -11,7 +11,7 @@ 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 {
|
||||
override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node {
|
||||
val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage())
|
||||
return hull.toNode()
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import javafx.scene.paint.PhongMaterial
|
||||
object FXMaterials {
|
||||
val RED = PhongMaterial().apply {
|
||||
diffuseColor = Color.DARKRED
|
||||
specularColor = Color.RED
|
||||
specularColor = Color.WHITE
|
||||
}
|
||||
|
||||
val WHITE = PhongMaterial().apply {
|
||||
@ -22,7 +22,7 @@ object FXMaterials {
|
||||
|
||||
val GREY = PhongMaterial().apply {
|
||||
diffuseColor = Color.DARKGREY
|
||||
specularColor = Color.GREY
|
||||
specularColor = Color.WHITE
|
||||
}
|
||||
|
||||
val BLUE = PhongMaterial(Color.BLUE)
|
||||
@ -34,14 +34,14 @@ object FXMaterials {
|
||||
*/
|
||||
fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
||||
return when (this) {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
||||
Color.web(this.value.string)
|
||||
} else {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
val int = value.number.toInt()
|
||||
val red = int and 0x00ff0000 shr 16
|
||||
val green = int and 0x0000ff00 shr 8
|
||||
val blue = int and 0x000000ff
|
||||
Color.rgb(red, green, blue)
|
||||
} else {
|
||||
Color.web(this.value.string)
|
||||
}
|
||||
is MetaItem.NodeItem -> {
|
||||
Color.rgb(
|
||||
@ -63,9 +63,8 @@ fun MetaItem<*>?.material(): Material {
|
||||
is MetaItem.ValueItem -> PhongMaterial(color())
|
||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
||||
val opacity = node["opacity"].double ?: 1.0
|
||||
(node["color"] ?: this@material).let { diffuseColor = it.color(opacity) }
|
||||
node["specularColor"]?.let { specularColor = it.color(opacity) }
|
||||
|
||||
diffuseColor = node["color"]?.color(opacity) ?: Color.DARKGREY
|
||||
specularColor = node["specularColor"]?.color(opacity) ?: Color.WHITE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,50 @@
|
||||
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 javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.shape.Shape3D
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FXProxyFactory(val plugin: FX3DPlugin) :
|
||||
FX3DFactory<Proxy> {
|
||||
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.
|
||||
override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node {
|
||||
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> {
|
||||
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()
|
||||
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