Moved visualization to a separate project
This commit is contained in:
parent
f8266d35c2
commit
e5e399e041
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,3 +7,4 @@
|
|||||||
|
|
||||||
|
|
||||||
!gradle-wrapper.jar
|
!gradle-wrapper.jar
|
||||||
|
gradle.properties
|
@ -7,6 +7,7 @@ import hep.dataforge.names.Name
|
|||||||
import hep.dataforge.names.NameToken
|
import hep.dataforge.names.NameToken
|
||||||
import hep.dataforge.names.plus
|
import hep.dataforge.names.plus
|
||||||
import hep.dataforge.names.toName
|
import hep.dataforge.names.toName
|
||||||
|
import hep.dataforge.values.EnumValue
|
||||||
import hep.dataforge.values.Value
|
import hep.dataforge.values.Value
|
||||||
import hep.dataforge.values.boolean
|
import hep.dataforge.values.boolean
|
||||||
|
|
||||||
@ -57,7 +58,8 @@ interface Meta : MetaRepr {
|
|||||||
*/
|
*/
|
||||||
operator fun <T> Map<NameToken, T>.get(body: String, query: String = ""): T? = get(NameToken(body, query))
|
operator fun <T> Map<NameToken, T>.get(body: String, query: String = ""): T? = get(NameToken(body, query))
|
||||||
|
|
||||||
operator fun Meta.get(name: Name): MetaItem<out Meta>? {
|
operator fun Meta?.get(name: Name): MetaItem<out Meta>? {
|
||||||
|
if (this == null) return null
|
||||||
return name.first()?.let { token ->
|
return name.first()?.let { token ->
|
||||||
val tail = name.cutFirst()
|
val tail = name.cutFirst()
|
||||||
when (tail.length) {
|
when (tail.length) {
|
||||||
@ -67,8 +69,8 @@ operator fun Meta.get(name: Name): MetaItem<out Meta>? {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun Meta.get(token: NameToken): MetaItem<out Meta>? = items[token]
|
operator fun Meta?.get(token: NameToken): MetaItem<out Meta>? = this?.items?.get(token)
|
||||||
operator fun Meta.get(key: String): MetaItem<out Meta>? = get(key.toName())
|
operator fun Meta?.get(key: String): MetaItem<out Meta>? = get(key.toName())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all items matching given name.
|
* Get all items matching given name.
|
||||||
@ -168,10 +170,17 @@ val MetaItem<*>?.string get() = value?.string
|
|||||||
val MetaItem<*>?.boolean get() = value?.boolean
|
val MetaItem<*>?.boolean get() = value?.boolean
|
||||||
val MetaItem<*>?.number get() = value?.number
|
val MetaItem<*>?.number get() = value?.number
|
||||||
val MetaItem<*>?.double get() = number?.toDouble()
|
val MetaItem<*>?.double get() = number?.toDouble()
|
||||||
|
val MetaItem<*>?.float get() = number?.toFloat()
|
||||||
val MetaItem<*>?.int get() = number?.toInt()
|
val MetaItem<*>?.int get() = number?.toInt()
|
||||||
val MetaItem<*>?.long get() = number?.toLong()
|
val MetaItem<*>?.long get() = number?.toLong()
|
||||||
val MetaItem<*>?.short get() = number?.toShort()
|
val MetaItem<*>?.short get() = number?.toShort()
|
||||||
|
|
||||||
|
inline fun <reified E : Enum<E>> MetaItem<*>?.enum() = if (this is ValueItem && this.value is EnumValue<*>) {
|
||||||
|
this.value as E
|
||||||
|
} else {
|
||||||
|
string?.let { enumValueOf<E>(it) }
|
||||||
|
}
|
||||||
|
|
||||||
val MetaItem<*>?.stringList get() = value?.list?.map { it.string } ?: emptyList()
|
val MetaItem<*>?.stringList get() = value?.list?.map { it.string } ?: emptyList()
|
||||||
|
|
||||||
val <M : Meta> MetaItem<M>?.node: M?
|
val <M : Meta> MetaItem<M>?.node: M?
|
||||||
|
@ -34,7 +34,7 @@ class Styled(val base: Meta, val style: Config = Config().empty()) : MutableMeta
|
|||||||
if (item == null) {
|
if (item == null) {
|
||||||
style.remove(name)
|
style.remove(name)
|
||||||
} else {
|
} else {
|
||||||
style.set(name, item)
|
style[name] = item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvm()
|
|
||||||
js()
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
val commonMain by getting {
|
|
||||||
dependencies {
|
|
||||||
api(project(":dataforge-io"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jvmMain by getting {
|
|
||||||
dependencies {
|
|
||||||
//api("no.tornado:tornadofx:1.7.18")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsMain by getting {
|
|
||||||
dependencies {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
import org.openjfx.gradle.JavaFXOptions
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
kotlin("jvm")
|
|
||||||
id("org.openjfx.javafxplugin")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(project(":dataforge-vis"))
|
|
||||||
api(project(":dataforge-vis:dataforge-vis-spatial"))
|
|
||||||
api("no.tornado:tornadofx:1.7.18")
|
|
||||||
implementation("org.fxyz3d:fxyz3d:0.4.0")
|
|
||||||
}
|
|
||||||
|
|
||||||
configure<JavaFXOptions> {
|
|
||||||
modules("javafx.controls")
|
|
||||||
}
|
|
@ -1,166 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import javafx.event.EventHandler
|
|
||||||
import javafx.scene.*
|
|
||||||
import javafx.scene.input.KeyCode
|
|
||||||
import javafx.scene.input.KeyEvent
|
|
||||||
import javafx.scene.input.MouseEvent
|
|
||||||
import javafx.scene.input.ScrollEvent
|
|
||||||
import javafx.scene.paint.Color
|
|
||||||
import org.fxyz3d.utils.CameraTransformer
|
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
class Canvas3D : Fragment() {
|
|
||||||
val world: Group = Group()
|
|
||||||
|
|
||||||
private val camera = PerspectiveCamera().apply {
|
|
||||||
nearClip = CAMERA_NEAR_CLIP
|
|
||||||
farClip = CAMERA_FAR_CLIP
|
|
||||||
translateZ = CAMERA_INITIAL_DISTANCE
|
|
||||||
}
|
|
||||||
|
|
||||||
private val cameraShift = CameraTransformer().apply {
|
|
||||||
val cameraFlip = CameraTransformer()
|
|
||||||
cameraFlip.children.add(camera)
|
|
||||||
cameraFlip.setRotateZ(180.0)
|
|
||||||
children.add(cameraFlip)
|
|
||||||
}
|
|
||||||
|
|
||||||
val translationXProperty get() = cameraShift.t.xProperty()
|
|
||||||
var translateX by translationXProperty
|
|
||||||
val translationYProperty get() = cameraShift.t.yProperty()
|
|
||||||
var translateY by translationYProperty
|
|
||||||
val translationZProperty get() = cameraShift.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()
|
|
||||||
var angleX by rotationXProperty
|
|
||||||
val rotationYProperty get() = cameraRotation.ry.angleProperty()
|
|
||||||
var angleY by rotationYProperty
|
|
||||||
val rotationZProperty get() = cameraRotation.rz.angleProperty()
|
|
||||||
var angleZ by rotationZProperty
|
|
||||||
|
|
||||||
|
|
||||||
override val root =borderpane {
|
|
||||||
center = SubScene(
|
|
||||||
Group(world, cameraRotation).apply { DepthTest.ENABLE },
|
|
||||||
1024.0,
|
|
||||||
768.0,
|
|
||||||
true,
|
|
||||||
SceneAntialiasing.BALANCED
|
|
||||||
).apply {
|
|
||||||
fill = Color.GREY
|
|
||||||
this.camera = this@Canvas3D.camera
|
|
||||||
id = "canvas"
|
|
||||||
handleKeyboard(this)
|
|
||||||
handleMouse(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun handleKeyboard(scene: SubScene) {
|
|
||||||
scene.onKeyPressed = EventHandler<KeyEvent> { event ->
|
|
||||||
if (event.isControlDown) {
|
|
||||||
when (event.code) {
|
|
||||||
KeyCode.Z -> {
|
|
||||||
cameraShift.t.x = 0.0
|
|
||||||
cameraShift.t.y = 0.0
|
|
||||||
camera.translateZ = CAMERA_INITIAL_DISTANCE
|
|
||||||
cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE
|
|
||||||
cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE
|
|
||||||
}
|
|
||||||
// KeyCode.X -> axisGroup.isVisible = !axisGroup.isVisible
|
|
||||||
// KeyCode.S -> snapshot()
|
|
||||||
// KeyCode.DIGIT1 -> pixelMap.filterKeys { it.getLayerNumber() == 1 }.values.forEach {
|
|
||||||
// toggleTransparency(
|
|
||||||
// it
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// KeyCode.DIGIT2 -> pixelMap.filterKeys { it.getLayerNumber() == 2 }.values.forEach {
|
|
||||||
// toggleTransparency(
|
|
||||||
// it
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// KeyCode.DIGIT3 -> pixelMap.filterKeys { it.getLayerNumber() == 3 }.values.forEach {
|
|
||||||
// toggleTransparency(
|
|
||||||
// it
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
else -> {
|
|
||||||
}//do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleMouse(scene: SubScene) {
|
|
||||||
|
|
||||||
var mousePosX: Double = 0.0
|
|
||||||
var mousePosY: Double = 0.0
|
|
||||||
var mouseOldX: Double = 0.0
|
|
||||||
var mouseOldY: Double = 0.0
|
|
||||||
var mouseDeltaX: Double = 0.0
|
|
||||||
var mouseDeltaY: Double = 0.0
|
|
||||||
|
|
||||||
scene.onMousePressed = EventHandler<MouseEvent> { me ->
|
|
||||||
mousePosX = me.sceneX
|
|
||||||
mousePosY = me.sceneY
|
|
||||||
mouseOldX = me.sceneX
|
|
||||||
mouseOldY = me.sceneY
|
|
||||||
}
|
|
||||||
|
|
||||||
scene.onMouseDragged = EventHandler<MouseEvent> { me ->
|
|
||||||
mouseOldX = mousePosX
|
|
||||||
mouseOldY = mousePosY
|
|
||||||
mousePosX = me.sceneX
|
|
||||||
mousePosY = me.sceneY
|
|
||||||
mouseDeltaX = mousePosX - mouseOldX
|
|
||||||
mouseDeltaY = mousePosY - mouseOldY
|
|
||||||
|
|
||||||
val modifier = when {
|
|
||||||
me.isControlDown -> CONTROL_MULTIPLIER
|
|
||||||
me.isShiftDown -> SHIFT_MULTIPLIER
|
|
||||||
else -> 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
if (me.isPrimaryButtonDown) {
|
|
||||||
cameraRotation.rz.angle =
|
|
||||||
cameraRotation.rz.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
|
|
||||||
cameraRotation.rx.angle =
|
|
||||||
cameraRotation.rx.angle + 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
scene.onScroll = EventHandler<ScrollEvent> { event ->
|
|
||||||
val z = camera.translateZ
|
|
||||||
val newZ = z + MOUSE_SPEED * event.deltaY * RESIZE_SPEED
|
|
||||||
camera.translateZ = newZ
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val CAMERA_INITIAL_DISTANCE = -4500.0
|
|
||||||
private const val CAMERA_INITIAL_X_ANGLE = -50.0
|
|
||||||
private const val CAMERA_INITIAL_Y_ANGLE = 0.0
|
|
||||||
private const val CAMERA_INITIAL_Z_ANGLE = -210.0
|
|
||||||
private const val CAMERA_NEAR_CLIP = 0.1
|
|
||||||
private const val CAMERA_FAR_CLIP = 10000.0
|
|
||||||
private const val AXIS_LENGTH = 2000.0
|
|
||||||
private const val CONTROL_MULTIPLIER = 0.1
|
|
||||||
private const val SHIFT_MULTIPLIER = 10.0
|
|
||||||
private const val MOUSE_SPEED = 0.1
|
|
||||||
private const val ROTATION_SPEED = 2.0
|
|
||||||
private const val TRACK_SPEED = 6.0
|
|
||||||
private const val RESIZE_SPEED = 50.0
|
|
||||||
private const val LINE_WIDTH = 3.0
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
|
||||||
import hep.dataforge.io.Output
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.vis.DisplayGroup
|
|
||||||
import hep.dataforge.vis.DisplayObjectPropertyListener
|
|
||||||
import hep.dataforge.vis.float
|
|
||||||
import hep.dataforge.vis.transform
|
|
||||||
import javafx.scene.Group
|
|
||||||
import javafx.scene.Node
|
|
||||||
import org.fxyz3d.shapes.primitives.CuboidMesh
|
|
||||||
import tornadofx.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://github.com/miho/JCSG for operations
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
class FX3DOutput(override val context: Context) : Output<Any> {
|
|
||||||
val canvas by lazy { Canvas3D() }
|
|
||||||
|
|
||||||
|
|
||||||
private fun buildNode(obj: Any): Node? {
|
|
||||||
return when (obj) {
|
|
||||||
is DisplayShape3D -> {
|
|
||||||
val listener = DisplayObjectPropertyListener(obj)
|
|
||||||
val x = listener["x"].float()
|
|
||||||
val y = listener["y"].float()
|
|
||||||
val z = listener["z"].float()
|
|
||||||
val center = objectBinding(x, y, z) {
|
|
||||||
org.fxyz3d.geometry.Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f)
|
|
||||||
}
|
|
||||||
when (obj) {
|
|
||||||
is DisplayGroup3D -> Group(obj.children.map { buildNode(it) }).apply {
|
|
||||||
this.translateXProperty().bind(x)
|
|
||||||
this.translateYProperty().bind(y)
|
|
||||||
this.translateZProperty().bind(z)
|
|
||||||
}
|
|
||||||
is Box -> CuboidMesh(obj.xSize, obj.ySize, obj.zSize).apply {
|
|
||||||
this.centerProperty().bind(center)
|
|
||||||
this.materialProperty().bind(listener["color"].transform { it.material() })
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
logger.error { "No renderer defined for ${obj::class}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is DisplayGroup -> Group(obj.children.map { buildNode(it) }) // a logical group
|
|
||||||
else -> {
|
|
||||||
logger.error { "No renderer defined for ${obj::class}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(obj: Any, meta: Meta) {
|
|
||||||
buildNode(obj)?.let { canvas.world.children.add(it) }
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.meta.MetaItem
|
|
||||||
import hep.dataforge.meta.double
|
|
||||||
import hep.dataforge.meta.get
|
|
||||||
import hep.dataforge.meta.int
|
|
||||||
import hep.dataforge.values.ValueType
|
|
||||||
import javafx.scene.paint.Color
|
|
||||||
import javafx.scene.paint.Material
|
|
||||||
import javafx.scene.paint.PhongMaterial
|
|
||||||
|
|
||||||
object Materials {
|
|
||||||
val RED = PhongMaterial().apply {
|
|
||||||
diffuseColor = Color.DARKRED
|
|
||||||
specularColor = Color.RED
|
|
||||||
}
|
|
||||||
|
|
||||||
val WHITE = PhongMaterial().apply {
|
|
||||||
diffuseColor = Color.WHITE
|
|
||||||
specularColor = Color.LIGHTBLUE
|
|
||||||
}
|
|
||||||
|
|
||||||
val GREY = PhongMaterial().apply {
|
|
||||||
diffuseColor = Color.DARKGREY
|
|
||||||
specularColor = Color.GREY
|
|
||||||
}
|
|
||||||
|
|
||||||
val BLUE = PhongMaterial(Color.BLUE)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infer color based on meta item
|
|
||||||
*/
|
|
||||||
fun MetaItem<*>.color(): Color {
|
|
||||||
return when (this) {
|
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
|
||||||
Color.web(this.value.string)
|
|
||||||
} else {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
is MetaItem.NodeItem -> {
|
|
||||||
Color.rgb(
|
|
||||||
node["red"]?.int ?: 0,
|
|
||||||
node["green"]?.int ?: 0,
|
|
||||||
node["blue"]?.int ?: 0,
|
|
||||||
node["opacity"]?.double ?: 1.0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infer FX material based on meta item
|
|
||||||
*/
|
|
||||||
fun MetaItem<*>?.material(): Material {
|
|
||||||
return when (this) {
|
|
||||||
null -> Materials.GREY
|
|
||||||
is MetaItem.ValueItem -> PhongMaterial(color())
|
|
||||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
|
||||||
(node["color"]?: this@material).let { diffuseColor = it.color() }
|
|
||||||
node["specularColor"]?.let { specularColor = it.color() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
|
||||||
import hep.dataforge.meta.number
|
|
||||||
import hep.dataforge.vis.DisplayGroup
|
|
||||||
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 RendererDemoApp : App(RendererDemoView::class)
|
|
||||||
|
|
||||||
|
|
||||||
class RendererDemoView : View() {
|
|
||||||
val renderer = FX3DOutput(Global)
|
|
||||||
override val root: Parent = borderpane {
|
|
||||||
center = renderer.canvas.root
|
|
||||||
}
|
|
||||||
|
|
||||||
lateinit var group: DisplayGroup
|
|
||||||
|
|
||||||
init {
|
|
||||||
|
|
||||||
renderer.render {
|
|
||||||
group = group {
|
|
||||||
box {
|
|
||||||
xSize = 100.0
|
|
||||||
ySize = 100.0
|
|
||||||
zSize = 100.0
|
|
||||||
}
|
|
||||||
box {
|
|
||||||
x = 110.0
|
|
||||||
xSize = 100.0
|
|
||||||
ySize = 100.0
|
|
||||||
zSize = 100.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var color by group.properties.number(1530).int
|
|
||||||
|
|
||||||
GlobalScope.launch {
|
|
||||||
val random = Random(111)
|
|
||||||
while (isActive) {
|
|
||||||
delay(1000)
|
|
||||||
color = random.nextInt(0, Int.MAX_VALUE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderer.canvas.apply {
|
|
||||||
angleY = -30.0
|
|
||||||
angleX = -15.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
launch<RendererDemoApp>()
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
import org.jetbrains.kotlin.gradle.frontend.KotlinFrontendExtension
|
|
||||||
import org.jetbrains.kotlin.gradle.frontend.npm.NpmExtension
|
|
||||||
import org.jetbrains.kotlin.gradle.frontend.webpack.WebPackExtension
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("kotlin2js")
|
|
||||||
id("kotlin-dce-js")
|
|
||||||
id("org.jetbrains.kotlin.frontend")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(project(":dataforge-vis:dataforge-vis-spatial"))
|
|
||||||
implementation("info.laht.threekt:threejs-wrapper:0.88-npm-1")
|
|
||||||
}
|
|
||||||
|
|
||||||
configure<KotlinFrontendExtension> {
|
|
||||||
downloadNodeJsVersion = "latest"
|
|
||||||
|
|
||||||
configure<NpmExtension> {
|
|
||||||
dependency("three")
|
|
||||||
dependency("three-orbitcontrols")
|
|
||||||
dependency("style-loader")
|
|
||||||
devDependency("karma")
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceMaps = true
|
|
||||||
|
|
||||||
bundle("webpack") {
|
|
||||||
this as WebPackExtension
|
|
||||||
bundleName = "main"
|
|
||||||
proxyUrl = "http://localhost:8080"
|
|
||||||
contentPath = file("src/main/web")
|
|
||||||
sourceMapEnabled = true
|
|
||||||
//mode = "production"
|
|
||||||
mode = "development"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks{
|
|
||||||
compileKotlin2Js{
|
|
||||||
kotlinOptions{
|
|
||||||
metaInfo = true
|
|
||||||
outputFile = "${project.buildDir.path}/js/${project.name}.js"
|
|
||||||
sourceMap = true
|
|
||||||
moduleKind = "umd"
|
|
||||||
main = "call"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
compileTestKotlin2Js{
|
|
||||||
kotlinOptions{
|
|
||||||
metaInfo = true
|
|
||||||
outputFile = "${project.buildDir.path}/js/${project.name}-test.js"
|
|
||||||
sourceMap = true
|
|
||||||
moduleKind = "umd"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"description": "A demo project for particle visualization in JS"
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package hep.dataforge.vis.hmr
|
|
||||||
|
|
||||||
external val module: Module
|
|
||||||
|
|
||||||
external interface Module {
|
|
||||||
val hot: Hot?
|
|
||||||
}
|
|
||||||
|
|
||||||
external interface Hot {
|
|
||||||
val data: dynamic
|
|
||||||
|
|
||||||
fun accept()
|
|
||||||
fun accept(dependency: String, callback: () -> Unit)
|
|
||||||
fun accept(dependencies: Array<String>, callback: (updated: Array<String>) -> Unit)
|
|
||||||
|
|
||||||
fun dispose(callback: (data: dynamic) -> Unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
external fun require(name: String): dynamic
|
|
@ -1,50 +0,0 @@
|
|||||||
package hep.dataforge.vis
|
|
||||||
|
|
||||||
import hep.dataforge.vis.hmr.module
|
|
||||||
import hep.dataforge.vis.spatial.ThreeDemoApp
|
|
||||||
import kotlin.browser.document
|
|
||||||
import kotlin.dom.hasClass
|
|
||||||
|
|
||||||
|
|
||||||
abstract class ApplicationBase {
|
|
||||||
abstract val stateKeys: List<String>
|
|
||||||
|
|
||||||
abstract fun start(state: Map<String, Any>)
|
|
||||||
abstract fun dispose(): Map<String, Any>
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun main() {
|
|
||||||
var application: ApplicationBase? = null
|
|
||||||
|
|
||||||
val state: dynamic = module.hot?.let { hot ->
|
|
||||||
hot.accept()
|
|
||||||
|
|
||||||
hot.dispose { data ->
|
|
||||||
data.appState = application?.dispose()
|
|
||||||
application = null
|
|
||||||
}
|
|
||||||
|
|
||||||
hot.data
|
|
||||||
}
|
|
||||||
|
|
||||||
if (document.body != null) {
|
|
||||||
application = start(state)
|
|
||||||
} else {
|
|
||||||
application = null
|
|
||||||
document.addEventListener("DOMContentLoaded", { application = start(state) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start(state: dynamic): ApplicationBase? {
|
|
||||||
return if (document.body?.hasClass("testApp") == true) {
|
|
||||||
val application = ThreeDemoApp()
|
|
||||||
|
|
||||||
@Suppress("UnsafeCastFromDynamic")
|
|
||||||
application.start(state?.appState ?: emptyMap())
|
|
||||||
|
|
||||||
application
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,59 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.meta.MetaItem
|
|
||||||
import hep.dataforge.meta.double
|
|
||||||
import hep.dataforge.meta.get
|
|
||||||
import hep.dataforge.meta.int
|
|
||||||
import hep.dataforge.values.ValueType
|
|
||||||
import info.laht.threekt.materials.Material
|
|
||||||
import info.laht.threekt.materials.MeshPhongMaterial
|
|
||||||
import info.laht.threekt.math.Color
|
|
||||||
import info.laht.threekt.math.ColorConstants
|
|
||||||
|
|
||||||
object Materials {
|
|
||||||
val DEFAULT = MeshPhongMaterial().apply {
|
|
||||||
this.color.set(ColorConstants.darkgreen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infer color based on meta item
|
|
||||||
*/
|
|
||||||
fun MetaItem<*>.color(): Color {
|
|
||||||
return when (this) {
|
|
||||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
|
||||||
Color(this.value.string)
|
|
||||||
} else {
|
|
||||||
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(red, green, blue)
|
|
||||||
}
|
|
||||||
is MetaItem.NodeItem -> {
|
|
||||||
Color(
|
|
||||||
node["red"]?.int ?: 0,
|
|
||||||
node["green"]?.int ?: 0,
|
|
||||||
node["blue"]?.int ?: 0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Infer FX material based on meta item
|
|
||||||
*/
|
|
||||||
fun MetaItem<*>?.material(): Material {
|
|
||||||
return when (this) {
|
|
||||||
null -> Materials.DEFAULT
|
|
||||||
is MetaItem.ValueItem -> MeshPhongMaterial().apply {
|
|
||||||
color = this@material.color()
|
|
||||||
}
|
|
||||||
is MetaItem.NodeItem -> MeshPhongMaterial().apply {
|
|
||||||
(node["color"] ?: this@material).let { color = it.color() }
|
|
||||||
opacity = node["opacity"]?.double ?: 1.0
|
|
||||||
node["specularColor"]?.let { specular = it.color() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,65 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.context.Global
|
|
||||||
import hep.dataforge.meta.number
|
|
||||||
import hep.dataforge.vis.ApplicationBase
|
|
||||||
import hep.dataforge.vis.DisplayGroup
|
|
||||||
import info.laht.threekt.external.controls.OrbitControls
|
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.isActive
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlin.browser.document
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
|
|
||||||
class ThreeDemoApp : ApplicationBase() {
|
|
||||||
|
|
||||||
override val stateKeys: List<String> = emptyList()
|
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
|
||||||
val renderer = ThreeOutput(Global)
|
|
||||||
renderer.start(document.getElementById("canvas")!!)
|
|
||||||
println("started")
|
|
||||||
|
|
||||||
lateinit var group: DisplayGroup
|
|
||||||
|
|
||||||
renderer.render {
|
|
||||||
group = group {
|
|
||||||
box {
|
|
||||||
xSize = 100.0
|
|
||||||
ySize = 100.0
|
|
||||||
zSize = 100.0
|
|
||||||
}
|
|
||||||
box {
|
|
||||||
x = 110.0
|
|
||||||
xSize = 100.0
|
|
||||||
ySize = 100.0
|
|
||||||
zSize = 100.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var color by group.properties.number(1530).int
|
|
||||||
|
|
||||||
GlobalScope.launch {
|
|
||||||
val random = Random(111)
|
|
||||||
while (isActive) {
|
|
||||||
delay(1000)
|
|
||||||
color = random.nextInt(0, Int.MAX_VALUE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// view.animate()
|
|
||||||
|
|
||||||
// view = WebLinesView(document.getElementById("lines")!!, document.getElementById("addForm")!!)
|
|
||||||
// presenter = LinesPresenter(view)
|
|
||||||
//
|
|
||||||
// state["lines"]?.let { linesState ->
|
|
||||||
// @Suppress("UNCHECKED_CAST")
|
|
||||||
// presenter.restore(linesState as Array<String>)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun dispose() = emptyMap<String,Any>()//mapOf("lines" to presenter.dispose())
|
|
||||||
}
|
|
@ -1,147 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.context.Context
|
|
||||||
import hep.dataforge.io.Output
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.meta.get
|
|
||||||
import hep.dataforge.vis.DisplayGroup
|
|
||||||
import info.laht.threekt.WebGLRenderer
|
|
||||||
import info.laht.threekt.cameras.PerspectiveCamera
|
|
||||||
import info.laht.threekt.core.BufferGeometry
|
|
||||||
import info.laht.threekt.core.Object3D
|
|
||||||
import info.laht.threekt.external.controls.OrbitControls
|
|
||||||
import info.laht.threekt.extras.curves.CatmullRomCurve3
|
|
||||||
import info.laht.threekt.geometries.BoxBufferGeometry
|
|
||||||
import info.laht.threekt.lights.AmbientLight
|
|
||||||
import info.laht.threekt.materials.LineBasicMaterial
|
|
||||||
import info.laht.threekt.materials.MeshBasicMaterial
|
|
||||||
import info.laht.threekt.materials.MeshPhongMaterial
|
|
||||||
import info.laht.threekt.math.ColorConstants
|
|
||||||
import info.laht.threekt.math.Vector3
|
|
||||||
import info.laht.threekt.objects.Line
|
|
||||||
import info.laht.threekt.objects.Mesh
|
|
||||||
import info.laht.threekt.scenes.Scene
|
|
||||||
import org.w3c.dom.Element
|
|
||||||
import kotlin.browser.window
|
|
||||||
|
|
||||||
class ThreeOutput(override val context: Context) : Output<Any> {
|
|
||||||
|
|
||||||
private val renderer = WebGLRenderer { antialias = true }.apply {
|
|
||||||
setClearColor(ColorConstants.skyblue, 1)
|
|
||||||
setSize(window.innerWidth, window.innerHeight)
|
|
||||||
}
|
|
||||||
|
|
||||||
val scene: Scene = Scene().apply {
|
|
||||||
add(AmbientLight())
|
|
||||||
}
|
|
||||||
|
|
||||||
val camera = PerspectiveCamera(
|
|
||||||
75,
|
|
||||||
window.innerWidth.toDouble() / window.innerHeight,
|
|
||||||
0.1,
|
|
||||||
10000
|
|
||||||
).apply {
|
|
||||||
position.setZ(1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
val controls: OrbitControls = OrbitControls(camera, renderer.domElement)
|
|
||||||
|
|
||||||
val root get() = renderer.domElement
|
|
||||||
|
|
||||||
private fun animate() {
|
|
||||||
window.requestAnimationFrame {
|
|
||||||
animate()
|
|
||||||
}
|
|
||||||
renderer.render(scene, camera)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start(element: Element) {
|
|
||||||
window.addEventListener("resize", {
|
|
||||||
camera.aspect = window.innerWidth.toDouble() / window.innerHeight;
|
|
||||||
camera.updateProjectionMatrix();
|
|
||||||
|
|
||||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
|
||||||
}, false)
|
|
||||||
element.appendChild(root)
|
|
||||||
animate()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun buildNode(obj: Any): Object3D? {
|
|
||||||
return when (obj) {
|
|
||||||
is DisplayShape3D -> {
|
|
||||||
// val listener = DisplayObjectPropertyListener(obj)
|
|
||||||
// val x = listener["x"].float()
|
|
||||||
// val y = listener["y"].float()
|
|
||||||
// val z = listener["z"].float()
|
|
||||||
// val center = objectBinding(x, y, z) {
|
|
||||||
// Vector3(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f)
|
|
||||||
// }
|
|
||||||
when (obj) {
|
|
||||||
is DisplayGroup3D -> Group(obj.children.mapNotNull { buildNode(it) }).apply {
|
|
||||||
this.translateX(obj.x)
|
|
||||||
this.translateY(obj.y)
|
|
||||||
this.translateZ(obj.z)
|
|
||||||
}
|
|
||||||
is Box -> {
|
|
||||||
//TODO add bindings
|
|
||||||
val geometry = BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize)
|
|
||||||
.translate(obj.x, obj.y, obj.z)
|
|
||||||
Mesh(geometry, obj.properties["color"].material())
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
logger.error { "No renderer defined for ${obj::class}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is DisplayGroup -> Group(obj.children.mapNotNull { buildNode(it) }) // a logical group
|
|
||||||
else -> {
|
|
||||||
logger.error { "No renderer defined for ${obj::class}" }
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun render(obj: Any, meta: Meta) {
|
|
||||||
buildNode(obj)?.let { scene.add(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// init {
|
|
||||||
// val cube: Mesh
|
|
||||||
//
|
|
||||||
// cube = Mesh(
|
|
||||||
// BoxBufferGeometry(1, 1, 1),
|
|
||||||
// MeshPhongMaterial().apply {
|
|
||||||
// this.color.set(ColorConstants.darkgreen)
|
|
||||||
// }
|
|
||||||
// ).also(scene::add)
|
|
||||||
//
|
|
||||||
// Mesh(cube.geometry as BufferGeometry,
|
|
||||||
// MeshBasicMaterial().apply {
|
|
||||||
// this.wireframe = true
|
|
||||||
// this.color.set(ColorConstants.black)
|
|
||||||
// }
|
|
||||||
// ).also(cube::add)
|
|
||||||
//
|
|
||||||
// val points = CatmullRomCurve3(
|
|
||||||
// arrayOf(
|
|
||||||
// Vector3(-10, 0, 10),
|
|
||||||
// Vector3(-5, 5, 5),
|
|
||||||
// Vector3(0, 0, 0),
|
|
||||||
// Vector3(5, -5, 5),
|
|
||||||
// Vector3(10, 0, 10)
|
|
||||||
// )
|
|
||||||
// ).getPoints(50)
|
|
||||||
//
|
|
||||||
// val geometry = BufferGeometry().setFromPoints(points)
|
|
||||||
//
|
|
||||||
// val material = LineBasicMaterial().apply {
|
|
||||||
// color.set(0xff0000)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Create the final object to add to the scene
|
|
||||||
// Line(geometry, material).apply(scene::add)
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import info.laht.threekt.core.Object3D
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility methods for three.kt.
|
|
||||||
* TODO move to three project
|
|
||||||
*/
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
fun Group(children: Collection<Object3D>) = info.laht.threekt.objects.Group().apply {
|
|
||||||
children.forEach { this.add(it) }
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Three js demo for particle physics</title>
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
margin: 0px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<!--<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/three.js/88/three.min.js"></script>-->
|
|
||||||
<!--<script type="text/javascript" src="js/OrbitControls.js"></script>-->
|
|
||||||
<script type="text/javascript" language="JavaScript" src="main.bundle.js"></script>
|
|
||||||
</head>
|
|
||||||
<body class="testApp">
|
|
||||||
<h1>Demo canvas</h1>
|
|
||||||
<div id="canvas"></div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1 +0,0 @@
|
|||||||
config.module.rules.push({ test: /\.css$/, loader: "style!css" });
|
|
@ -1,27 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvm()
|
|
||||||
js()
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
val commonMain by getting {
|
|
||||||
dependencies {
|
|
||||||
api(project(":dataforge-vis"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jvmMain by getting {
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsMain by getting {
|
|
||||||
dependencies {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.meta.EmptyMeta
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.vis.DisplayGroup
|
|
||||||
import hep.dataforge.vis.DisplayObject
|
|
||||||
import hep.dataforge.vis.double
|
|
||||||
|
|
||||||
class Box(parent: DisplayObject?, meta: Meta) : DisplayShape3D(parent, TYPE, meta) {
|
|
||||||
var xSize by double(1.0)
|
|
||||||
var ySize by double(1.0)
|
|
||||||
var zSize by double(1.0)
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TYPE = "geometry.spatial.box"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DisplayGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) =
|
|
||||||
Box(this, meta).apply(action).also { addChild(it) }
|
|
@ -1,39 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.io.Output
|
|
||||||
import hep.dataforge.meta.EmptyMeta
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.vis.*
|
|
||||||
import hep.dataforge.vis.DisplayObject.Companion.DEFAULT_TYPE
|
|
||||||
|
|
||||||
|
|
||||||
interface DisplayObject3D : DisplayObject {
|
|
||||||
val x: Double
|
|
||||||
val y: Double
|
|
||||||
val z: Double
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TYPE = "geometry.spatial"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
open class DisplayShape3D(parent: DisplayObject?, type: String, meta: Meta) :
|
|
||||||
DisplayLeaf(parent, type, meta), DisplayObject3D {
|
|
||||||
override var x by double(0.0, inherited = false)
|
|
||||||
override var y by double(0.0, inherited = false)
|
|
||||||
override var z by double(0.0, inherited = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
class DisplayGroup3D(parent: DisplayObject?, type: String, meta: Meta) : DisplayNode(parent, type, meta),
|
|
||||||
DisplayObject3D {
|
|
||||||
override var x by double(0.0, inherited = false)
|
|
||||||
override var y by double(0.0, inherited = false)
|
|
||||||
override var z by double(0.0, inherited = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DisplayGroup.group(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit = {}) =
|
|
||||||
DisplayNode(this, DEFAULT_TYPE, meta).apply(action).also { addChild(it) }
|
|
||||||
|
|
||||||
|
|
||||||
fun Output<DisplayObject>.render(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit) =
|
|
||||||
render(DisplayNode(null, DEFAULT_TYPE, EmptyMeta).apply(action), meta)
|
|
@ -1,17 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
import hep.dataforge.meta.EmptyMeta
|
|
||||||
import hep.dataforge.meta.Meta
|
|
||||||
import hep.dataforge.vis.DisplayGroup
|
|
||||||
import hep.dataforge.vis.DisplayObject
|
|
||||||
|
|
||||||
class Extruded(parent: DisplayObject?, meta: Meta) : DisplayShape3D(parent, TYPE, meta) {
|
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TYPE = "geometry.spatial.extruded"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DisplayGroup.extrude(meta: Meta = EmptyMeta, action: Extruded.() -> Unit = {}) =
|
|
||||||
Extruded(this, meta).apply(action).also { addChild(it) }
|
|
@ -1,8 +0,0 @@
|
|||||||
package hep.dataforge.vis.spatial
|
|
||||||
|
|
||||||
//TODO replace by platform optimized version
|
|
||||||
data class Point3D(
|
|
||||||
val x: Float,
|
|
||||||
val y: Float,
|
|
||||||
val z: Float
|
|
||||||
)
|
|
@ -1,147 +0,0 @@
|
|||||||
package hep.dataforge.vis
|
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.vis.DisplayObject.Companion.DEFAULT_TYPE
|
|
||||||
import hep.dataforge.vis.DisplayObject.Companion.META_KEY
|
|
||||||
import hep.dataforge.vis.DisplayObject.Companion.TAGS_KEY
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A root type for display hierarchy
|
|
||||||
*/
|
|
||||||
interface DisplayObject {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The parent object of this one. If null, this one is a root.
|
|
||||||
*/
|
|
||||||
val parent: DisplayObject?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of this object. Uses `.` notation. Empty type means untyped group
|
|
||||||
*/
|
|
||||||
val type: String
|
|
||||||
|
|
||||||
val properties: Styled
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val DEFAULT_TYPE = ""
|
|
||||||
const val TYPE_KEY = "@type"
|
|
||||||
const val CHILDREN_KEY = "@children"
|
|
||||||
const val META_KEY = "@meta"
|
|
||||||
const val TAGS_KEY = "@tags"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DisplayGroup : DisplayObject {
|
|
||||||
|
|
||||||
val children: List<DisplayObject>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a child object and notify listeners
|
|
||||||
*/
|
|
||||||
fun addChild(obj: DisplayObject)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a specific child and notify listeners
|
|
||||||
*/
|
|
||||||
fun removeChild(obj: DisplayObject)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add listener for children change
|
|
||||||
* TODO add detailed information into change listener
|
|
||||||
*/
|
|
||||||
fun onChildrenChange(owner: Any? = null, action: () -> Unit)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove children change listener
|
|
||||||
*/
|
|
||||||
fun removeChildrenChangeListener(owner: Any? = null)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the property of this display object of parent's if not found
|
|
||||||
*/
|
|
||||||
tailrec fun DisplayObject.getProperty(name: Name): MetaItem<*>? = properties[name] ?: parent?.getProperty(name)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A change listener for [DisplayObject] configuration.
|
|
||||||
*/
|
|
||||||
fun DisplayObject.onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
|
||||||
properties.style.onChange(owner, action)
|
|
||||||
parent?.onChange(owner, action)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove all meta listeners with matching owners
|
|
||||||
*/
|
|
||||||
fun DisplayObject.removeChangeListener(owner: Any?) {
|
|
||||||
properties.style.removeListener(owner)
|
|
||||||
parent?.removeChangeListener(owner)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional meta not relevant to display
|
|
||||||
*/
|
|
||||||
val DisplayObject.meta: Meta get() = properties[META_KEY]?.node ?: EmptyMeta
|
|
||||||
|
|
||||||
val DisplayObject.tags: List<String> get() = properties[TAGS_KEY].stringList
|
|
||||||
|
|
||||||
internal data class ObjectListener(
|
|
||||||
val owner: Any?,
|
|
||||||
val action: () -> Unit
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic group of display objects
|
|
||||||
*/
|
|
||||||
open class DisplayNode(
|
|
||||||
override val parent: DisplayObject? = null,
|
|
||||||
override val type: String = DEFAULT_TYPE,
|
|
||||||
meta: Meta = EmptyMeta
|
|
||||||
) : DisplayGroup {
|
|
||||||
|
|
||||||
private val _children = ArrayList<DisplayObject>()
|
|
||||||
override val children: List<DisplayObject> get() = _children
|
|
||||||
override val properties = Styled(meta)
|
|
||||||
private val listeners = HashSet<ObjectListener>()
|
|
||||||
|
|
||||||
override fun addChild(obj: DisplayObject) {
|
|
||||||
// val before = _children[name]
|
|
||||||
// if (obj == null) {
|
|
||||||
// _children.remove(name)
|
|
||||||
// } else {
|
|
||||||
// _children[name] = obj
|
|
||||||
// }
|
|
||||||
// listeners.forEach { it.action(name, before, obj) }
|
|
||||||
_children.add(obj)
|
|
||||||
listeners.forEach { it.action() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeChild(obj: DisplayObject) {
|
|
||||||
if (_children.remove(obj)) {
|
|
||||||
listeners.forEach { it.action }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onChildrenChange(owner: Any?, action: () -> Unit) {
|
|
||||||
listeners.add(ObjectListener(owner, action))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
override fun removeChildrenChangeListener(owner: Any?) {
|
|
||||||
listeners.removeAll { it.owner === owner }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Basic [DisplayObject] leaf element
|
|
||||||
*/
|
|
||||||
open class DisplayLeaf(
|
|
||||||
override val parent: DisplayObject?,
|
|
||||||
override val type: String,
|
|
||||||
meta: Meta = EmptyMeta
|
|
||||||
) : DisplayObject {
|
|
||||||
final override val properties = Styled(meta)
|
|
||||||
}
|
|
||||||
|
|
@ -1,120 +0,0 @@
|
|||||||
package hep.dataforge.vis
|
|
||||||
|
|
||||||
import hep.dataforge.meta.*
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.toName
|
|
||||||
import hep.dataforge.values.Value
|
|
||||||
import kotlin.jvm.JvmName
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.properties.ReadWriteProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A delegate for display object properties
|
|
||||||
*/
|
|
||||||
class DisplayObjectDelegate(
|
|
||||||
val key: Name?,
|
|
||||||
val default: MetaItem<*>?,
|
|
||||||
val inherited: Boolean
|
|
||||||
) : ReadWriteProperty<DisplayObject, MetaItem<*>?> {
|
|
||||||
override fun getValue(thisRef: DisplayObject, property: KProperty<*>): MetaItem<*>? {
|
|
||||||
val name = key ?: property.name.toName()
|
|
||||||
return if (inherited) {
|
|
||||||
thisRef.getProperty(name)
|
|
||||||
} else {
|
|
||||||
thisRef.properties[name]
|
|
||||||
} ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: DisplayObject, property: KProperty<*>, value: MetaItem<*>?) {
|
|
||||||
val name = key ?: property.name.toName()
|
|
||||||
thisRef.properties.style[name] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DisplayObjectDelegateWrapper<T>(
|
|
||||||
val key: Name?,
|
|
||||||
val default: T,
|
|
||||||
val inherited: Boolean,
|
|
||||||
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
|
|
||||||
val read: (MetaItem<*>?) -> T?
|
|
||||||
) : ReadWriteProperty<DisplayObject, T> {
|
|
||||||
override fun getValue(thisRef: DisplayObject, property: KProperty<*>): T {
|
|
||||||
val name = key ?: property.name.toName()
|
|
||||||
return if (inherited) {
|
|
||||||
read(thisRef.getProperty(name))
|
|
||||||
} else {
|
|
||||||
read(thisRef.properties[name])
|
|
||||||
} ?: default
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setValue(thisRef: DisplayObject, property: KProperty<*>, value: T) {
|
|
||||||
val name = key ?: property.name.toName()
|
|
||||||
thisRef.properties.style.write(name, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fun DisplayObject.value(default: Value? = null, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.value }
|
|
||||||
|
|
||||||
fun DisplayObject.string(default: String? = null, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.string }
|
|
||||||
|
|
||||||
fun DisplayObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.boolean }
|
|
||||||
|
|
||||||
fun DisplayObject.number(default: Number? = null, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.number }
|
|
||||||
|
|
||||||
fun DisplayObject.double(default: Double? = null, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.double }
|
|
||||||
|
|
||||||
fun DisplayObject.int(default: Int? = null, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.int }
|
|
||||||
|
|
||||||
|
|
||||||
fun DisplayObject.node(key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), null, inherited) { it.node }
|
|
||||||
|
|
||||||
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
|
|
||||||
|
|
||||||
@JvmName("safeString")
|
|
||||||
fun DisplayObject.string(default: String, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.string }
|
|
||||||
|
|
||||||
@JvmName("safeBoolean")
|
|
||||||
fun DisplayObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.boolean }
|
|
||||||
|
|
||||||
@JvmName("safeNumber")
|
|
||||||
fun DisplayObject.number(default: Number, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.number }
|
|
||||||
|
|
||||||
@JvmName("safeDouble")
|
|
||||||
fun DisplayObject.double(default: Double, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.double }
|
|
||||||
|
|
||||||
inline fun <reified E : Enum<E>> DisplayObject.enum(default: E, key: String? = null, inherited: Boolean = true) =
|
|
||||||
DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { item -> item.string?.let { enumValueOf<E>(it) } }
|
|
||||||
|
|
||||||
//merge properties
|
|
||||||
|
|
||||||
fun <T> DisplayObject.merge(
|
|
||||||
key: String? = null,
|
|
||||||
transformer: (Sequence<MetaItem<*>>) -> T
|
|
||||||
): ReadOnlyProperty<DisplayObject, T> {
|
|
||||||
return object : ReadOnlyProperty<DisplayObject, T> {
|
|
||||||
override fun getValue(thisRef: DisplayObject, property: KProperty<*>): T {
|
|
||||||
val name = key?.toName() ?: property.name.toName()
|
|
||||||
val sequence = sequence<MetaItem<*>> {
|
|
||||||
var thisObj: DisplayObject? = thisRef
|
|
||||||
while (thisObj != null) {
|
|
||||||
thisObj.properties[name]?.let { yield(it) }
|
|
||||||
thisObj = thisObj.parent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return transformer(sequence)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
package hep.dataforge.vis
|
|
||||||
|
|
||||||
import hep.dataforge.names.Name
|
|
||||||
import hep.dataforge.names.NameToken
|
|
||||||
|
|
||||||
interface NamedObject : DisplayObject {
|
|
||||||
val name: String
|
|
||||||
|
|
||||||
operator fun get(nameToken: NameToken): DisplayGroup?
|
|
||||||
|
|
||||||
operator fun set(nameToken: NameToken, group: DisplayGroup)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively get a child
|
|
||||||
*/
|
|
||||||
tailrec operator fun NamedObject.get(name: Name): DisplayObject? = when (name.length) {
|
|
||||||
0 -> this
|
|
||||||
1 -> this[name[0]]
|
|
||||||
else -> name.first()?.let { this[it] as? NamedObject }?.get(name.cutFirst())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set given object creating intermediate empty groups if needed
|
|
||||||
* @param name - the full name of a child
|
|
||||||
* @param objFactory - a function that creates child object from parent (to avoid mutable parent parameter)
|
|
||||||
*/
|
|
||||||
fun NamedObject.set(name: Name, objFactory: (parent: DisplayObject) -> DisplayGroup): Unit = when (name.length) {
|
|
||||||
0 -> error("Can't set object with empty name")
|
|
||||||
1 -> set(name[0], objFactory(this))
|
|
||||||
else -> (this[name.first()!!] ?: DisplayNode(this))
|
|
||||||
.run {
|
|
||||||
if (this is NamedObject) {
|
|
||||||
this.set(name.cutFirst(), objFactory)
|
|
||||||
} else {
|
|
||||||
error("Can't assign child to a leaf element $this")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
# Enable official Kotlin Code Style in the IDE.
|
|
||||||
kotlin.code.style=official
|
|
||||||
|
|
||||||
artifactoryUser=darksnake
|
|
||||||
artifactoryPassword=nortlander
|
|
||||||
bintrayUser=altavir
|
|
||||||
bintrayApiKey=9dcb7a779986e1b08898980269b6d428cadda0c3
|
|
@ -26,9 +26,5 @@ include(
|
|||||||
":dataforge-data",
|
":dataforge-data",
|
||||||
":dataforge-io",
|
":dataforge-io",
|
||||||
":dataforge-workspace",
|
":dataforge-workspace",
|
||||||
":dataforge-scripting",
|
":dataforge-scripting"
|
||||||
":dataforge-vis",
|
|
||||||
":dataforge-vis:dataforge-vis-spatial",
|
|
||||||
":dataforge-vis:dataforge-vis-spatial-fx",
|
|
||||||
":dataforge-vis:dataforge-vis-spatial-js"
|
|
||||||
)
|
)
|
Loading…
Reference in New Issue
Block a user