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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -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,43 +42,32 @@ 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,
SceneAntialiasing.BALANCED
).also {scene->
).also { scene ->
scene.fill = Color.GREY
scene.camera = camera
id = "canvas"
@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

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