FX backend in progress

This commit is contained in:
Alexander Nozik 2019-12-15 22:15:14 +03:00
parent fd43ea4843
commit 0164534004
18 changed files with 467 additions and 188 deletions

View File

@ -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')

View File

@ -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
} }

View File

@ -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")
} }
} }

View File

@ -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

View File

@ -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()
// }
// }
} }
} }

View File

@ -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(

View File

@ -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>()
}

View File

@ -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)
}

View File

@ -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>()
}

View File

@ -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) }

View File

@ -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"

View File

@ -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,38 +42,27 @@ 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,
@ -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

View File

@ -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)

View File

@ -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()
} }

View File

@ -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
} }
} }
} }

View File

@ -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() }
}
} }

View File

@ -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)
} }

View File

@ -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) }