forked from kscience/visionforge
commit
8094f02f06
17
.github/workflows/gradle.yml
vendored
Normal file
17
.github/workflows/gradle.yml
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
name: Gradle build
|
||||
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 11
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
74
README.md
74
README.md
@ -1,11 +1,73 @@
|
||||
# DataForge plugins for visualisation
|
||||
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||
|
||||
## Common visualisation objects
|
||||
# DataForge Visualisation Platform
|
||||
|
||||
## JavaFX utilities for meta manipulations
|
||||
This repository contains [DataForge](http://npm.mipt.ru/dataforge/)
|
||||
(also [here](https://github.com/mipt-npm/dataforge-core)) components useful for visualization in
|
||||
various scientific applications. Currently, the main application is 3D visualization for particle
|
||||
physics experiments.
|
||||
|
||||
## 3D visualisation
|
||||
The project is developed as a Kotlin multiplatform application, currently
|
||||
targeting browser JavaScript and JVM.
|
||||
|
||||
Includes common discription and serializers, JavaFX and Three.js implementations.
|
||||
Main features:
|
||||
- 3D visualization of complex experimental set-ups
|
||||
- Event display such as particle tracks, etc.
|
||||
- Scales up to few hundred thousands of elements
|
||||
- Camera move, rotate, zoom-in and zoom-out
|
||||
- Object tree with property editor
|
||||
- Settings export and import
|
||||
- Multiple platform support
|
||||
|
||||
|
||||
## GDML bindings for 3D visualisation (to be moved to gdml project)
|
||||
## Modules contained in this repository:
|
||||
|
||||
|
||||
### dataforge-vis-common
|
||||
|
||||
Common visualisation objects such as VisualObject and VisualGroup.
|
||||
|
||||
|
||||
### dataforge-vis-spatial
|
||||
|
||||
Includes common description and serializers for 3D visualisation, JavaFX and Three.js implementations.
|
||||
|
||||
|
||||
### dataforge-vis-spatial-gdml
|
||||
|
||||
GDML bindings for 3D visualisation (to be moved to gdml project).
|
||||
|
||||
|
||||
### dataforge-vis-jsroot
|
||||
|
||||
Some JSROOT bindings.
|
||||
|
||||
Note: Currently, this part is experimental and put here for completeness. This module may not build.
|
||||
|
||||
|
||||
### demo
|
||||
|
||||
Several demonstrations of using the dataforge-vis framework:
|
||||
|
||||
##### spatial-showcase
|
||||
|
||||
Contains a simple demonstration (grid with a few shapes that you can rotate, move camera, etc.).
|
||||
|
||||
To see the demo: run `demo/spatial-showcase/distribution/installJsDist` Gradle task, then open
|
||||
`build/distribuions/spatial-showcase-js-0.1.0-dev/index.html` file in your browser.
|
||||
|
||||
Other demos can be built similarly.
|
||||
|
||||
##### muon-monitor
|
||||
|
||||
A full-stack application example, showing the
|
||||
[Muon Monitor](http://npm.mipt.ru/projects/physics.html#mounMonitor) experiment set-up.
|
||||
|
||||
Includes server back-end generating events, as well as visualization front-end.
|
||||
|
||||
To run full-stack app (both server and browser front-end), run
|
||||
`demo/muon-monitor/application/run` task.
|
||||
|
||||
##### gdml
|
||||
|
||||
Visualization example for geometry defined as GDML file.
|
||||
|
@ -1,34 +1,38 @@
|
||||
val dataforgeVersion by extra("0.1.3")
|
||||
import scientifik.useSerialization
|
||||
|
||||
plugins{
|
||||
val kotlinVersion = "1.3.50-eap-5"
|
||||
val dataforgeVersion by extra("0.1.5-dev-6")
|
||||
|
||||
plugins {
|
||||
val kotlinVersion = "1.3.61"
|
||||
val toolsVersion = "0.3.2"
|
||||
|
||||
kotlin("jvm") version kotlinVersion apply false
|
||||
id("kotlin2js") version kotlinVersion apply false
|
||||
id("kotlin-dce-js") version kotlinVersion apply false
|
||||
id("org.jetbrains.kotlin.frontend") version "0.0.45" apply false
|
||||
id("scientifik.mpp") version "0.1.4" apply false
|
||||
id("scientifik.jvm") version "0.1.4" apply false
|
||||
id("scientifik.js") version "0.1.4" apply false
|
||||
id("scientifik.publish") version "0.1.4" apply false
|
||||
id("org.openjfx.javafxplugin") version "0.0.7" apply false
|
||||
id("scientifik.mpp") version toolsVersion apply false
|
||||
id("scientifik.jvm") version toolsVersion apply false
|
||||
id("scientifik.js") version toolsVersion apply false
|
||||
id("scientifik.publish") version toolsVersion apply false
|
||||
id("org.openjfx.javafxplugin") version "0.0.8" apply false
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven("https://kotlin.bintray.com/kotlinx")
|
||||
maven("http://npm.mipt.ru:8081/artifactory/gradle-dev-local")
|
||||
maven("https://kotlin.bintray.com/js-externals")
|
||||
maven("https://dl.bintray.com/pdvrieze/maven")
|
||||
maven("https://dl.bintray.com/kotlin/kotlin-eap")
|
||||
maven("http://maven.jzy3d.org/releases")
|
||||
maven("https://kotlin.bintray.com/js-externals")
|
||||
// maven("https://dl.bintray.com/gbaldeck/kotlin")
|
||||
// maven("https://dl.bintray.com/rjaros/kotlin")
|
||||
}
|
||||
|
||||
group = "hep.dataforge"
|
||||
version = "0.1.0-dev"
|
||||
}
|
||||
|
||||
subprojects{
|
||||
this.useSerialization()
|
||||
}
|
||||
|
||||
val githubProject by extra("dataforge-vis")
|
||||
val bintrayRepo by extra("dataforge")
|
||||
|
||||
|
@ -1,25 +1,50 @@
|
||||
import org.openjfx.gradle.JavaFXOptions
|
||||
import scientifik.useSerialization
|
||||
|
||||
plugins {
|
||||
id("scientifik.mpp")
|
||||
}
|
||||
|
||||
scientifik{
|
||||
serialization = true
|
||||
id("org.openjfx.javafxplugin")
|
||||
}
|
||||
|
||||
val dataforgeVersion: String by rootProject.extra
|
||||
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
|
||||
|
||||
useSerialization()
|
||||
|
||||
kotlin {
|
||||
jvm{
|
||||
withJava()
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
commonMain{
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output:$dataforgeVersion")
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
jvmMain{
|
||||
dependencies {
|
||||
api("no.tornado:tornadofx:1.7.19")
|
||||
//api("no.tornado:tornadofx-controlsfx:0.1.1")
|
||||
api("de.jensd:fontawesomefx-fontawesome:4.7.0-11"){
|
||||
exclude(group = "org.openjfx")
|
||||
}
|
||||
api("de.jensd:fontawesomefx-commons:11.0"){
|
||||
exclude(group = "org.openjfx")
|
||||
}
|
||||
}
|
||||
}
|
||||
jsMain{
|
||||
dependencies {
|
||||
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
|
||||
api(npm("text-encoding"))
|
||||
api(npm("bootstrap","4.4.1"))
|
||||
implementation(npm("jsoneditor"))
|
||||
implementation(npm("file-saver"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure<JavaFXOptions> {
|
||||
modules("javafx.controls")
|
||||
}
|
||||
|
@ -0,0 +1,112 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.*
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
|
||||
/**
|
||||
* Abstract implementation of mutable group of [VisualObject]
|
||||
*/
|
||||
abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup {
|
||||
|
||||
//protected abstract val _children: MutableMap<NameToken, T>
|
||||
|
||||
/**
|
||||
* A map of top level named children
|
||||
*/
|
||||
abstract override val children: Map<NameToken, VisualObject>
|
||||
|
||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||
super.propertyChanged(name, before, after)
|
||||
forEach {
|
||||
it.propertyChanged(name, before, after)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Consider renaming to `StructureChangeListener` (singular)
|
||||
private data class StructureChangeListeners(val owner: Any?, val callback: (Name, VisualObject?) -> Unit)
|
||||
|
||||
@Transient
|
||||
private val structureChangeListeners = HashSet<StructureChangeListeners>()
|
||||
|
||||
/**
|
||||
* Add listener for children change
|
||||
*/
|
||||
override fun onChildrenChange(owner: Any?, action: (Name, VisualObject?) -> Unit) {
|
||||
structureChangeListeners.add(StructureChangeListeners(owner, action))
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove children change listener
|
||||
*/
|
||||
override fun removeChildrenChangeListener(owner: Any?) {
|
||||
structureChangeListeners.removeAll { it.owner === owner }
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagate children change event upwards
|
||||
*/
|
||||
protected fun childrenChanged(name: Name, child: VisualObject?) {
|
||||
structureChangeListeners.forEach { it.callback(name, child) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a child with given name token
|
||||
*/
|
||||
protected abstract fun removeChild(token: NameToken)
|
||||
|
||||
/**
|
||||
* Add, remove or replace child with given name
|
||||
*/
|
||||
protected abstract fun setChild(token: NameToken, child: VisualObject)
|
||||
|
||||
/**
|
||||
* Add a static child. Statics could not be found by name, removed or replaced
|
||||
*/
|
||||
protected open fun addStatic(child: VisualObject) =
|
||||
setChild(NameToken("@static(${child.hashCode()})"), child)
|
||||
|
||||
/**
|
||||
* Recursively create a child group
|
||||
*/
|
||||
protected abstract fun createGroup(name: Name): MutableVisualGroup
|
||||
|
||||
/**
|
||||
* Add named or unnamed child to the group. If key is null the child is considered unnamed. Both key and value are not
|
||||
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
|
||||
*/
|
||||
override fun set(name: Name, child: VisualObject?) {
|
||||
when {
|
||||
name.isEmpty() -> {
|
||||
if (child != null) {
|
||||
addStatic(child)
|
||||
}
|
||||
}
|
||||
name.length == 1 -> {
|
||||
val token = name.first()!!
|
||||
if (child == null) {
|
||||
removeChild(token)
|
||||
} else {
|
||||
setChild(token, child)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
//TODO add safety check
|
||||
val parent = (get(name.cutLast()) as? MutableVisualGroup) ?: createGroup(name.cutLast())
|
||||
parent[name.last()!!.asName()] = child
|
||||
}
|
||||
}
|
||||
structureChangeListeners.forEach { it.callback(name, child) }
|
||||
}
|
||||
|
||||
operator fun set(key: String, child: VisualObject?): Unit {
|
||||
if (key.isBlank()) {
|
||||
if(child!= null) {
|
||||
addStatic(child)
|
||||
}
|
||||
} else {
|
||||
set(key.toName(), child)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vis.common.VisualObject.Companion.STYLE_KEY
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
internal data class PropertyListener(
|
||||
val owner: Any? = null,
|
||||
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
|
||||
)
|
||||
|
||||
abstract class AbstractVisualObject : VisualObject {
|
||||
|
||||
@Transient
|
||||
override var parent: VisualObject? = null
|
||||
|
||||
protected abstract var properties: Config?
|
||||
|
||||
override var styles: List<String>
|
||||
get() = properties?.get(STYLE_KEY).stringList
|
||||
set(value) {
|
||||
//val allStyles = (field + value).distinct()
|
||||
setProperty(STYLE_KEY, value)
|
||||
updateStyles(value)
|
||||
}
|
||||
|
||||
protected fun updateStyles(names: List<String>) {
|
||||
names.mapNotNull { findStyle(it) }.asSequence()
|
||||
.flatMap { it.items.asSequence() }
|
||||
.distinctBy { it.key }
|
||||
.forEach {
|
||||
propertyChanged(it.key.asName(), null, it.value)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The config is initialized and assigned on-demand.
|
||||
* To avoid unnecessary allocations, one should access [properties] via [getProperty] instead.
|
||||
*/
|
||||
override val config: Config
|
||||
get() = properties ?: Config().also { config ->
|
||||
properties = config.apply { onChange(this, ::propertyChanged) }
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val listeners = HashSet<PropertyListener>()
|
||||
|
||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||
if (before != after) {
|
||||
for (l in listeners) {
|
||||
l.action(name, before, after)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
||||
listeners.add(PropertyListener(owner, action))
|
||||
}
|
||||
|
||||
override fun removeChangeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner == owner }
|
||||
}
|
||||
|
||||
private var styleCache: Meta? = null
|
||||
|
||||
/**
|
||||
* Collect all styles for this object in a laminate
|
||||
*/
|
||||
protected val mergedStyles: Meta
|
||||
get() = styleCache ?: findAllStyles().merge().also {
|
||||
styleCache = it
|
||||
}
|
||||
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
properties?.get(name) ?: mergedStyles[name] ?: parent?.getProperty(name, inherit)
|
||||
} else {
|
||||
properties?.get(name) ?: mergedStyles[name]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//fun VisualObject.findStyle(styleName: Name): Meta? {
|
||||
// if (this is VisualGroup) {
|
||||
// val style = resolveStyle(styleName)
|
||||
// if (style != null) return style
|
||||
// }
|
||||
// return parent?.findStyle(styleName)
|
||||
//}
|
@ -1,7 +1,13 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
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
|
||||
* Definitions of common colors. Taken from
|
||||
* https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
|
||||
*/
|
||||
object Colors {
|
||||
const val aliceblue = 0xF0F8FF
|
||||
@ -174,4 +180,63 @@ object Colors {
|
||||
const val whitesmoke = 0xF5F5F5
|
||||
const val yellow = 0xFFFF00
|
||||
const val yellowgreen = 0x9ACD32
|
||||
|
||||
const val RED_KEY = "red"
|
||||
const val GREEN_KEY = "green"
|
||||
const val BLUE_KEY = "blue"
|
||||
|
||||
/**
|
||||
* Convert color represented as Meta to string of format #rrggbb
|
||||
*/
|
||||
fun fromMeta(item: MetaItem<*>): String {
|
||||
return when (item) {
|
||||
is MetaItem.NodeItem<*> -> {
|
||||
val node = item.node
|
||||
rgbToString(
|
||||
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||
node[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u,
|
||||
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
|
||||
)
|
||||
}
|
||||
is MetaItem.ValueItem -> {
|
||||
if (item.value.type == ValueType.NUMBER) {
|
||||
rgbToString(item.value.int)
|
||||
} else {
|
||||
item.value.string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Int color to string of format #rrggbb
|
||||
*/
|
||||
fun rgbToString(rgb: Int): String {
|
||||
val string = rgb.toString(16).padStart(6, '0')
|
||||
return "#" + string.substring(max(0, string.length - 6))
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert three bytes representing color to string of format #rrggbb
|
||||
*/
|
||||
fun rgbToString(red: UByte, green: UByte, blue: UByte): String {
|
||||
fun colorToString(color: UByte): String {
|
||||
return color.toString(16).padStart(2, '0')
|
||||
}
|
||||
return buildString {
|
||||
append("#")
|
||||
append(colorToString(red))
|
||||
append(colorToString(green))
|
||||
append(colorToString(blue))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert three bytes representing color to Meta
|
||||
*/
|
||||
fun rgbToMeta(r: UByte, g: UByte, b: UByte): Meta = buildMeta {
|
||||
RED_KEY put r.toInt()
|
||||
GREEN_KEY put g.toInt()
|
||||
BLUE_KEY put b.toInt()
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
@file:UseSerializers(MetaSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.io.serialization.MetaSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@Serializable
|
||||
class StyleSheet() {
|
||||
@Transient
|
||||
internal var owner: VisualObject? = null
|
||||
|
||||
constructor(owner: VisualObject) : this() {
|
||||
this.owner = owner
|
||||
}
|
||||
|
||||
private val styleMap = HashMap<String, Meta>()
|
||||
|
||||
val items: Map<String, Meta> get() = styleMap
|
||||
|
||||
operator fun get(key: String): Meta? {
|
||||
return styleMap[key] ?: (owner?.parent as? VisualGroup)?.styleSheet?.get(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a style without notifying
|
||||
*/
|
||||
fun define(key: String, style: Meta?) {
|
||||
if (style == null) {
|
||||
styleMap.remove(key)
|
||||
} else {
|
||||
styleMap[key] = style
|
||||
}
|
||||
}
|
||||
|
||||
operator fun set(key: String, style: Meta?) {
|
||||
val oldStyle = styleMap[key]
|
||||
define(key, style)
|
||||
owner?.styleChanged(key, oldStyle, style)
|
||||
}
|
||||
|
||||
operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
|
||||
val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder)
|
||||
set(key, newStyle.seal())
|
||||
}
|
||||
}
|
||||
|
||||
private fun VisualObject.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {
|
||||
if (styles.contains(key)) {
|
||||
//TODO optimize set concatenation
|
||||
val tokens: Collection<Name> = ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
|
||||
.map { it.asName() }
|
||||
tokens.forEach { parent?.propertyChanged(it, oldStyle?.get(it), newStyle?.get(it)) }
|
||||
}
|
||||
if (this is VisualGroup) {
|
||||
this.forEach { it.styleChanged(key, oldStyle, newStyle) }
|
||||
}
|
||||
}
|
@ -1,131 +1,89 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.provider.Provider
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlin.collections.set
|
||||
|
||||
open class VisualGroup<T : VisualObject> : AbstractVisualObject(), Iterable<T>, Provider {
|
||||
|
||||
protected open val namedChildren: MutableMap<Name, T> = HashMap()
|
||||
protected open val unnamedChildren: MutableList<T> = ArrayList()
|
||||
|
||||
override var properties: Config? = null
|
||||
/**
|
||||
* Represents a group of [VisualObject] instances
|
||||
*/
|
||||
interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||
/**
|
||||
* A map of top level named children
|
||||
*/
|
||||
val children: Map<NameToken, VisualObject>
|
||||
|
||||
override val defaultTarget: String get() = VisualObject.TYPE
|
||||
|
||||
override fun iterator(): Iterator<T> = (namedChildren.values + unnamedChildren).iterator()
|
||||
val styleSheet: StyleSheet?
|
||||
|
||||
override fun provideTop(target: String): Map<Name, Any> {
|
||||
return when (target) {
|
||||
VisualObject.TYPE -> namedChildren
|
||||
override fun provideTop(target: String): Map<Name, Any> =
|
||||
when (target) {
|
||||
VisualObject.TYPE -> children.flatMap { (key, value) ->
|
||||
val res: Map<Name, Any> = if (value is VisualGroup) {
|
||||
value.provideTop(target).mapKeys { key + it.key }
|
||||
} else {
|
||||
mapOf(key.asName() to value)
|
||||
}
|
||||
res.entries
|
||||
}.associate { it.toPair() }
|
||||
STYLE_TARGET -> styleSheet?.items?.mapKeys { it.key.toName() } ?: emptyMap()
|
||||
else -> emptyMap()
|
||||
}
|
||||
}
|
||||
|
||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||
super.propertyChanged(name, before, after)
|
||||
forEach {
|
||||
it.propertyChanged(name, before, after)
|
||||
|
||||
/**
|
||||
* Iterate over children of this group
|
||||
*/
|
||||
override fun iterator(): Iterator<VisualObject> = children.values.iterator()
|
||||
|
||||
operator fun get(name: Name): VisualObject? {
|
||||
return when {
|
||||
name.isEmpty() -> this
|
||||
name.length == 1 -> children[name.first()!!]
|
||||
else -> (children[name.first()!!] as? VisualGroup)?.get(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
private data class Listener<T : VisualObject>(val owner: Any?, val callback: (Name?, T?) -> Unit)
|
||||
|
||||
@Transient
|
||||
private val listeners = HashSet<Listener<T>>()
|
||||
|
||||
/**
|
||||
* Add listener for children change
|
||||
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
|
||||
*/
|
||||
fun onChildrenChange(owner: Any?, action: (Name?, T?) -> Unit) {
|
||||
listeners.add(Listener(owner, action))
|
||||
fun attachChildren() {
|
||||
styleSheet?.owner = this
|
||||
this.children.values.forEach {
|
||||
it.parent = this
|
||||
(it as? VisualGroup)?.attachChildren()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val STYLE_TARGET = "style"
|
||||
}
|
||||
}
|
||||
|
||||
data class StyleRef(val group: VisualGroup, val styleName: Name)
|
||||
|
||||
val VisualGroup.isEmpty: Boolean get() = this.children.isEmpty()
|
||||
|
||||
/**
|
||||
* Mutable version of [VisualGroup]
|
||||
*/
|
||||
interface MutableVisualGroup : VisualGroup {
|
||||
|
||||
/**
|
||||
* Add listener for children structure change.
|
||||
* @param owner the handler to properly remove listeners
|
||||
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
|
||||
*/
|
||||
fun onChildrenChange(owner: Any?, action: (Name, VisualObject?) -> Unit)
|
||||
|
||||
/**
|
||||
* Remove children change listener
|
||||
*/
|
||||
fun removeChildrenChangeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner === owner }
|
||||
}
|
||||
fun removeChildrenChangeListener(owner: Any?)
|
||||
|
||||
/**
|
||||
* Add named or unnamed child to the group. If key is [null] the child is considered unnamed. Both key and value are not
|
||||
* allowed to be null in the same time. If name is present and [child] is null, the appropriate element is removed.
|
||||
*/
|
||||
operator fun set(name: Name?, child: T?) {
|
||||
when {
|
||||
name != null -> {
|
||||
if (child == null) {
|
||||
namedChildren.remove(name)
|
||||
} else {
|
||||
if (child.parent == null) {
|
||||
child.parent = this
|
||||
} else {
|
||||
error("Can't reassign existing parent for $child")
|
||||
}
|
||||
namedChildren[name] = child
|
||||
}
|
||||
listeners.forEach { it.callback(name, child) }
|
||||
}
|
||||
child != null -> add(child)
|
||||
else -> error("Both key and child element are empty")
|
||||
}
|
||||
}
|
||||
operator fun set(name: Name, child: VisualObject?)
|
||||
}
|
||||
|
||||
operator fun set(key: String?, child: T?) = set(key?.asName(), child)
|
||||
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: Name.EMPTY)
|
||||
|
||||
/**
|
||||
* Get named child by name
|
||||
*/
|
||||
operator fun get(name: Name): T? = namedChildren[name]
|
||||
|
||||
/**
|
||||
* Get named child by string
|
||||
*/
|
||||
operator fun get(key: String): T? = namedChildren[key.toName()]
|
||||
|
||||
/**
|
||||
* Get an unnamed child
|
||||
*/
|
||||
operator fun get(index: Int): T? = unnamedChildren[index]
|
||||
|
||||
/**
|
||||
* Append unnamed child
|
||||
*/
|
||||
fun add(child: T) {
|
||||
if (child.parent == null) {
|
||||
child.parent = this
|
||||
} else {
|
||||
error("Can't reassign existing parent for $child")
|
||||
}
|
||||
unnamedChildren.add(child)
|
||||
listeners.forEach { it.callback(null, child) }
|
||||
}
|
||||
|
||||
/**
|
||||
* remove unnamed child
|
||||
*/
|
||||
fun remove(child: VisualObject) {
|
||||
unnamedChildren.remove(child)
|
||||
listeners.forEach { it.callback(null, null) }
|
||||
}
|
||||
|
||||
protected fun MetaBuilder.updateChildren() {
|
||||
//adding unnamed children
|
||||
"unnamedChildren" to unnamedChildren.map { it.toMeta() }
|
||||
//adding named children
|
||||
namedChildren.forEach {
|
||||
"children[${it.key}]" to it.value.toMeta()
|
||||
}
|
||||
}
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
updateChildren()
|
||||
}
|
||||
}
|
||||
fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null }
|
@ -2,18 +2,20 @@ package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.vis.common.VisualObject.Companion.TYPE
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
|
||||
private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
|
||||
//private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
|
||||
//private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
|
||||
|
||||
/**
|
||||
* A root type for display hierarchy
|
||||
*/
|
||||
@Type(TYPE)
|
||||
interface VisualObject : MetaRepr, Configurable {
|
||||
interface VisualObject : Configurable {
|
||||
|
||||
/**
|
||||
* The parent object of this one. If null, this one is a root.
|
||||
@ -21,10 +23,17 @@ interface VisualObject : MetaRepr, Configurable {
|
||||
@Transient
|
||||
var parent: VisualObject?
|
||||
|
||||
/**
|
||||
* All properties including styles and prototypes if present, but without inheritance
|
||||
*/
|
||||
fun allProperties(): Laminate
|
||||
|
||||
/**
|
||||
* Set property for this object
|
||||
*/
|
||||
fun setProperty(name: Name, value: Any?)
|
||||
fun setProperty(name: Name, value: Any?) {
|
||||
config[name] = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Get property including or excluding parent properties
|
||||
@ -32,9 +41,11 @@ interface VisualObject : MetaRepr, Configurable {
|
||||
fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
|
||||
|
||||
/**
|
||||
* Manually trigger property changed event. If [name] is empty, notify that the whole object is changed
|
||||
* Trigger property invalidation event. If [name] is empty, notify that the whole object is changed
|
||||
*/
|
||||
fun propertyChanged(name: Name, before: MetaItem<*>? = null, after: MetaItem<*>? = null): Unit
|
||||
fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?): Unit
|
||||
|
||||
fun propertyInvalidated(name: Name) = propertyChanged(name, null, null)
|
||||
|
||||
/**
|
||||
* Add listener triggering on property change
|
||||
@ -46,65 +57,67 @@ interface VisualObject : MetaRepr, Configurable {
|
||||
*/
|
||||
fun removeChangeListener(owner: Any?)
|
||||
|
||||
/**
|
||||
* List of names of styles applied to this object. Order matters. Not inherited
|
||||
*/
|
||||
var styles: List<String>
|
||||
|
||||
companion object {
|
||||
const val TYPE = "visual"
|
||||
val STYLE_KEY = "@style".asName()
|
||||
|
||||
//const val META_KEY = "@meta"
|
||||
//const val TAGS_KEY = "@tags"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
internal data class MetaListener(
|
||||
val owner: Any? = null,
|
||||
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
|
||||
)
|
||||
/**
|
||||
* Get [VisualObject] property using key as a String
|
||||
*/
|
||||
fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = getProperty(key.toName(), inherit)
|
||||
|
||||
abstract class AbstractVisualObject: VisualObject {
|
||||
/**
|
||||
* Set [VisualObject] property using key as a String
|
||||
*/
|
||||
fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value)
|
||||
|
||||
@Transient
|
||||
override var parent: VisualObject? = null
|
||||
/**
|
||||
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
|
||||
*/
|
||||
fun VisualObject.useStyle(name: String) {
|
||||
styles = styles + name
|
||||
}
|
||||
|
||||
@Transient
|
||||
private val listeners = HashSet<MetaListener>()
|
||||
//private tailrec fun VisualObject.topGroup(): VisualGroup? {
|
||||
// val parent = this.parent
|
||||
// return if (parent == null) {
|
||||
// this as? VisualGroup
|
||||
// }
|
||||
// else {
|
||||
// parent.topGroup()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Add or update given style on a top-most reachable parent group and apply it to this object
|
||||
// */
|
||||
//fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||
// val styleName = name.toName()
|
||||
// topGroup()?.updateStyle(styleName, builder) ?: error("Can't find parent group for $this")
|
||||
// useStyle(styleName)
|
||||
//}
|
||||
|
||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||
for (l in listeners) {
|
||||
l.action(name, before, after)
|
||||
}
|
||||
}
|
||||
tailrec fun VisualObject.findStyle(name: String): Meta? =
|
||||
(this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name)
|
||||
|
||||
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
|
||||
listeners.add(MetaListener(owner, action))
|
||||
}
|
||||
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
|
||||
|
||||
override fun removeChangeListener(owner: Any?) {
|
||||
listeners.removeAll { it.owner == owner }
|
||||
}
|
||||
//operator fun VisualObject.get(name: Name): VisualObject?{
|
||||
// return when {
|
||||
// name.isEmpty() -> this
|
||||
// this is VisualGroup -> this[name]
|
||||
// else -> null
|
||||
// }
|
||||
//}
|
||||
|
||||
abstract var properties: Config?
|
||||
override val config: Config
|
||||
get() = properties ?: Config().also { config ->
|
||||
properties = config
|
||||
config.onChange(this, ::propertyChanged)
|
||||
}
|
||||
|
||||
override fun setProperty(name: Name, value: Any?) {
|
||||
config[name] = value
|
||||
}
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
properties?.get(name) ?: parent?.getProperty(name, inherit)
|
||||
} else {
|
||||
properties?.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun MetaBuilder.updateMeta() {}
|
||||
|
||||
override fun toMeta(): Meta = buildMeta {
|
||||
"type" to this::class.simpleName
|
||||
"properties" to properties
|
||||
updateMeta()
|
||||
}
|
||||
}
|
@ -4,18 +4,18 @@ import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
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
|
||||
|
||||
fun String.asName() = NameToken(this).asName()
|
||||
|
||||
/**
|
||||
* A delegate for display object properties
|
||||
*/
|
||||
class DisplayObjectDelegate(
|
||||
class VisualObjectDelegate(
|
||||
val key: Name?,
|
||||
val default: MetaItem<*>?,
|
||||
val inherited: Boolean
|
||||
@ -35,82 +35,84 @@ class DisplayObjectDelegate(
|
||||
}
|
||||
}
|
||||
|
||||
class DisplayObjectDelegateWrapper<T>(
|
||||
class VisualObjectDelegateWrapper<T>(
|
||||
val obj: VisualObject,
|
||||
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<VisualObject, T> {
|
||||
) : ReadWriteProperty<Any?, T> {
|
||||
|
||||
//private var cachedName: Name? = null
|
||||
|
||||
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val name = key ?: property.name.asName()
|
||||
return if (inherited) {
|
||||
read(thisRef.getProperty(name))
|
||||
} else {
|
||||
read(thisRef.config[name])
|
||||
} ?: default
|
||||
return read(obj.getProperty(name,inherited))?:default
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: VisualObject, property: KProperty<*>, value: T) {
|
||||
override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
|
||||
val name = key ?: property.name.asName()
|
||||
thisRef.config[name] = value
|
||||
obj.config[name] = value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun VisualObject.value(default: Value? = null, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.value }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.value }
|
||||
|
||||
fun VisualObject.string(default: String? = null, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.string }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
|
||||
|
||||
fun VisualObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
|
||||
|
||||
fun VisualObject.number(default: Number? = null, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.number }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
|
||||
|
||||
fun VisualObject.double(default: Double? = null, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.double }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
|
||||
|
||||
fun VisualObject.int(default: Int? = null, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.int }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
|
||||
|
||||
|
||||
fun VisualObject.node(key: String? = null, inherited: Boolean = true) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), null, inherited) { it.node }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it.node }
|
||||
|
||||
fun VisualObject.item(key: String? = null, inherited: Boolean = true) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), null, inherited) { it }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), null, inherited) { it }
|
||||
|
||||
//fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
|
||||
|
||||
@JvmName("safeString")
|
||||
fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.string }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.string }
|
||||
|
||||
@JvmName("safeBoolean")
|
||||
fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.boolean }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.boolean }
|
||||
|
||||
@JvmName("safeNumber")
|
||||
fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.number }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.number }
|
||||
|
||||
@JvmName("safeDouble")
|
||||
fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.double }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.double }
|
||||
|
||||
@JvmName("safeInt")
|
||||
fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.asName(), default, inherited) { it.int }
|
||||
VisualObjectDelegateWrapper(this, key?.toName(), default, inherited) { it.int }
|
||||
|
||||
|
||||
inline fun <reified E : Enum<E>> VisualObject.enum(default: E, key: String? = null, inherited: Boolean = false) =
|
||||
DisplayObjectDelegateWrapper(key?.let{ NameToken(it).asName()}, default, inherited) { item -> item.string?.let { enumValueOf<E>(it) } }
|
||||
VisualObjectDelegateWrapper(
|
||||
this,
|
||||
key?.let { NameToken(it).asName() },
|
||||
default,
|
||||
inherited
|
||||
) { item -> item.string?.let { enumValueOf<E>(it) } }
|
||||
|
||||
//merge properties
|
||||
|
||||
@ -118,11 +120,11 @@ fun <T> VisualObject.merge(
|
||||
key: String? = null,
|
||||
transformer: (Sequence<MetaItem<*>>) -> T
|
||||
): ReadOnlyProperty<VisualObject, T> {
|
||||
return object : ReadOnlyProperty<VisualObject, T> {
|
||||
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T {
|
||||
val name = key?.asName() ?: property.name.asName()
|
||||
return object : ReadOnlyProperty<Any?, T> {
|
||||
override fun getValue(thisRef: Any?, property: KProperty<*>): T {
|
||||
val name = key?.toName() ?: property.name.asName()
|
||||
val sequence = sequence<MetaItem<*>> {
|
||||
var thisObj: VisualObject? = thisRef
|
||||
var thisObj: VisualObject? = this@merge
|
||||
while (thisObj != null) {
|
||||
thisObj.config[name]?.let { yield(it) }
|
||||
thisObj = thisObj.parent
|
@ -30,7 +30,8 @@ class VisualPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
companion object : PluginFactory<VisualPlugin> {
|
||||
override val tag: PluginTag = PluginTag(name = "visual", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<out VisualPlugin> = VisualPlugin::class
|
||||
override fun invoke(meta: Meta): VisualPlugin = VisualPlugin(meta)
|
||||
|
||||
override fun invoke(meta: Meta, context: Context): VisualPlugin = VisualPlugin(meta)
|
||||
|
||||
const val VISUAL_FACTORY_TYPE = "visual.factory"
|
||||
}
|
||||
|
@ -3,12 +3,18 @@ package hep.dataforge.vis.common
|
||||
import hep.dataforge.descriptors.ValueDescriptor
|
||||
import hep.dataforge.meta.*
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget" key of [ValueDescriptor]
|
||||
*/
|
||||
var ValueDescriptor.widget: Meta
|
||||
get() = this.config["widget"].node?: EmptyMeta
|
||||
set(value) {
|
||||
this.config["widget"] = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension property to access the "widget.type" key of [ValueDescriptor]
|
||||
*/
|
||||
var ValueDescriptor.widgetType: String?
|
||||
get() = this["widget.type"].string
|
||||
set(value) {
|
||||
|
@ -1,14 +1,10 @@
|
||||
package hep.dataforge.vis.hmr
|
||||
package hep.dataforge.js
|
||||
|
||||
import kotlin.browser.document
|
||||
import kotlin.dom.hasClass
|
||||
|
||||
external val module: Module
|
||||
|
||||
external interface Module {
|
||||
val hot: Hot?
|
||||
}
|
||||
|
||||
external interface Hot {
|
||||
val data: dynamic
|
||||
|
||||
@ -19,17 +15,31 @@ external interface Hot {
|
||||
fun dispose(callback: (data: dynamic) -> Unit)
|
||||
}
|
||||
|
||||
external fun require(name: String): dynamic
|
||||
|
||||
abstract class ApplicationBase {
|
||||
open val stateKeys: List<String> get() = emptyList()
|
||||
|
||||
abstract fun start(state: Map<String, Any>)
|
||||
open fun dispose(): Map<String, Any> = emptyMap()
|
||||
external interface Module {
|
||||
val hot: Hot?
|
||||
}
|
||||
|
||||
fun startApplication(builder: () -> ApplicationBase) {
|
||||
fun start(state: dynamic): ApplicationBase? {
|
||||
/**
|
||||
* Base interface for applications.
|
||||
*
|
||||
* Base interface for applications supporting Hot Module Replacement (HMR).
|
||||
*/
|
||||
interface Application {
|
||||
/**
|
||||
* Starting point for an application.
|
||||
* @param state Initial state between Hot Module Replacement (HMR).
|
||||
*/
|
||||
fun start(state: Map<String, Any>)
|
||||
|
||||
/**
|
||||
* Ending point for an application.
|
||||
* @return final state for Hot Module Replacement (HMR).
|
||||
*/
|
||||
fun dispose(): Map<String, Any> = emptyMap()
|
||||
}
|
||||
|
||||
fun startApplication(builder: () -> Application) {
|
||||
fun start(state: dynamic): Application? {
|
||||
return if (document.body?.hasClass("testApp") == true) {
|
||||
val application = builder()
|
||||
|
||||
@ -42,7 +52,7 @@ fun startApplication(builder: () -> ApplicationBase) {
|
||||
}
|
||||
}
|
||||
|
||||
var application: ApplicationBase? = null
|
||||
var application: Application? = null
|
||||
|
||||
val state: dynamic = module.hot?.let { hot ->
|
||||
hot.accept()
|
@ -0,0 +1,24 @@
|
||||
package hep.dataforge.js
|
||||
|
||||
@JsName("require")
|
||||
external fun requireJS(name: String): dynamic
|
||||
|
||||
inline fun <T : Any> jsObject(builder: T.() -> Unit): T {
|
||||
val obj: T = js("({})") as T
|
||||
return obj.apply {
|
||||
builder()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun js(builder: dynamic.() -> Unit): dynamic = jsObject(builder)
|
||||
|
||||
//fun <T : Any> clone(obj: T) = objectAssign(jsObject<T> {}, obj)
|
||||
|
||||
//inline fun <T : Any> assign(obj: T, builder: T.() -> Unit) = clone(obj).apply(builder)
|
||||
|
||||
fun toPlainObjectStripNull(obj: Any) = js {
|
||||
for (key in Object.keys(obj)) {
|
||||
val value = obj.asDynamic()[key]
|
||||
if (value != null) this[key] = value
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.js.div
|
||||
import kotlinx.html.js.h3
|
||||
import org.w3c.dom.HTMLElement
|
||||
|
||||
inline fun TagConsumer<HTMLElement>.card(title: String, crossinline block: TagConsumer<HTMLElement>.() -> Unit) {
|
||||
div("card w-100") {
|
||||
div("card-body") {
|
||||
h3(classes = "card-title") { +title }
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.isEmpty
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.*
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.HTMLSpanElement
|
||||
import kotlin.dom.clear
|
||||
|
||||
fun Element.displayObjectTree(
|
||||
obj: VisualObject,
|
||||
clickCallback: (Name) -> Unit = {}
|
||||
) {
|
||||
clear()
|
||||
append {
|
||||
card("Object tree") {
|
||||
subTree(Name.EMPTY, obj, clickCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TagConsumer<HTMLElement>.subTree(
|
||||
fullName: Name,
|
||||
obj: VisualObject,
|
||||
clickCallback: (Name) -> Unit
|
||||
) {
|
||||
// val fullName = parentName + token
|
||||
val token = fullName.last()?.toString()?:"World"
|
||||
|
||||
//display as node if any child is visible
|
||||
if (obj is VisualGroup && obj.children.keys.any { !it.body.startsWith("@") }) {
|
||||
lateinit var toggle: HTMLSpanElement
|
||||
div("d-inline-block text-truncate") {
|
||||
toggle = span("objTree-caret")
|
||||
label("objTree-label") {
|
||||
+token
|
||||
onClickFunction = { clickCallback(fullName) }
|
||||
}
|
||||
}
|
||||
val subtree = ul("objTree-subtree")
|
||||
toggle.onclick = {
|
||||
toggle.classList.toggle("objTree-caret-down")
|
||||
subtree.apply {
|
||||
//If expanded, add children dynamically
|
||||
if (toggle.classList.contains("objTree-caret-down")) {
|
||||
obj.children.entries
|
||||
.filter { !it.key.toString().startsWith("@") } // ignore statics and other hidden children
|
||||
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
|
||||
.forEach { (childToken, child) ->
|
||||
append {
|
||||
li().apply {
|
||||
subTree(fullName + childToken, child, clickCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if not, clear them to conserve memory on very long lists
|
||||
this.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
div("d-inline-block text-truncate") {
|
||||
span("objTree-leaf")
|
||||
label("objTree-label") {
|
||||
+token
|
||||
onClickFunction = { clickCallback(fullName) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,185 @@
|
||||
@file:Suppress(
|
||||
"INTERFACE_WITH_SUPERCLASS",
|
||||
"OVERRIDING_FINAL_MEMBER",
|
||||
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
|
||||
"CONFLICTING_OVERLOADS",
|
||||
"EXTERNAL_DELEGATION"
|
||||
)
|
||||
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import org.w3c.dom.HTMLElement
|
||||
|
||||
external interface Node {
|
||||
var field: String
|
||||
var value: String? get() = definedExternally; set(value) = definedExternally
|
||||
var path: dynamic
|
||||
}
|
||||
|
||||
external interface NodeName {
|
||||
var path: Array<String>
|
||||
var type: dynamic /* 'object' | 'array' */
|
||||
var size: Number
|
||||
}
|
||||
|
||||
external interface ValidationError {
|
||||
var path: dynamic
|
||||
var message: String
|
||||
}
|
||||
|
||||
external interface Template {
|
||||
var text: String
|
||||
var title: String
|
||||
var className: String? get() = definedExternally; set(value) = definedExternally
|
||||
var field: String
|
||||
var value: Any
|
||||
}
|
||||
|
||||
external interface `T$6` {
|
||||
var startFrom: Number
|
||||
var options: Array<String>
|
||||
}
|
||||
|
||||
external interface AutoCompleteOptions {
|
||||
var confirmKeys: Array<Number>? get() = definedExternally; set(value) = definedExternally
|
||||
var caseSensitive: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
// var getOptions: AutoCompleteOptionsGetter? get() = definedExternally; set(value) = definedExternally
|
||||
}
|
||||
|
||||
external interface SelectionPosition {
|
||||
var row: Number
|
||||
var column: Number
|
||||
}
|
||||
|
||||
external interface SerializableNode {
|
||||
var value: Any
|
||||
var path: dynamic
|
||||
}
|
||||
|
||||
external interface Color {
|
||||
var rgba: Array<Number>
|
||||
var hsla: Array<Number>
|
||||
var rgbString: String
|
||||
var rgbaString: String
|
||||
var hslString: String
|
||||
var hslaString: String
|
||||
var hex: String
|
||||
}
|
||||
|
||||
//external interface `T$0` {
|
||||
// var field: Boolean
|
||||
// var value: Boolean
|
||||
//}
|
||||
//
|
||||
//external interface `T$1` {
|
||||
// @nativeGetter
|
||||
// operator fun get(key: String): String?
|
||||
//
|
||||
// @nativeSetter
|
||||
// operator fun set(key: String, value: String)
|
||||
//}
|
||||
|
||||
//external interface Languages {
|
||||
// @nativeGetter
|
||||
// operator fun get(lang: String): `T$1`?
|
||||
//
|
||||
// @nativeSetter
|
||||
// operator fun set(lang: String, value: `T$1`)
|
||||
//}
|
||||
|
||||
external interface JSONEditorOptions {
|
||||
// var ace: AceAjax.Ace? get() = definedExternally; set(value) = definedExternally
|
||||
// var ajv: Ajv? get() = definedExternally; set(value) = definedExternally
|
||||
var onChange: (() -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onChangeJSON: ((json: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onChangeText: ((jsonString: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onEditable: ((node: Node) -> dynamic)? get() = definedExternally; set(value) = definedExternally
|
||||
var onError: ((error: Error) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onModeChange: ((newMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */, oldMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onNodeName: ((nodeName: NodeName) -> String?)? get() = definedExternally; set(value) = definedExternally
|
||||
var onValidate: ((json: Any) -> dynamic)? get() = definedExternally; set(value) = definedExternally
|
||||
var escapeUnicode: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var sortObjectKeys: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var history: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var mode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
|
||||
var modes: Array<dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */>? get() = definedExternally; set(value) = definedExternally
|
||||
var name: String? get() = definedExternally; set(value) = definedExternally
|
||||
var schema: Any? get() = definedExternally; set(value) = definedExternally
|
||||
var schemaRefs: Any? get() = definedExternally; set(value) = definedExternally
|
||||
var search: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var indentation: Number? get() = definedExternally; set(value) = definedExternally
|
||||
var theme: String? get() = definedExternally; set(value) = definedExternally
|
||||
var templates: Array<Template>? get() = definedExternally; set(value) = definedExternally
|
||||
var autocomplete: AutoCompleteOptions? get() = definedExternally; set(value) = definedExternally
|
||||
var mainMenuBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var navigationBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var statusBar: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var onTextSelectionChange: ((start: SelectionPosition, end: SelectionPosition, text: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onSelectionChange: ((start: SerializableNode, end: SerializableNode) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var onEvent: ((node: Node, event: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var colorPicker: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var onColorPicker: ((parent: HTMLElement, color: String, onChange: (color: Color) -> Unit) -> Unit)? get() = definedExternally; set(value) = definedExternally
|
||||
var timestampTag: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var language: String? get() = definedExternally; set(value) = definedExternally
|
||||
//var languages: Languages? get() = definedExternally; set(value) = definedExternally
|
||||
var modalAnchor: HTMLElement? get() = definedExternally; set(value) = definedExternally
|
||||
var enableSort: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var enableTransform: Boolean? get() = definedExternally; set(value) = definedExternally
|
||||
var maxVisibleChilds: Number? get() = definedExternally; set(value) = definedExternally
|
||||
}
|
||||
|
||||
external interface JsonPath {
|
||||
var path: dynamic
|
||||
}
|
||||
|
||||
external interface EditorSelection {
|
||||
var start: SerializableNode
|
||||
var end: SerializableNode
|
||||
}
|
||||
|
||||
external interface TextSelection {
|
||||
var start: SelectionPosition
|
||||
var end: SelectionPosition
|
||||
var text: String
|
||||
}
|
||||
|
||||
@JsModule("jsoneditor")
|
||||
@JsNonModule
|
||||
external open class JSONEditor(
|
||||
container: HTMLElement,
|
||||
options: JSONEditorOptions? = definedExternally /* null */,
|
||||
json: dynamic = definedExternally /* null */
|
||||
) {
|
||||
open fun collapseAll()
|
||||
open fun destroy()
|
||||
open fun expandAll()
|
||||
open fun focus()
|
||||
open fun get(): Any
|
||||
open fun getMode(): dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
|
||||
open fun getName(): String?
|
||||
open fun getNodesByRange(start: JsonPath, end: JsonPath): Array<SerializableNode>
|
||||
open fun getSelection(): EditorSelection
|
||||
open fun getText(): String
|
||||
open fun getTextSelection(): TextSelection
|
||||
open fun refresh()
|
||||
open fun set(json: Any)
|
||||
open fun setMode(mode: String /* 'tree' */)
|
||||
open fun setMode(mode: String /* 'view' */)
|
||||
open fun setMode(mode: String /* 'form' */)
|
||||
open fun setMode(mode: String /* 'code' */)
|
||||
open fun setMode(mode: String /* 'text' */)
|
||||
open fun setName(name: String? = definedExternally /* null */)
|
||||
open fun setSchema(schema: Any?, schemaRefs: Any? = definedExternally /* null */)
|
||||
open fun setSelection(start: JsonPath, end: JsonPath)
|
||||
open fun setText(jsonString: String)
|
||||
open fun setTextSelection(start: SelectionPosition, end: SelectionPosition)
|
||||
open fun update(json: Any)
|
||||
open fun updateText(jsonString: String)
|
||||
|
||||
companion object {
|
||||
var VALID_OPTIONS: Array<String>
|
||||
// var ace: AceAjax.Ace
|
||||
// var Ajv: Ajv
|
||||
var VanillaPicker: Any
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import hep.dataforge.io.toJson
|
||||
import hep.dataforge.js.jsObject
|
||||
import hep.dataforge.meta.DynamicMeta
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.findStyle
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.*
|
||||
import org.w3c.dom.Element
|
||||
import kotlin.collections.forEach
|
||||
import kotlin.collections.isNotEmpty
|
||||
import kotlin.collections.set
|
||||
import kotlin.dom.clear
|
||||
|
||||
//FIXME something rotten in JS-Meta converter
|
||||
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
|
||||
|
||||
//TODO add node descriptor instead of configuring property selector
|
||||
fun Element.displayPropertyEditor(
|
||||
name: Name,
|
||||
item: VisualObject,
|
||||
propertySelector: (VisualObject) -> Meta = { it.config }
|
||||
) {
|
||||
clear()
|
||||
|
||||
append {
|
||||
card("Properties") {
|
||||
if (!name.isEmpty()) {
|
||||
nav {
|
||||
attributes["aria-label"] = "breadcrumb"
|
||||
ol("breadcrumb") {
|
||||
name.tokens.forEach { token ->
|
||||
li("breadcrumb-item") {
|
||||
+token.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val dMeta: dynamic = propertySelector(item).toDynamic()
|
||||
val options: JSONEditorOptions = jsObject {
|
||||
mode = "form"
|
||||
onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
|
||||
}
|
||||
JSONEditor(div(), options, dMeta)
|
||||
}
|
||||
|
||||
val styles = item.styles
|
||||
if (styles.isNotEmpty()) {
|
||||
card("Styles") {
|
||||
item.styles.forEach { style ->
|
||||
val styleMeta = item.findStyle(style)
|
||||
h4("container") { +style }
|
||||
if (styleMeta != null) {
|
||||
div("container").apply {
|
||||
val options: JSONEditorOptions = jsObject {
|
||||
mode = "view"
|
||||
}
|
||||
JSONEditor(
|
||||
this,
|
||||
options,
|
||||
styleMeta.toDynamic()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
23
dataforge-vis-common/src/jsMain/resources/css/common.css
Normal file
23
dataforge-vis-common/src/jsMain/resources/css/common.css
Normal file
@ -0,0 +1,23 @@
|
||||
/* Remove default bullets */
|
||||
ul, .objTree-subtree {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Style the caret/arrow */
|
||||
.objTree-caret {
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
}
|
||||
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
.objTree-caret::before {
|
||||
content: "\25B6";
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||
.objTree-caret-down::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
@ -96,7 +96,7 @@ class FXPlugin(meta: Meta = EmptyMeta) : AbstractPlugin(meta) {
|
||||
companion object : PluginFactory<FXPlugin> {
|
||||
override val type: KClass<out FXPlugin> = FXPlugin::class
|
||||
override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP)
|
||||
override fun invoke(meta: Meta): FXPlugin = FXPlugin(meta)
|
||||
override fun invoke(meta: Meta, context: Context): FXPlugin = FXPlugin(meta)
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
@ -39,9 +41,10 @@ class ColorValueChooser : ValueChooserBase<ColorPicker>() {
|
||||
return node
|
||||
}
|
||||
|
||||
companion object: ValueChooser.Factory{
|
||||
override val name: String = "color"
|
||||
companion object : ValueChooser.Factory {
|
||||
override val name: Name = "color".asName()
|
||||
|
||||
override fun invoke(meta: Meta): ValueChooser = ColorValueChooser()
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
ColorValueChooser()
|
||||
}
|
||||
}
|
@ -3,11 +3,13 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.value
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.parseValue
|
||||
import javafx.collections.FXCollections
|
||||
@ -50,9 +52,10 @@ class ComboBoxValueChooser(val values: Collection<Value>? = null) : ValueChooser
|
||||
}
|
||||
|
||||
companion object : ValueChooser.Factory {
|
||||
override val name: String = "combo"
|
||||
override val name: Name = "combo".asName()
|
||||
|
||||
override fun invoke(meta: Meta): ValueChooser = ComboBoxValueChooser(meta["values"].value?.list)
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
ComboBoxValueChooser(meta["values"].value?.list)
|
||||
}
|
||||
|
||||
}
|
@ -3,20 +3,22 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.meta
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.vis.fx.dfIconView
|
||||
import hep.dataforge.vis.fx.values.ValueChooser
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.*
|
||||
import javafx.scene.control.cell.TextFieldTreeTableCell
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.Text
|
||||
import org.controlsfx.glyphfont.Glyph
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
@ -29,8 +31,9 @@ class ConfigEditor(
|
||||
val allowNew: Boolean = true,
|
||||
title: String = "Configuration editor"
|
||||
) : Fragment(title = title, icon = dfIconView) {
|
||||
//TODO replace parameters by properties
|
||||
|
||||
constructor(config: Config, descriptor: NodeDescriptor, title: String = "Configuration editor") :
|
||||
constructor(config: Config, descriptor: NodeDescriptor?, title: String = "Configuration editor") :
|
||||
this(FXMeta.root(config, descriptor = descriptor), title = title)
|
||||
|
||||
override val root = borderpane {
|
||||
@ -133,8 +136,9 @@ class ConfigEditor(
|
||||
is FXMetaNode<Config> -> {
|
||||
if (allowNew) {
|
||||
text = null
|
||||
graphic = hbox {
|
||||
button("node", Glyph("FontAwesome", "PLUS_CIRCLE")) {
|
||||
graphic = HBox().apply {
|
||||
val glyph: Node = FontAwesomeIconView(FontAwesomeIcon.PLUS_CIRCLE)
|
||||
button("node", graphic = glyph) {
|
||||
hgrow = Priority.ALWAYS
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
action {
|
||||
@ -143,7 +147,7 @@ class ConfigEditor(
|
||||
}
|
||||
}
|
||||
}
|
||||
button("value", Glyph("FontAwesome", "PLUS_SQUARE")) {
|
||||
button("value", graphic = FontAwesomeIconView(FontAwesomeIcon.PLUS_SQUARE)) {
|
||||
hgrow = Priority.ALWAYS
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
action {
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.fx.meta
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.descriptors.ItemDescriptor
|
||||
import hep.dataforge.descriptors.NodeDescriptor
|
||||
@ -84,7 +84,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
||||
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
|
||||
|
||||
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
|
||||
!(cfg.descriptor?.tags?.contains(ConfigEditor.NO_CONFIGURATOR_TAG) ?: false)
|
||||
!(cfg.descriptor?.attributes?.get(ConfigEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
|
||||
}
|
||||
|
||||
val children = object : ListBinding<FXMeta<M>>() {
|
||||
@ -172,7 +172,7 @@ fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
|
||||
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
|
||||
return if (append && token.index.isNotEmpty()) {
|
||||
val name = token.asName()
|
||||
val index = (getAll(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
|
||||
val index = (getIndexed(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
|
||||
val newName = name.withIndex(index.toString())
|
||||
set(newName, EmptyMeta)
|
||||
get(newName).node!!
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.vis.fx.meta
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.vis.fx.dfIconView
|
@ -3,9 +3,11 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.*
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.scene.control.TextField
|
||||
@ -100,7 +102,8 @@ class TextValueChooser : ValueChooserBase<TextField>() {
|
||||
}
|
||||
|
||||
companion object : ValueChooser.Factory {
|
||||
override val name: String = "text"
|
||||
override fun invoke(meta: Meta): ValueChooser = TextValueChooser()
|
||||
override val name: Name = "text".asName()
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
TextValueChooser()
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.values.Value
|
||||
|
@ -3,13 +3,14 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Named
|
||||
import hep.dataforge.descriptors.ValueDescriptor
|
||||
import hep.dataforge.meta.EmptyMeta
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.provider.provideByType
|
||||
import hep.dataforge.values.Null
|
||||
@ -71,7 +72,7 @@ interface ValueChooser {
|
||||
companion object {
|
||||
|
||||
private fun findWidgetByType(context: Context, type: String): Factory? {
|
||||
return when (type) {
|
||||
return when (type.toName()) {
|
||||
TextValueChooser.name -> TextValueChooser
|
||||
ColorValueChooser.name -> ColorValueChooser
|
||||
ComboBoxValueChooser.name -> ComboBoxValueChooser
|
||||
@ -86,7 +87,10 @@ interface ValueChooser {
|
||||
val widgetType = descriptor.widgetType
|
||||
val chooser: ValueChooser = when {
|
||||
widgetType != null -> {
|
||||
findWidgetByType(context, widgetType)?.invoke(
|
||||
findWidgetByType(
|
||||
context,
|
||||
widgetType
|
||||
)?.invoke(
|
||||
descriptor.widget
|
||||
) ?: TextValueChooser()
|
||||
}
|
||||
@ -104,7 +108,8 @@ interface ValueChooser {
|
||||
descriptor: ValueDescriptor? = null,
|
||||
setter: (Value) -> Unit
|
||||
): ValueChooser {
|
||||
val chooser = build(context, descriptor)
|
||||
val chooser =
|
||||
build(context, descriptor)
|
||||
chooser.setDisplayValue(value.value ?: Null)
|
||||
value.onChange {
|
||||
chooser.setDisplayValue(it ?: Null)
|
||||
@ -114,7 +119,11 @@ interface ValueChooser {
|
||||
setter(result)
|
||||
ValueCallbackResponse(true, result, "OK")
|
||||
} else {
|
||||
ValueCallbackResponse(false, value.value ?: Null, "Not allowed")
|
||||
ValueCallbackResponse(
|
||||
false,
|
||||
value.value ?: Null,
|
||||
"Not allowed"
|
||||
)
|
||||
}
|
||||
}
|
||||
return chooser
|
@ -3,7 +3,7 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.descriptors.ValueDescriptor
|
||||
import hep.dataforge.values.Null
|
@ -0,0 +1,74 @@
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.findStyle
|
||||
import javafx.beans.binding.Binding
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.layout.VBox
|
||||
import tornadofx.*
|
||||
|
||||
class VisualObjectEditorFragment(val selector: (VisualObject) -> Meta) : Fragment() {
|
||||
|
||||
val itemProperty = SimpleObjectProperty<VisualObject>()
|
||||
var item: VisualObject? by itemProperty
|
||||
val descriptorProperty = SimpleObjectProperty<NodeDescriptor>()
|
||||
|
||||
constructor(
|
||||
item: VisualObject?,
|
||||
descriptor: NodeDescriptor?,
|
||||
selector: (VisualObject) -> Config = { it.config }
|
||||
) : this(selector) {
|
||||
this.item = item
|
||||
this.descriptorProperty.set(descriptor)
|
||||
}
|
||||
|
||||
private var currentConfig: Config? = null
|
||||
|
||||
private val configProperty: Binding<Config?> = itemProperty.objectBinding { visualObject ->
|
||||
if (visualObject == null) return@objectBinding null
|
||||
val meta = selector(visualObject)
|
||||
val config = Config().apply {
|
||||
update(meta)
|
||||
onChange(this@VisualObjectEditorFragment) { key, _, after ->
|
||||
visualObject.setProperty(key, after)
|
||||
}
|
||||
}
|
||||
//remember old config reference to cleanup listeners
|
||||
currentConfig?.removeListener(this)
|
||||
currentConfig = config
|
||||
config
|
||||
}
|
||||
|
||||
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
|
||||
it?.let {
|
||||
ConfigEditor(it, descriptorProperty.get()).root
|
||||
}
|
||||
}
|
||||
|
||||
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding() {
|
||||
VBox().apply {
|
||||
item?.styles?.forEach { styleName ->
|
||||
val styleMeta = item?.findStyle(styleName)
|
||||
if (styleMeta != null) {
|
||||
titledpane(styleName, node = MetaViewer(styleMeta).root)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val root: Parent = vbox {
|
||||
titledpane("Properties", collapsible = false) {
|
||||
contentProperty().bind(configEditorProperty)
|
||||
}
|
||||
titledpane("Styles", collapsible = false) {
|
||||
visibleWhen(itemProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false })
|
||||
contentProperty().bind(styleBoxProperty)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.control.SelectionMode
|
||||
import javafx.scene.control.TreeItem
|
||||
import tornadofx.*
|
||||
|
||||
private fun toTreeItem(visualObject: VisualObject, title: String): TreeItem<Pair<String, VisualObject>> {
|
||||
return object : TreeItem<Pair<String, VisualObject>>(title to visualObject) {
|
||||
init {
|
||||
if (visualObject is VisualGroup) {
|
||||
//lazy populate the tree
|
||||
expandedProperty().onChange { expanded ->
|
||||
if (expanded && children.isEmpty()) {
|
||||
children.setAll(visualObject.children.map {
|
||||
toTreeItem(it.value, it.key.toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isLeaf(): Boolean {
|
||||
return !(visualObject is VisualGroup && visualObject.children.isNotEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class VisualObjectTreeFragment : Fragment() {
|
||||
val itemProperty = SimpleObjectProperty<VisualObject>()
|
||||
var item: VisualObject? by itemProperty
|
||||
|
||||
val selectedProperty = SimpleObjectProperty<VisualObject>()
|
||||
|
||||
override val root = vbox {
|
||||
titledpane("Object tree", collapsible = false) {
|
||||
treeview<Pair<String, VisualObject>> {
|
||||
cellFormat {
|
||||
text = item.first
|
||||
}
|
||||
itemProperty.onChange { rootObject ->
|
||||
if (rootObject != null) {
|
||||
root = toTreeItem(rootObject, "world")
|
||||
}
|
||||
}
|
||||
selectionModel.selectionMode = SelectionMode.SINGLE
|
||||
val selectedValue = selectionModel.selectedItemProperty().objectBinding { it?.value?.second }
|
||||
selectedProperty.bind(selectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@ -4,9 +4,9 @@ import hep.dataforge.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.meta.toConfig
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.fx.meta.ConfigEditor
|
||||
import hep.dataforge.vis.fx.meta.FXMeta
|
||||
import hep.dataforge.vis.fx.meta.MetaViewer
|
||||
import hep.dataforge.vis.fx.editor.ConfigEditor
|
||||
import hep.dataforge.vis.fx.editor.FXMeta
|
||||
import hep.dataforge.vis.fx.editor.MetaViewer
|
||||
import javafx.geometry.Orientation
|
||||
import tornadofx.*
|
||||
|
||||
@ -16,16 +16,16 @@ class MetaEditorDemoApp : App(MetaEditorDemo::class)
|
||||
class MetaEditorDemo : View("Meta editor demo") {
|
||||
|
||||
val meta = buildMeta {
|
||||
"aNode" to {
|
||||
"innerNode" to {
|
||||
"innerValue" to true
|
||||
"aNode" put {
|
||||
"innerNode" put {
|
||||
"innerValue" put true
|
||||
}
|
||||
"b" to 223
|
||||
"c" to "StringValue"
|
||||
"b" put 223
|
||||
"c" put "StringValue"
|
||||
}
|
||||
}.toConfig()
|
||||
|
||||
val descriptor = NodeDescriptor.build {
|
||||
val descriptor = NodeDescriptor {
|
||||
node("aNode") {
|
||||
info = "A root demo node"
|
||||
value("b") {
|
@ -1,23 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.openjfx.gradle.JavaFXOptions
|
||||
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("org.openjfx.javafxplugin")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-common"))
|
||||
api("no.tornado:tornadofx:1.7.19")
|
||||
api("no.tornado:tornadofx-controlsfx:0.1")
|
||||
}
|
||||
|
||||
configure<JavaFXOptions> {
|
||||
modules("javafx.controls")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions{
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
@ -74,5 +74,5 @@ class JSRootDemoApp : ApplicationBase() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" to presenter.dispose())
|
||||
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" put presenter.dispose())
|
||||
}
|
@ -15,37 +15,37 @@ class JSRootGeometry(parent: VisualObject?, meta: Meta) : DisplayLeaf(parent, me
|
||||
var facesLimit by int(0)
|
||||
|
||||
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
|
||||
"_typename" to "TGeoBBox"
|
||||
"fDX" to xSize
|
||||
"fDY" to ySize
|
||||
"fDZ" to zSize
|
||||
"_typename" put "TGeoBBox"
|
||||
"fDX" put xSize
|
||||
"fDY" put ySize
|
||||
"fDZ" put zSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GDML union
|
||||
*/
|
||||
operator fun Meta.plus(other: Meta) = buildMeta {
|
||||
"fNode.fLeft" to this
|
||||
"fNode.fRight" to other
|
||||
"fNode._typename" to "TGeoUnion"
|
||||
"fNode.fLeft" put this
|
||||
"fNode.fRight" put other
|
||||
"fNode._typename" put "TGeoUnion"
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GDML subtraction
|
||||
*/
|
||||
operator fun Meta.minus(other: Meta) = buildMeta {
|
||||
"fNode.fLeft" to this
|
||||
"fNode.fRight" to other
|
||||
"fNode._typename" to "TGeoSubtraction"
|
||||
"fNode.fLeft" put this
|
||||
"fNode.fRight" put other
|
||||
"fNode._typename" put "TGeoSubtraction"
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect two GDML geometries
|
||||
*/
|
||||
infix fun Meta.intersect(other: Meta) = buildMeta {
|
||||
"fNode.fLeft" to this
|
||||
"fNode.fRight" to other
|
||||
"fNode._typename" to "TGeoIntersection"
|
||||
"fNode.fLeft" put this
|
||||
"fNode.fRight" put other
|
||||
"fNode._typename" put "TGeoIntersection"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -7,13 +7,14 @@ kotlin {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-spatial"))
|
||||
api("scientifik:gdml:0.1.4-dev-1")
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-spatial"))
|
||||
api("scientifik:gdml:0.1.4")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//tasks{
|
||||
// val jsBrowserWebpack by getting(KotlinWebpack::class) {
|
||||
// sourceMaps = false
|
||||
// }
|
||||
//}
|
@ -1,20 +1,21 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.meta.builder
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import scientifik.gdml.GDML
|
||||
import scientifik.gdml.GDMLGroup
|
||||
import scientifik.gdml.GDMLMaterial
|
||||
import scientifik.gdml.GDMLSolid
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.common.useStyle
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
|
||||
import scientifik.gdml.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class GDMLTransformer(val root: GDML) {
|
||||
private val materialCache = HashMap<GDMLMaterial, Meta>()
|
||||
private val random = Random(111)
|
||||
//private val materialCache = HashMap<GDMLMaterial, Meta>()
|
||||
private val random = Random(222)
|
||||
|
||||
enum class Action{
|
||||
enum class Action {
|
||||
ACCEPT,
|
||||
REJECT,
|
||||
CACHE
|
||||
@ -23,45 +24,60 @@ class GDMLTransformer(val root: GDML) {
|
||||
/**
|
||||
* A special group for local templates
|
||||
*/
|
||||
val templates by lazy { VisualGroup3D() }
|
||||
val proto by lazy { VisualGroup3D() }
|
||||
private val styleCache = HashMap<Name, Meta>()
|
||||
|
||||
var lUnit: LUnit = LUnit.MM
|
||||
var resolveColor: ColorResolver = { material, _ ->
|
||||
val materialColor = materialCache.getOrPut(material) {
|
||||
buildMeta {
|
||||
"color" to random.nextInt(0, Int.MAX_VALUE)
|
||||
}
|
||||
}
|
||||
|
||||
if (this?.physVolumes?.isEmpty() != false) {
|
||||
materialColor
|
||||
} else {
|
||||
materialColor.builder().apply { "opacity" to 0.5 }
|
||||
}
|
||||
}
|
||||
|
||||
var solidAction: (GDMLSolid) -> Action = { Action.CACHE }
|
||||
var volumeAction: (GDMLGroup) -> Action = { Action.ACCEPT }
|
||||
var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
|
||||
|
||||
fun printStatistics() {
|
||||
println("Solids:")
|
||||
solidCounter.entries.sortedByDescending { it.value }.forEach {
|
||||
println("\t$it")
|
||||
|
||||
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { parent, _ ->
|
||||
lUnit = LUnit.CM
|
||||
if (parent.physVolumes.isNotEmpty()) {
|
||||
useStyle("opaque") {
|
||||
Material3D.MATERIAL_OPACITY_KEY put 0.3
|
||||
}
|
||||
}
|
||||
println("Solids total: ${solidCounter.values.sum()}")
|
||||
}
|
||||
|
||||
private val solidCounter = HashMap<String, Int>()
|
||||
|
||||
internal fun solidAdded(solid: GDMLSolid) {
|
||||
solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
|
||||
fun VisualObject3D.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||
styleCache.getOrPut(name.toName()) {
|
||||
buildMeta(builder)
|
||||
}
|
||||
useStyle(name)
|
||||
}
|
||||
|
||||
internal fun configureSolid(obj: VisualObject3D, parent: GDMLVolume, solid: GDMLSolid) {
|
||||
val material = parent.materialref.resolve(root) ?: GDMLElement(parent.materialref.ref)
|
||||
|
||||
val styleName = "material[${material.name}]"
|
||||
|
||||
obj.useStyle(styleName) {
|
||||
MATERIAL_COLOR_KEY put random.nextInt(16777216)
|
||||
"gdml.material" put material.name
|
||||
}
|
||||
|
||||
obj.solidConfiguration(parent, solid)
|
||||
}
|
||||
//
|
||||
// internal fun solidAdded(solid: GDMLSolid) {
|
||||
// solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
|
||||
// }
|
||||
|
||||
var onFinish: GDMLTransformer.() -> Unit = {}
|
||||
|
||||
internal fun finished(final: VisualGroup3D) {
|
||||
final.templates = templates
|
||||
internal fun finalize(final: VisualGroup3D): VisualGroup3D {
|
||||
final.prototypes = proto
|
||||
styleCache.forEach {
|
||||
final.styleSheet {
|
||||
define(it.key.toString(), it.value)
|
||||
}
|
||||
}
|
||||
final.rotationOrder = RotationOrder.ZXY
|
||||
onFinish(this@GDMLTransformer)
|
||||
return final
|
||||
}
|
||||
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.common.asName
|
||||
import hep.dataforge.vis.common.get
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.World.ONE
|
||||
import hep.dataforge.vis.spatial.World.ZERO
|
||||
import scientifik.gdml.*
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
@ -11,41 +15,45 @@ import kotlin.math.sin
|
||||
|
||||
private fun VisualObject3D.withPosition(
|
||||
lUnit: LUnit,
|
||||
pos: GDMLPosition? = null,
|
||||
rotation: GDMLRotation? = null,
|
||||
scale: GDMLScale? = null
|
||||
newPos: GDMLPosition? = null,
|
||||
newRotation: GDMLRotation? = null,
|
||||
newScale: GDMLScale? = null
|
||||
): VisualObject3D = apply {
|
||||
pos?.let {
|
||||
this@withPosition.x = pos.x(lUnit)
|
||||
this@withPosition.y = pos.y(lUnit)
|
||||
this@withPosition.z = pos.z(lUnit)
|
||||
newPos?.let {
|
||||
val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit))
|
||||
if (position != null || point != ZERO) {
|
||||
position = point
|
||||
}
|
||||
}
|
||||
rotation?.let {
|
||||
this@withPosition.rotationX = rotation.x()
|
||||
this@withPosition.rotationY = rotation.y()
|
||||
this@withPosition.rotationZ = rotation.z()
|
||||
this@withPosition.rotationOrder=RotationOrder.ZXY
|
||||
newRotation?.let {
|
||||
val point = Point3D(it.x(), it.y(), it.z())
|
||||
if (rotation != null || point != ZERO) {
|
||||
rotation = point
|
||||
}
|
||||
//this@withPosition.rotationOrder = RotationOrder.ZXY
|
||||
}
|
||||
scale?.let {
|
||||
this@withPosition.scaleX = scale.x.toFloat()
|
||||
this@withPosition.scaleY = scale.y.toFloat()
|
||||
this@withPosition.scaleZ = scale.z.toFloat()
|
||||
newScale?.let {
|
||||
val point = Point3D(it.x, it.y, it.z)
|
||||
if (scale != null || point != ONE) {
|
||||
scale = point
|
||||
}
|
||||
}
|
||||
//TODO convert units if needed
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline operator fun Number.times(d: Double) = toDouble() * d
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
private inline operator fun Number.times(f: Float) = toFloat() * f
|
||||
|
||||
private fun VisualGroup3D.addSolid(
|
||||
context: GDMLTransformer,
|
||||
solid: GDMLSolid,
|
||||
name: String? = null,
|
||||
name: String = "",
|
||||
block: VisualObject3D.() -> Unit = {}
|
||||
): VisualObject3D {
|
||||
context.solidAdded(solid)
|
||||
//context.solidAdded(solid)
|
||||
val lScale = solid.lscale(context.lUnit)
|
||||
val aScale = solid.ascale()
|
||||
return when (solid) {
|
||||
@ -78,7 +86,7 @@ private fun VisualGroup3D.addSolid(
|
||||
val innerSolid = solid.solidref.resolve(context.root)
|
||||
?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined")
|
||||
|
||||
addSolid(context, innerSolid) {
|
||||
addSolid(context, innerSolid, name) {
|
||||
block()
|
||||
scaleX *= solid.scale.x.toFloat()
|
||||
scaleY *= solid.scale.y.toFloat()
|
||||
@ -147,7 +155,8 @@ private fun VisualGroup3D.addPhysicalVolume(
|
||||
|
||||
when (context.volumeAction(volume)) {
|
||||
GDMLTransformer.Action.ACCEPT -> {
|
||||
this[physVolume.name] = volume(context, volume).apply {
|
||||
val group = volume(context, volume)
|
||||
this[physVolume.name ?: ""] = group.apply {
|
||||
withPosition(
|
||||
context.lUnit,
|
||||
physVolume.resolvePosition(context.root),
|
||||
@ -157,12 +166,12 @@ private fun VisualGroup3D.addPhysicalVolume(
|
||||
}
|
||||
}
|
||||
GDMLTransformer.Action.CACHE -> {
|
||||
val name = volumesName + volume.name.asName()
|
||||
if (context.templates[name] == null) {
|
||||
context.templates[name] = volume(context, volume)
|
||||
val fullName = volumesName + volume.name.asName()
|
||||
if (context.proto[fullName] == null) {
|
||||
context.proto[fullName] = volume(context, volume)
|
||||
}
|
||||
|
||||
this[physVolume.name] = Proxy(name).apply {
|
||||
this[physVolume.name ?: ""] = Proxy(fullName).apply {
|
||||
withPosition(
|
||||
context.lUnit,
|
||||
physVolume.resolvePosition(context.root),
|
||||
@ -185,7 +194,8 @@ private fun VisualGroup3D.addDivisionVolume(
|
||||
?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved")
|
||||
|
||||
//TODO add divisions
|
||||
add(
|
||||
set(
|
||||
Name.EMPTY,
|
||||
volume(
|
||||
context,
|
||||
volume
|
||||
@ -193,12 +203,7 @@ private fun VisualGroup3D.addDivisionVolume(
|
||||
)
|
||||
}
|
||||
|
||||
private fun VisualGroup3D.addVolume(
|
||||
context: GDMLTransformer,
|
||||
group: GDMLGroup
|
||||
) {
|
||||
this[group.name] = volume(context, group)
|
||||
}
|
||||
private val solidsName = "solids".asName()
|
||||
|
||||
private fun volume(
|
||||
context: GDMLTransformer,
|
||||
@ -208,18 +213,17 @@ private fun volume(
|
||||
if (group is GDMLVolume) {
|
||||
val solid = group.solidref.resolve(context.root)
|
||||
?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined")
|
||||
val material = group.materialref.resolve(context.root) ?: GDMLElement(group.materialref.ref)
|
||||
|
||||
when (context.solidAction(solid)) {
|
||||
GDMLTransformer.Action.ACCEPT -> {
|
||||
addSolid(context, solid, solid.name) {
|
||||
this.material = context.resolveColor(group, material, solid)
|
||||
context.configureSolid(this, group, solid)
|
||||
}
|
||||
}
|
||||
GDMLTransformer.Action.CACHE -> {
|
||||
if (context.templates[solid.name] == null) {
|
||||
context.templates.addSolid(context, solid, solid.name) {
|
||||
this.material = context.resolveColor(group, material, solid)
|
||||
if (context.proto[solid.name] == null) {
|
||||
context.proto.addSolid(context, solid, solid.name) {
|
||||
context.configureSolid(this, group, solid)
|
||||
}
|
||||
}
|
||||
ref(solid.name.asName(), solid.name)
|
||||
@ -241,14 +245,18 @@ private fun volume(
|
||||
}
|
||||
}
|
||||
|
||||
typealias ColorResolver = GDMLGroup?.(GDMLMaterial, GDMLSolid?) -> Meta
|
||||
|
||||
|
||||
fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
|
||||
|
||||
val context = GDMLTransformer(this).apply(block)
|
||||
|
||||
return volume(context, world).also {
|
||||
context.finished(it)
|
||||
}
|
||||
return context.finalize(volume(context, world))
|
||||
}
|
||||
|
||||
/**
|
||||
* Append gdml node to the group
|
||||
*/
|
||||
fun VisualGroup3D.gdml(gdml: GDML, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
|
||||
val visual = gdml.toVisual(transformer)
|
||||
//println(Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual))
|
||||
set(key, visual)
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml.demo
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vis.hmr.ApplicationBase
|
||||
import hep.dataforge.vis.hmr.startApplication
|
||||
import hep.dataforge.vis.spatial.gdml.GDMLTransformer
|
||||
import hep.dataforge.vis.spatial.gdml.LUnit
|
||||
import hep.dataforge.vis.spatial.gdml.toVisual
|
||||
import hep.dataforge.vis.spatial.three.ThreePlugin
|
||||
import hep.dataforge.vis.spatial.three.output
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import org.w3c.dom.HTMLDivElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.files.FileList
|
||||
import org.w3c.files.FileReader
|
||||
import org.w3c.files.get
|
||||
import scientifik.gdml.GDML
|
||||
import kotlin.browser.document
|
||||
import kotlin.dom.clear
|
||||
|
||||
private class GDMLDemoApp : ApplicationBase() {
|
||||
|
||||
|
||||
/**
|
||||
* Handle mouse drag according to https://www.html5rocks.com/en/tutorials/file/dndfiles/
|
||||
*/
|
||||
private fun handleDragOver(event: Event) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
event.asDynamic().dataTransfer.dropEffect = "copy"
|
||||
}
|
||||
|
||||
/**
|
||||
* Load data from text file
|
||||
*/
|
||||
private fun loadData(event: Event, block: suspend CoroutineScope.(String) -> Unit) {
|
||||
event.stopPropagation()
|
||||
event.preventDefault()
|
||||
|
||||
|
||||
val file = (event.asDynamic().dataTransfer.files as FileList)[0]
|
||||
?: throw RuntimeException("Failed to load file")
|
||||
|
||||
FileReader().apply {
|
||||
onload = {
|
||||
val string = result as String
|
||||
GlobalScope.launch {
|
||||
block(string)
|
||||
}
|
||||
}
|
||||
readAsText(file)
|
||||
}
|
||||
}
|
||||
|
||||
private fun spinner(show: Boolean) {
|
||||
val style = if (show) {
|
||||
"display:block;"
|
||||
} else {
|
||||
"display:none;"
|
||||
}
|
||||
document.getElementById("loader")?.setAttribute("style", style)
|
||||
}
|
||||
|
||||
private fun message(message: String?) {
|
||||
val element = document.getElementById("message")
|
||||
if (message == null) {
|
||||
element?.setAttribute("style", "display:none;")
|
||||
} else {
|
||||
element?.textContent = message
|
||||
element?.setAttribute("style", "display:block;")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun start(state: Map<String, Any>) {
|
||||
|
||||
val context = Global.context("demo") {}
|
||||
val three = context.plugins.load(ThreePlugin)
|
||||
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
|
||||
|
||||
val canvas = document.getElementById("canvas") ?: error("Element with id canvas not found on page")
|
||||
canvas.clear()
|
||||
|
||||
val action: suspend CoroutineScope.(String) -> Unit = { it ->
|
||||
canvas.clear()
|
||||
launch { spinner(true) }
|
||||
launch { message("Loading GDML") }
|
||||
val gdml = GDML.format.parse(GDML.serializer(), it)
|
||||
launch { message("Converting GDML into DF-VIS format") }
|
||||
val visual = gdml.toVisual {
|
||||
lUnit = LUnit.CM
|
||||
volumeAction = { volume ->
|
||||
when {
|
||||
volume.name.startsWith("ecal") -> GDMLTransformer.Action.REJECT
|
||||
volume.name.startsWith("U") -> GDMLTransformer.Action.CACHE
|
||||
volume.name.startsWith("V") -> GDMLTransformer.Action.CACHE
|
||||
else -> GDMLTransformer.Action.ACCEPT
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
launch { message("Rendering") }
|
||||
val output = three.output(canvas){
|
||||
"axis" to {
|
||||
"size" to 100
|
||||
}
|
||||
}
|
||||
output.render(visual)
|
||||
launch {
|
||||
message(null)
|
||||
spinner(false)
|
||||
}
|
||||
}
|
||||
|
||||
(document.getElementById("drop_zone") as? HTMLDivElement)?.apply {
|
||||
addEventListener("dragover", { handleDragOver(it) }, false)
|
||||
addEventListener("drop", { loadData(it, action) }, false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun main() {
|
||||
startApplication(::GDMLDemoApp)
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>Three js demo for particle physics</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
|
||||
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="main.css">
|
||||
<script type="text/javascript" src="main.bundle.js"></script>
|
||||
</head>
|
||||
<body class="testApp">
|
||||
<div class="container" id="drop_zone" data-toggle="tooltip" data-placement="right"
|
||||
title="Для загрузки данных в текстовом формате, надо перетащить файл сюда">
|
||||
Загрузить данные
|
||||
<br/>
|
||||
(перетащить файл сюда)
|
||||
</div>
|
||||
<div class="container">
|
||||
<h1>GDML demo</h1>
|
||||
</div>
|
||||
<div class="container loader" id="loader" style="display:none;"></div>
|
||||
<div class="container animate-bottom" id="message" style="display:none;"></div>
|
||||
<div class="container" id="canvas"></div>
|
||||
|
||||
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
|
||||
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
|
||||
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
|
||||
crossorigin="anonymous"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
|
||||
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
|
||||
crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
@ -1,13 +0,0 @@
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import kotlinx.serialization.modules.SerialModuleCollector
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal val SerialDescriptor.jsonType
|
||||
get() = when (this.kind) {
|
||||
StructureKind.LIST -> "array"
|
||||
PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG,
|
||||
PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> "number"
|
||||
PrimitiveKind.STRING, PrimitiveKind.CHAR, UnionKind.ENUM_KIND -> "string"
|
||||
PrimitiveKind.BOOLEAN -> "boolean"
|
||||
else -> "object"
|
||||
}
|
||||
|
||||
|
||||
private fun SerialModule.enumerate(type: KClass<*>): Sequence<SerialDescriptor> {
|
||||
val list = ArrayList<SerialDescriptor>()
|
||||
fun send(descriptor: SerialDescriptor) = list.add(descriptor)
|
||||
|
||||
val enumerator = object : SerialModuleCollector {
|
||||
override fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>) {
|
||||
if (kClass == type) {
|
||||
send(serializer.descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <Base : Any, Sub : Base> polymorphic(
|
||||
baseClass: KClass<Base>,
|
||||
actualClass: KClass<Sub>,
|
||||
actualSerializer: KSerializer<Sub>
|
||||
) {
|
||||
if (baseClass == type) {
|
||||
send(actualSerializer.descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
dumpTo(enumerator)
|
||||
return list.asSequence()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an [JsonObject] which contains Json Schema of given [descriptor].
|
||||
*
|
||||
* Schema can contain following fields:
|
||||
* `description`, `type` for all descriptors;
|
||||
* `properties` and `required` for objects;
|
||||
* `enum` for enums;
|
||||
* `items` for arrays.
|
||||
*
|
||||
* User can modify this schema to add additional validation keywords
|
||||
* (as per [https://json-schema.org/latest/json-schema-validation.html])
|
||||
* if they want.
|
||||
*/
|
||||
private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): JsonObject {
|
||||
|
||||
if (descriptor.name in arrayOf(
|
||||
"hep.dataforge.vis.spatial.Point3D",
|
||||
"hep.dataforge.vis.spatial.Point2D",
|
||||
Meta::class.qualifiedName
|
||||
)
|
||||
) return json {
|
||||
"\$ref" to "#/definitions/${descriptor.name}"
|
||||
}
|
||||
|
||||
|
||||
val properties: MutableMap<String, JsonObject> = mutableMapOf()
|
||||
val requiredProperties: MutableSet<String> = mutableSetOf()
|
||||
val isEnum = descriptor.kind == UnionKind.ENUM_KIND
|
||||
val isPolymorphic = descriptor.kind is PolymorphicKind
|
||||
|
||||
|
||||
if (!isEnum && !isPolymorphic) descriptor.elementDescriptors().forEachIndexed { index, child ->
|
||||
val elementName = descriptor.getElementName(index)
|
||||
|
||||
properties[elementName] = when (elementName) {
|
||||
"templates" -> json {
|
||||
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
|
||||
}
|
||||
"properties" -> json {
|
||||
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
|
||||
}
|
||||
"first", "second" -> json{
|
||||
"\$ref" to "#/definitions/children"
|
||||
}
|
||||
"styleSheet" -> json {
|
||||
"type" to "object"
|
||||
"additionalProperties" to json {
|
||||
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
|
||||
}
|
||||
}
|
||||
in arrayOf("children") -> json {
|
||||
"type" to "object"
|
||||
"additionalProperties" to json {
|
||||
"\$ref" to "#/definitions/children"
|
||||
}
|
||||
}
|
||||
else -> jsonSchema(child, context)
|
||||
}
|
||||
|
||||
if (!descriptor.isElementOptional(index)) requiredProperties.add(elementName)
|
||||
}
|
||||
|
||||
val jsonType = descriptor.jsonType
|
||||
val objectData: MutableMap<String, JsonElement> = mutableMapOf(
|
||||
"description" to JsonLiteral(descriptor.name),
|
||||
"type" to JsonLiteral(jsonType)
|
||||
)
|
||||
if (isEnum) {
|
||||
val allElementNames = (0 until descriptor.elementsCount).map(descriptor::getElementName)
|
||||
objectData += "enum" to JsonArray(allElementNames.map(::JsonLiteral))
|
||||
}
|
||||
when (jsonType) {
|
||||
"object" -> {
|
||||
objectData["properties"] = JsonObject(properties)
|
||||
val required = requiredProperties.map { JsonLiteral(it) }
|
||||
if (required.isNotEmpty()) {
|
||||
objectData["required"] = JsonArray(required)
|
||||
}
|
||||
}
|
||||
"array" -> objectData["items"] = properties.values.let {
|
||||
check(it.size == 1) { "Array descriptor has returned inconsistent number of elements: expected 1, found ${it.size}" }
|
||||
it.first()
|
||||
}
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
return JsonObject(objectData)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val context = Visual3DPlugin.serialModule
|
||||
val definitions = json {
|
||||
"children" to json {
|
||||
"anyOf" to jsonArray {
|
||||
context.enumerate(VisualObject3D::class).forEach {
|
||||
if (it.name == "hep.dataforge.vis.spatial.VisualGroup3D") {
|
||||
+json {
|
||||
"\$ref" to "#/definitions/${it.name}"
|
||||
}
|
||||
} else {
|
||||
+jsonSchema(it, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"hep.dataforge.vis.spatial.Point3D" to jsonSchema(Point3DSerializer.descriptor, context)
|
||||
"hep.dataforge.vis.spatial.Point2D" to jsonSchema(Point2DSerializer.descriptor, context)
|
||||
"hep.dataforge.vis.spatial.VisualGroup3D" to jsonSchema(VisualGroup3D.serializer().descriptor, context)
|
||||
|
||||
}
|
||||
|
||||
println(
|
||||
Json.indented.stringify(
|
||||
JsonObjectSerializer,
|
||||
json {
|
||||
"definitions" to definitions
|
||||
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import nl.adaptivity.xmlutil.StAXReader
|
||||
import scientifik.gdml.GDML
|
||||
import java.io.File
|
||||
|
||||
|
||||
fun main() {
|
||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N_coil.gdml")
|
||||
|
||||
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
|
||||
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
|
||||
val visual = xml.toVisual {
|
||||
lUnit = LUnit.CM
|
||||
}
|
||||
|
||||
//val meta = visual.toMeta()
|
||||
|
||||
val str = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
|
||||
|
||||
println(str)
|
||||
|
||||
//println(Json.indented.stringify(meta.toJson()))
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import nl.adaptivity.xmlutil.StAXReader
|
||||
import scientifik.gdml.GDML
|
||||
import java.io.File
|
||||
|
||||
fun main() {
|
||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml")
|
||||
//val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml")
|
||||
|
||||
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
|
||||
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
|
||||
val visual = xml.toVisual {
|
||||
lUnit = LUnit.CM
|
||||
volumeAction = { volume ->
|
||||
when {
|
||||
volume.name.startsWith("ecal") -> GDMLTransformer.Action.CACHE
|
||||
volume.name.startsWith("U") -> GDMLTransformer.Action.CACHE
|
||||
volume.name.startsWith("V") -> GDMLTransformer.Action.CACHE
|
||||
else -> GDMLTransformer.Action.ACCEPT
|
||||
}
|
||||
}
|
||||
onFinish = { printStatistics() }
|
||||
}
|
||||
|
||||
val template = visual.getTemplate("volumes.ecal01mod".toName())
|
||||
println(template)
|
||||
visual.flatMap { (it as? VisualGroup3D) ?: listOf(it) }.forEach {
|
||||
if(it.parent==null) error("")
|
||||
}
|
||||
//readLine()
|
||||
//val meta = visual.toMeta()
|
||||
// val tmpFile = File.createTempFile("dataforge-visual", "json")
|
||||
//tmpFile.writeText(meta.toString())
|
||||
//println(tmpFile.absoluteFile)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import nl.adaptivity.xmlutil.StAXReader
|
||||
import scientifik.gdml.GDML
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
fun GDML.Companion.readFile(file: Path): GDML {
|
||||
val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8")
|
||||
return GDML.format.parse(GDML.serializer(), xmlReader)
|
||||
}
|
||||
|
||||
fun VisualGroup3D.gdml(file: Path, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
|
||||
val gdml = GDML.readFile(file)
|
||||
gdml(gdml, key, transformer)
|
||||
}
|
||||
|
@ -5,10 +5,12 @@ import org.junit.Test
|
||||
import scientifik.gdml.GDML
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import kotlin.test.Ignore
|
||||
|
||||
class TestConvertor {
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
fun testBMNGeometry() {
|
||||
val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
|
||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml")
|
||||
@ -24,6 +26,7 @@ class TestConvertor {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
fun testCubes() {
|
||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml ")
|
||||
val stream = if (file.exists()) {
|
||||
|
@ -1,16 +1,15 @@
|
||||
import org.openjfx.gradle.JavaFXOptions
|
||||
import scientifik.useSerialization
|
||||
|
||||
plugins {
|
||||
id("scientifik.mpp")
|
||||
id("org.openjfx.javafxplugin")
|
||||
}
|
||||
|
||||
scientifik{
|
||||
serialization = true
|
||||
}
|
||||
useSerialization()
|
||||
|
||||
kotlin {
|
||||
jvm{
|
||||
jvm {
|
||||
withJava()
|
||||
}
|
||||
sourceSets {
|
||||
@ -19,19 +18,22 @@ kotlin {
|
||||
api(project(":dataforge-vis-common"))
|
||||
}
|
||||
}
|
||||
jvmMain{
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation(project(":dataforge-vis-fx"))
|
||||
implementation("org.fxyz3d:fxyz3d:0.4.0")
|
||||
implementation("org.fxyz3d:fxyz3d:0.5.2") {
|
||||
exclude(module = "slf4j-simple")
|
||||
}
|
||||
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}")
|
||||
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
|
||||
exclude(module = "slf4j-simple")
|
||||
}
|
||||
}
|
||||
}
|
||||
jsMain{
|
||||
jsMain {
|
||||
dependencies {
|
||||
api("info.laht.threekt:threejs-wrapper:0.106-npm-3")
|
||||
// api(project(":wrappers"))
|
||||
implementation(npm("three", "0.106.2"))
|
||||
implementation(npm("@hi-level/three-csg", "1.0.6"))
|
||||
implementation(npm("style-loader"))
|
||||
implementation(npm("element-resize-event"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,17 +2,22 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.float
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.VisualFactory
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@Serializable
|
||||
data class Box(
|
||||
@SerialName("3d.box")
|
||||
class Box(
|
||||
val xSize: Float,
|
||||
val ySize: Float,
|
||||
val zSize: Float
|
||||
@ -27,9 +32,9 @@ data class Box(
|
||||
|
||||
//TODO add helper for color configuration
|
||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||
val dx = xSize.toFloat() / 2
|
||||
val dy = ySize.toFloat() / 2
|
||||
val dz = zSize.toFloat() / 2
|
||||
val dx = xSize / 2
|
||||
val dy = ySize / 2
|
||||
val dz = zSize / 2
|
||||
val node1 = Point3D(-dx, -dy, -dz)
|
||||
val node2 = Point3D(dx, -dy, -dz)
|
||||
val node3 = Point3D(dx, dy, -dz)
|
||||
@ -46,17 +51,6 @@ data class Box(
|
||||
geometryBuilder.face4(node8, node5, node6, node7)
|
||||
}
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
"xSize" to xSize
|
||||
"ySize" to ySize
|
||||
"zSize" to ySize
|
||||
updatePosition()
|
||||
}
|
||||
|
||||
// override fun toMeta(): Meta {
|
||||
// return (Visual3DPlugin.json.toJson(Box.serializer(), this) as JsonObject).toMeta()
|
||||
// }
|
||||
|
||||
companion object : VisualFactory<Box> {
|
||||
const val TYPE = "geometry.3d.box"
|
||||
|
||||
@ -76,6 +70,6 @@ inline fun VisualGroup3D.box(
|
||||
xSize: Number,
|
||||
ySize: Number,
|
||||
zSize: Number,
|
||||
name: String? = null,
|
||||
name: String = "",
|
||||
action: Box.() -> Unit = {}
|
||||
) = Box(xSize.toFloat(), ySize.toFloat(), zSize.toFloat()).apply(action).also { set(name, it) }
|
@ -1,9 +1,9 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -22,46 +22,49 @@ class Composite(
|
||||
val second: VisualObject3D
|
||||
) : AbstractVisualObject(), VisualObject3D {
|
||||
|
||||
init {
|
||||
first.parent = this
|
||||
second.parent = this
|
||||
}
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
"compositeType" to compositeType
|
||||
"first" to first.toMeta()
|
||||
"second" to second.toMeta()
|
||||
updatePosition()
|
||||
}
|
||||
}
|
||||
|
||||
inline fun VisualGroup3D.composite(
|
||||
type: CompositeType,
|
||||
name: String? = null,
|
||||
name: String = "",
|
||||
builder: VisualGroup3D.() -> Unit
|
||||
): Composite {
|
||||
val group = VisualGroup3D().apply(builder)
|
||||
val children = group.filterIsInstance<VisualObject3D>()
|
||||
if (children.size != 2) error("Composite requires exactly two children")
|
||||
return Composite(type, children[0], children[1]).also {
|
||||
if (group.properties != null) {
|
||||
it.config.update(group.config)
|
||||
it.material = group.material
|
||||
it.config.update(group.config)
|
||||
//it.material = group.material
|
||||
|
||||
if(group.position!=null) {
|
||||
it.position = group.position
|
||||
}
|
||||
if(group.rotation!=null) {
|
||||
it.rotation = group.rotation
|
||||
}
|
||||
if(group.scale!=null) {
|
||||
it.scale = group.scale
|
||||
}
|
||||
it.position = group.position
|
||||
it.rotation = group.rotation
|
||||
it.scale = group.scale
|
||||
set(name, it)
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualGroup3D.union(name: String? = null, builder: VisualGroup3D.() -> Unit) =
|
||||
fun VisualGroup3D.union(name: String = "", builder: VisualGroup3D.() -> Unit) =
|
||||
composite(CompositeType.UNION, name, builder = builder)
|
||||
|
||||
fun VisualGroup3D.subtract(name: String? = null, builder: VisualGroup3D.() -> Unit) =
|
||||
fun VisualGroup3D.subtract(name: String = "", builder: VisualGroup3D.() -> Unit) =
|
||||
composite(CompositeType.SUBTRACT, name, builder = builder)
|
||||
|
||||
fun VisualGroup3D.intersect(name: String? = null, builder: VisualGroup3D.() -> Unit) =
|
||||
fun VisualGroup3D.intersect(name: String = "", builder: VisualGroup3D.() -> Unit) =
|
||||
composite(CompositeType.INTERSECT, name, builder = builder)
|
@ -2,11 +2,13 @@
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
* A cylinder or cut cone segment
|
||||
@ -18,7 +20,7 @@ class ConeSegment(
|
||||
var upperRadius: Float,
|
||||
var startAngle: Float = 0f,
|
||||
var angle: Float = PI2
|
||||
) : AbstractVisualObject(), VisualObject3D {
|
||||
) : AbstractVisualObject(), VisualObject3D, Shape {
|
||||
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
@ -26,12 +28,55 @@ class ConeSegment(
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||
val segments = detail ?: 8
|
||||
require(segments >= 4) { "The number of segments in cone segment is too small" }
|
||||
val angleStep = angle / (segments - 1)
|
||||
|
||||
fun shape(r: Float, z: Float): List<Point3D> {
|
||||
return (0 until segments).map { i ->
|
||||
Point3D(r * cos(startAngle + angleStep * i), r * sin(startAngle + angleStep * i), z)
|
||||
}
|
||||
}
|
||||
|
||||
geometryBuilder.apply {
|
||||
|
||||
//creating shape in x-y plane with z = 0
|
||||
val bottomOuterPoints = shape(upperRadius, -height / 2)
|
||||
val upperOuterPoints = shape(radius, height / 2)
|
||||
//outer face
|
||||
(1 until segments).forEach {
|
||||
face4(bottomOuterPoints[it - 1], bottomOuterPoints[it], upperOuterPoints[it], upperOuterPoints[it - 1])
|
||||
}
|
||||
|
||||
if (angle == PI2) {
|
||||
face4(bottomOuterPoints.last(), bottomOuterPoints[0], upperOuterPoints[0], upperOuterPoints.last())
|
||||
}
|
||||
|
||||
val zeroBottom = Point3D(0f, 0f, 0f)
|
||||
val zeroTop = Point3D(0f, 0f, height)
|
||||
(1 until segments).forEach {
|
||||
face(bottomOuterPoints[it - 1], zeroBottom, bottomOuterPoints[it])
|
||||
face(upperOuterPoints[it - 1], upperOuterPoints[it], zeroTop)
|
||||
}
|
||||
if (angle == PI2) {
|
||||
face(bottomOuterPoints.last(), zeroBottom, bottomOuterPoints[0])
|
||||
face(upperOuterPoints.last(), upperOuterPoints[0], zeroTop)
|
||||
} else {
|
||||
face4(zeroTop, zeroBottom, bottomOuterPoints[0], upperOuterPoints[0])
|
||||
face4(zeroTop, zeroBottom, bottomOuterPoints.last(), upperOuterPoints.last())
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline fun VisualGroup3D.cylinder(
|
||||
r: Number,
|
||||
height: Number,
|
||||
name: String? = null,
|
||||
name: String = "",
|
||||
block: ConeSegment.() -> Unit = {}
|
||||
): ConeSegment = ConeSegment(
|
||||
r.toFloat(),
|
||||
|
@ -2,9 +2,8 @@
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
@ -19,19 +18,12 @@ class Convex(val points: List<Point3D>) : AbstractVisualObject(), VisualObject3D
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
"points" to {
|
||||
"point" to points.map { it.toMeta() }
|
||||
}
|
||||
updatePosition()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TYPE = "geometry.3d.convex"
|
||||
}
|
||||
}
|
||||
|
||||
inline fun VisualGroup3D.convex(name: String? = null, action: ConvexBuilder.() -> Unit = {}) =
|
||||
inline fun VisualGroup3D.convex(name: String = "", action: ConvexBuilder.() -> Unit = {}) =
|
||||
ConvexBuilder().apply(action).build().also { set(name, it) }
|
||||
|
||||
class ConvexBuilder {
|
||||
|
@ -1,7 +1,7 @@
|
||||
@file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class)
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -62,7 +62,7 @@ class Extruded(
|
||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||
val shape: Shape2D = shape
|
||||
|
||||
if (shape.size < 3) error("Extruded shape requires more than points per layer")
|
||||
if (shape.size < 3) error("Extruded shape requires more than 2 points per layer")
|
||||
|
||||
/**
|
||||
* Expand the shape for specific layers
|
||||
@ -110,5 +110,5 @@ class Extruded(
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualGroup3D.extrude(name: String? = null, action: Extruded.() -> Unit = {}) =
|
||||
fun VisualGroup3D.extrude(name: String = "", action: Extruded.() -> Unit = {}) =
|
||||
Extruded().apply(action).also { set(name, it) }
|
@ -0,0 +1,30 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@Serializable
|
||||
class Label3D(var text: String, var fontSize: Double, var fontFamily: String) : AbstractVisualObject(),
|
||||
VisualObject3D {
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
}
|
||||
|
||||
fun VisualGroup3D.label(
|
||||
text: String,
|
||||
fontSize: Number = 20,
|
||||
fontFamily: String = "Arial",
|
||||
name: String = "",
|
||||
action: Label3D.() -> Unit = {}
|
||||
) =
|
||||
Label3D(text, fontSize.toDouble(), fontFamily).apply(action).also { set(name, it) }
|
@ -0,0 +1,96 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
|
||||
|
||||
class Material3D(override val config: Config) : Specific {
|
||||
|
||||
var color by string(key = COLOR_KEY)
|
||||
|
||||
var specularColor by string()
|
||||
|
||||
var opacity by float(1f, key = OPACITY_KEY)
|
||||
|
||||
var wireframe by boolean(false, WIREFRAME_KEY)
|
||||
|
||||
companion object : Specification<Material3D> {
|
||||
override fun wrap(config: Config): Material3D = Material3D(config)
|
||||
|
||||
val MATERIAL_KEY = "material".asName()
|
||||
internal val COLOR_KEY = "color".asName()
|
||||
val MATERIAL_COLOR_KEY = MATERIAL_KEY + COLOR_KEY
|
||||
val SPECULAR_COLOR = "specularColor".asName()
|
||||
internal val OPACITY_KEY = "opacity".asName()
|
||||
val MATERIAL_OPACITY_KEY = MATERIAL_KEY + OPACITY_KEY
|
||||
internal val WIREFRAME_KEY = "wireframe".asName()
|
||||
val MATERIAL_WIREFRAME_KEY = MATERIAL_KEY + WIREFRAME_KEY
|
||||
|
||||
val descriptor = NodeDescriptor {
|
||||
value(VisualObject3D.VISIBLE_KEY) {
|
||||
type(ValueType.BOOLEAN)
|
||||
default(true)
|
||||
}
|
||||
node(MATERIAL_KEY) {
|
||||
value(COLOR_KEY) {
|
||||
type(ValueType.STRING, ValueType.NUMBER)
|
||||
default("#ffffff")
|
||||
}
|
||||
value(OPACITY_KEY) {
|
||||
type(ValueType.NUMBER)
|
||||
default(1.0)
|
||||
}
|
||||
value(WIREFRAME_KEY) {
|
||||
type(ValueType.BOOLEAN)
|
||||
default(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualObject3D.color(rgb: String) {
|
||||
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||
}
|
||||
|
||||
fun VisualObject3D.color(rgb: Int) {
|
||||
setProperty(MATERIAL_COLOR_KEY, rgb)
|
||||
}
|
||||
|
||||
fun VisualObject3D.color(r: UByte, g: UByte, b: UByte) = setProperty(
|
||||
MATERIAL_COLOR_KEY,
|
||||
Colors.rgbToMeta(r, g, b)
|
||||
)
|
||||
|
||||
/**
|
||||
* Web colors representation of the color in `#rrggbb` format or HTML name
|
||||
*/
|
||||
var VisualObject3D.color: String?
|
||||
get() = getProperty(MATERIAL_COLOR_KEY)?.let { Colors.fromMeta(it) }
|
||||
set(value) {
|
||||
setProperty(MATERIAL_COLOR_KEY, value)
|
||||
}
|
||||
|
||||
//var VisualObject3D.material: Material3D?
|
||||
// get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
|
||||
// set(value) = setProperty(MATERIAL_KEY, value?.config)
|
||||
|
||||
fun VisualObject3D.material(builder: Material3D.() -> Unit) {
|
||||
val node = config[Material3D.MATERIAL_KEY].node
|
||||
if (node != null) {
|
||||
Material3D.update(node, builder)
|
||||
} else {
|
||||
config[Material3D.MATERIAL_KEY] = Material3D(builder)
|
||||
}
|
||||
}
|
||||
|
||||
var VisualObject3D.opacity: Double?
|
||||
get() = getProperty(MATERIAL_OPACITY_KEY).double
|
||||
set(value) {
|
||||
setProperty(MATERIAL_OPACITY_KEY, value)
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.number
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@Serializable
|
||||
class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject3D {
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
//var lineType by string()
|
||||
var thickness by number(1.0, key = "material.thickness")
|
||||
|
||||
}
|
||||
|
||||
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =
|
||||
PolyLine(points.toList()).apply(action).also { set(name, it) }
|
@ -1,22 +1,32 @@
|
||||
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class)
|
||||
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class, ConfigSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.NameSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.NameSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.Laminate
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.common.*
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.set
|
||||
|
||||
/**
|
||||
* A proxy [VisualObject3D] to reuse a template object
|
||||
*/
|
||||
@Serializable
|
||||
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D {
|
||||
@SerialName("3d.proxy")
|
||||
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
@ -28,36 +38,133 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D {
|
||||
/**
|
||||
* Recursively search for defined template in the parent
|
||||
*/
|
||||
val template: VisualObject3D
|
||||
get() = (parent as? VisualGroup3D)?.getTemplate(templateName)
|
||||
val prototype: VisualObject3D
|
||||
get() = (parent as? VisualGroup3D)?.getPrototype(templateName)
|
||||
?: error("Template with name $templateName not found in $parent")
|
||||
|
||||
override val styleSheet: StyleSheet
|
||||
get() = (parent as? VisualGroup)?.styleSheet ?: StyleSheet(this)
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
super.getProperty(name, false) ?: template.getProperty(name, false) ?: parent?.getProperty(name, inherit)
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
?: prototype.getProperty(name)
|
||||
?: parent?.getProperty(name)
|
||||
} else {
|
||||
super.getProperty(name, false) ?: template.getProperty(name, false)
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
?: prototype.getProperty(name, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
//TODO add reference to child
|
||||
updatePosition()
|
||||
override val children: Map<NameToken, ProxyChild>
|
||||
get() = (prototype as? MutableVisualGroup)?.children
|
||||
?.filter { !it.key.toString().startsWith("@") }
|
||||
?.mapValues {
|
||||
ProxyChild(it.key.asName())
|
||||
} ?: emptyMap()
|
||||
|
||||
@Transient
|
||||
private val propertyCache: HashMap<Name, Config> = HashMap()
|
||||
|
||||
fun childPropertyName(childName: Name, propertyName: Name): Name {
|
||||
return NameToken(PROXY_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
|
||||
}
|
||||
|
||||
private fun prototypeFor(name: Name): VisualObject {
|
||||
return (prototype as? VisualGroup)?.get(name)
|
||||
?: error("Prototype with name $name not found in $this")
|
||||
}
|
||||
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||
|
||||
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
|
||||
|
||||
@Serializable
|
||||
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
|
||||
|
||||
val prototype: VisualObject get() = prototypeFor(name)
|
||||
|
||||
override val styleSheet: StyleSheet get() = this@Proxy.styleSheet
|
||||
|
||||
override val children: Map<NameToken, VisualObject>
|
||||
get() = (prototype as? VisualGroup)?.children?.mapValues { (key, _) ->
|
||||
ProxyChild(
|
||||
name + key.asName()
|
||||
)
|
||||
} ?: emptyMap()
|
||||
|
||||
override var properties: Config?
|
||||
get() = propertyCache[name]
|
||||
set(value) {
|
||||
if (value == null) {
|
||||
propertyCache.remove(name)?.also {
|
||||
//Removing listener if it is present
|
||||
removeChangeListener(this@Proxy)
|
||||
}
|
||||
} else {
|
||||
propertyCache[name] = value.also {
|
||||
onPropertyChange(this@Proxy) { propertyName, before, after ->
|
||||
this@Proxy.propertyChanged(childPropertyName(name, propertyName), before, after)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
?: prototype.getProperty(name)
|
||||
?: parent?.getProperty(name)
|
||||
} else {
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
?: prototype.getProperty(name, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PROXY_CHILD_PROPERTY_PREFIX = "@child"
|
||||
}
|
||||
}
|
||||
|
||||
//fun VisualGroup3D.proxy(
|
||||
// templateName: Name,
|
||||
// //name: String? = null,
|
||||
// builder: VisualGroup3D.() -> Unit
|
||||
//): Proxy {
|
||||
// val template = getTemplate(templateName) ?: templates.builder()
|
||||
// return Proxy(this, templateName).also { set(name, it) }
|
||||
//}
|
||||
val VisualObject.prototype: VisualObject
|
||||
get() = when (this) {
|
||||
is Proxy -> prototype
|
||||
is Proxy.ProxyChild -> prototype
|
||||
else -> this
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ref for existing prototype
|
||||
*/
|
||||
inline fun VisualGroup3D.ref(
|
||||
templateName: Name,
|
||||
name: String? = null,
|
||||
action: Proxy.() -> Unit = {}
|
||||
) = Proxy(templateName).apply(action).also { set(name, it) }
|
||||
name: String = "",
|
||||
block: Proxy.() -> Unit = {}
|
||||
) = Proxy(templateName).apply(block).also { set(name, it) }
|
||||
|
||||
/**
|
||||
* Add new proxy wrapping given object and automatically adding it to the prototypes
|
||||
*/
|
||||
fun VisualGroup3D.proxy(
|
||||
templateName: Name,
|
||||
obj: VisualObject3D,
|
||||
name: String = "",
|
||||
block: Proxy.() -> Unit = {}
|
||||
): Proxy {
|
||||
val existing = getPrototype(templateName)
|
||||
if (existing == null) {
|
||||
setPrototype(templateName, obj)
|
||||
} else if (existing != obj) {
|
||||
error("Can't add different prototype on top of existing one")
|
||||
}
|
||||
return ref(templateName, name, block)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -16,7 +16,7 @@ class Sphere(
|
||||
var phi: Float = PI2,
|
||||
var thetaStart: Float = 0f,
|
||||
var theta: Float = PI.toFloat()
|
||||
) : AbstractVisualObject(), VisualObject3D {
|
||||
) : AbstractVisualObject(), VisualObject3D, Shape {
|
||||
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
@ -24,13 +24,26 @@ class Sphere(
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
|
||||
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()
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
inline fun VisualGroup3D.sphere(
|
||||
radius: Number,
|
||||
phi: Number = 2 * PI,
|
||||
theta: Number = PI,
|
||||
name: String? = null,
|
||||
name: String = "",
|
||||
action: Sphere.() -> Unit = {}
|
||||
) = Sphere(
|
||||
radius.toFloat(),
|
||||
|
@ -1,7 +1,7 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -123,6 +123,7 @@ class Tube(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inline fun VisualGroup3D.tube(
|
||||
@ -131,7 +132,7 @@ inline fun VisualGroup3D.tube(
|
||||
innerRadius: Number = 0f,
|
||||
startAngle: Number = 0f,
|
||||
angle: Number = 2 * PI,
|
||||
name: String? = null,
|
||||
name: String = "",
|
||||
block: Tube.() -> Unit = {}
|
||||
): Tube = Tube(
|
||||
r.toFloat(),
|
||||
|
@ -1,14 +1,20 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.context.AbstractPlugin
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.PluginFactory
|
||||
import hep.dataforge.context.PluginTag
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.MetaSerializer
|
||||
import hep.dataforge.io.serialization.NameSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.VisualPlugin
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonConfiguration
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.contextual
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
@ -25,16 +31,26 @@ class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
companion object : PluginFactory<Visual3DPlugin> {
|
||||
override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP)
|
||||
override val type: KClass<out Visual3DPlugin> = Visual3DPlugin::class
|
||||
override fun invoke(meta: Meta): Visual3DPlugin = Visual3DPlugin(meta)
|
||||
override fun invoke(meta: Meta, context: Context): Visual3DPlugin = Visual3DPlugin(meta)
|
||||
|
||||
val serialModule = SerializersModule {
|
||||
polymorphic(VisualObject3D::class) {
|
||||
contextual(Point3DSerializer)
|
||||
contextual(Point2DSerializer)
|
||||
contextual(NameSerializer)
|
||||
contextual(NameTokenSerializer)
|
||||
contextual(MetaSerializer)
|
||||
contextual(ConfigSerializer)
|
||||
|
||||
polymorphic(VisualObject::class, VisualObject3D::class) {
|
||||
VisualGroup3D::class with VisualGroup3D.serializer()
|
||||
Proxy::class with Proxy.serializer()
|
||||
Composite::class with Composite.serializer()
|
||||
Tube::class with Tube.serializer()
|
||||
Box::class with Box.serializer()
|
||||
Convex::class with Convex.serializer()
|
||||
Extruded::class with Extruded.serializer()
|
||||
addSubclass(PolyLine.serializer())
|
||||
addSubclass(Label3D.serializer())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,143 @@
|
||||
@file:UseSerializers(
|
||||
Point3DSerializer::class,
|
||||
ConfigSerializer::class,
|
||||
NameTokenSerializer::class,
|
||||
NameSerializer::class,
|
||||
MetaSerializer::class
|
||||
)
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.MetaSerializer
|
||||
import hep.dataforge.io.serialization.NameSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.vis.common.AbstractVisualGroup
|
||||
import hep.dataforge.vis.common.StyleSheet
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import kotlin.collections.set
|
||||
|
||||
/**
|
||||
* Represents 3-dimensional Visual Group
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("group.3d")
|
||||
class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||
/**
|
||||
* A container for templates visible inside this group
|
||||
*/
|
||||
@SerialName(PROTOTYPES_KEY)
|
||||
var prototypes: VisualGroup3D? = null
|
||||
set(value) {
|
||||
value?.parent = this
|
||||
field = value
|
||||
}
|
||||
|
||||
override var styleSheet: StyleSheet? = null
|
||||
private set
|
||||
|
||||
//FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed
|
||||
override var properties: Config? = null
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
@SerialName("children")
|
||||
private val _children = HashMap<NameToken, VisualObject>()
|
||||
override val children: Map<NameToken, VisualObject> get() = _children
|
||||
|
||||
init {
|
||||
//Do after deserialization
|
||||
attachChildren()
|
||||
}
|
||||
|
||||
/**
|
||||
* Update or create stylesheet
|
||||
*/
|
||||
fun styleSheet(block: StyleSheet.() -> Unit) {
|
||||
val res = this.styleSheet ?: StyleSheet(this).also { this.styleSheet = it }
|
||||
res.block()
|
||||
}
|
||||
|
||||
override fun removeChild(token: NameToken) {
|
||||
_children.remove(token)
|
||||
childrenChanged(token.asName(), null)
|
||||
}
|
||||
|
||||
override fun setChild(token: NameToken, child: VisualObject) {
|
||||
if (child.parent == null) {
|
||||
child.parent = this
|
||||
} else {
|
||||
error("Can't reassign existing parent for $child")
|
||||
}
|
||||
_children[token] = child
|
||||
childrenChanged(token.asName(), child)
|
||||
}
|
||||
|
||||
// /**
|
||||
// * TODO add special static group to hold statics without propagation
|
||||
// */
|
||||
// override fun addStatic(child: VisualObject) = setChild(NameToken("@static(${child.hashCode()})"), child)
|
||||
|
||||
override fun createGroup(name: Name): VisualGroup3D {
|
||||
return when {
|
||||
name.isEmpty() -> error("Should be unreachable")
|
||||
name.length == 1 -> {
|
||||
val token = name.first()!!
|
||||
when (val current = children[token]) {
|
||||
null -> VisualGroup3D().also { setChild(token, it) }
|
||||
is VisualGroup3D -> current
|
||||
else -> error("Can't create group with name $name because it exists and not a group")
|
||||
}
|
||||
}
|
||||
else -> createGroup(name.first()!!.asName()).createGroup(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
override fun attachChildren() {
|
||||
super.attachChildren()
|
||||
prototypes?.run {
|
||||
parent = this
|
||||
attachChildren()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PROTOTYPES_KEY = "templates"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||
*/
|
||||
fun VisualGroup3D.getPrototype(name: Name): VisualObject3D? =
|
||||
prototypes?.get(name) as? VisualObject3D ?: (parent as? VisualGroup3D)?.getPrototype(name)
|
||||
|
||||
/**
|
||||
* Defined a prototype inside current group
|
||||
*/
|
||||
fun VisualGroup3D.setPrototype(name: Name, obj: VisualObject3D) {
|
||||
(prototypes ?: VisualGroup3D().also { this.prototypes = it })[name] = obj
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a group with given [key], attach it to this parent and return it.
|
||||
*/
|
||||
fun VisualGroup3D.group(key: String = "", action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
|
||||
VisualGroup3D().apply(action).also { set(key, it) }
|
||||
|
||||
/**
|
||||
* Create or edit prototype node as a group
|
||||
*/
|
||||
inline fun VisualGroup3D.prototypes(builder: VisualGroup3D.() -> Unit): Unit {
|
||||
(prototypes ?: VisualGroup3D().also { this.prototypes = it }).run(builder)
|
||||
}
|
||||
|
@ -1,22 +1,22 @@
|
||||
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class)
|
||||
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class, NameTokenSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.NameSerializer
|
||||
import hep.dataforge.io.serialization.NameSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.output.Output
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.asName
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.MATERIAL_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.IGNORE_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.LAYER_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
/**
|
||||
* Interface for 3-dimensional [VisualObject]
|
||||
*/
|
||||
interface VisualObject3D : VisualObject {
|
||||
var position: Point3D?
|
||||
var rotation: Point3D?
|
||||
@ -35,9 +35,14 @@ interface VisualObject3D : VisualObject {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val MATERIAL_KEY = "material".asName()
|
||||
|
||||
val VISIBLE_KEY = "visible".asName()
|
||||
val SELECTED_KEY = "selected".asName()
|
||||
val DETAIL_KEY = "detail".asName()
|
||||
val LAYER_KEY = "layer".asName()
|
||||
val IGNORE_KEY = "ignore".asName()
|
||||
|
||||
val GEOMETRY_KEY = "geometry".asName()
|
||||
|
||||
val x = "x".asName()
|
||||
val y = "y".asName()
|
||||
@ -65,44 +70,16 @@ interface VisualObject3D : VisualObject {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class VisualGroup3D : VisualGroup<VisualObject3D>(), VisualObject3D, Configurable {
|
||||
/**
|
||||
* A container for templates visible inside this group
|
||||
*/
|
||||
var templates: VisualGroup3D? = null
|
||||
set(value) {
|
||||
value?.parent = this
|
||||
field = value
|
||||
}
|
||||
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
override val namedChildren: MutableMap<Name, VisualObject3D> = HashMap()
|
||||
override val unnamedChildren: MutableList<VisualObject3D> = ArrayList()
|
||||
|
||||
fun getTemplate(name: Name): VisualObject3D? = templates?.get(name) ?: (parent as? VisualGroup3D)?.getTemplate(name)
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
set(TEMPLATES_KEY, templates?.toMeta())
|
||||
updatePosition()
|
||||
updateChildren()
|
||||
/**
|
||||
* Count number of layers to the top object. Return 1 if this is top layer
|
||||
*/
|
||||
var VisualObject3D.layer: Int
|
||||
get() = getProperty(LAYER_KEY).int ?: 0
|
||||
set(value) {
|
||||
setProperty(LAYER_KEY, value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TEMPLATES_KEY = "templates"
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualGroup3D.group(key: String? = null, action: VisualGroup3D.() -> Unit = {}): VisualGroup3D =
|
||||
VisualGroup3D().apply(action).also { set(key, it) }
|
||||
|
||||
fun Output<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
|
||||
fun Renderer<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
|
||||
render(VisualGroup3D().apply(action), meta)
|
||||
|
||||
// Common properties
|
||||
@ -131,27 +108,21 @@ var VisualObject3D.detail: Int?
|
||||
get() = getProperty(DETAIL_KEY, false).int
|
||||
set(value) = setProperty(DETAIL_KEY, value)
|
||||
|
||||
var VisualObject3D.material: Meta?
|
||||
get() = getProperty(MATERIAL_KEY).node
|
||||
set(value) = setProperty(MATERIAL_KEY, value)
|
||||
|
||||
var VisualObject3D.visible: Boolean?
|
||||
var VisualObject.visible: Boolean?
|
||||
get() = getProperty(VISIBLE_KEY).boolean
|
||||
set(value) = setProperty(VISIBLE_KEY, value)
|
||||
|
||||
fun VisualObject3D.color(rgb: Int) {
|
||||
material = buildMeta { "color" to rgb }
|
||||
}
|
||||
/**
|
||||
* If this property is true, the object will be ignored on render.
|
||||
* Property is not inherited.
|
||||
*/
|
||||
var VisualObject.ignore: Boolean?
|
||||
get() = getProperty(IGNORE_KEY,false).boolean
|
||||
set(value) = setProperty(IGNORE_KEY, value)
|
||||
|
||||
fun VisualObject3D.material(builder: MetaBuilder.() -> Unit) {
|
||||
material = buildMeta(builder)
|
||||
}
|
||||
|
||||
fun VisualObject3D.color(r: Int, g: Int, b: Int) = material {
|
||||
"red" to r
|
||||
"green" to g
|
||||
"blue" to b
|
||||
}
|
||||
//var VisualObject.selected: Boolean?
|
||||
// get() = getProperty(SELECTED_KEY).boolean
|
||||
// set(value) = setProperty(SELECTED_KEY, value)
|
||||
|
||||
private fun VisualObject3D.position(): Point3D =
|
||||
position ?: Point3D(0.0, 0.0, 0.0).also { position = it }
|
||||
@ -160,21 +131,21 @@ var VisualObject3D.x: Number
|
||||
get() = position?.x ?: 0f
|
||||
set(value) {
|
||||
position().x = value.toDouble()
|
||||
propertyChanged(VisualObject3D.xPos)
|
||||
propertyInvalidated(VisualObject3D.xPos)
|
||||
}
|
||||
|
||||
var VisualObject3D.y: Number
|
||||
get() = position?.y ?: 0f
|
||||
set(value) {
|
||||
position().y = value.toDouble()
|
||||
propertyChanged(VisualObject3D.yPos)
|
||||
propertyInvalidated(VisualObject3D.yPos)
|
||||
}
|
||||
|
||||
var VisualObject3D.z: Number
|
||||
get() = position?.z ?: 0f
|
||||
set(value) {
|
||||
position().z = value.toDouble()
|
||||
propertyChanged(VisualObject3D.zPos)
|
||||
propertyInvalidated(VisualObject3D.zPos)
|
||||
}
|
||||
|
||||
private fun VisualObject3D.rotation(): Point3D =
|
||||
@ -184,21 +155,21 @@ var VisualObject3D.rotationX: Number
|
||||
get() = rotation?.x ?: 0f
|
||||
set(value) {
|
||||
rotation().x = value.toDouble()
|
||||
propertyChanged(VisualObject3D.xRotation)
|
||||
propertyInvalidated(VisualObject3D.xRotation)
|
||||
}
|
||||
|
||||
var VisualObject3D.rotationY: Number
|
||||
get() = rotation?.y ?: 0f
|
||||
set(value) {
|
||||
rotation().y = value.toDouble()
|
||||
propertyChanged(VisualObject3D.yRotation)
|
||||
propertyInvalidated(VisualObject3D.yRotation)
|
||||
}
|
||||
|
||||
var VisualObject3D.rotationZ: Number
|
||||
get() = rotation?.z ?: 0f
|
||||
set(value) {
|
||||
rotation().z = value.toDouble()
|
||||
propertyChanged(VisualObject3D.zRotation)
|
||||
propertyInvalidated(VisualObject3D.zRotation)
|
||||
}
|
||||
|
||||
private fun VisualObject3D.scale(): Point3D =
|
||||
@ -208,19 +179,19 @@ var VisualObject3D.scaleX: Number
|
||||
get() = scale?.x ?: 1f
|
||||
set(value) {
|
||||
scale().x = value.toDouble()
|
||||
propertyChanged(VisualObject3D.xScale)
|
||||
propertyInvalidated(VisualObject3D.xScale)
|
||||
}
|
||||
|
||||
var VisualObject3D.scaleY: Number
|
||||
get() = scale?.y ?: 1f
|
||||
set(value) {
|
||||
scale().y = value.toDouble()
|
||||
propertyChanged(VisualObject3D.yScale)
|
||||
propertyInvalidated(VisualObject3D.yScale)
|
||||
}
|
||||
|
||||
var VisualObject3D.scaleZ: Number
|
||||
get() = scale?.z ?: 1f
|
||||
set(value) {
|
||||
scale().z = value.toDouble()
|
||||
propertyChanged(VisualObject3D.zScale)
|
||||
propertyInvalidated(VisualObject3D.zScale)
|
||||
}
|
@ -3,12 +3,8 @@ package hep.dataforge.vis.spatial
|
||||
import kotlin.math.PI
|
||||
|
||||
object World {
|
||||
const val CAMERA_INITIAL_DISTANCE = -500.0
|
||||
const val CAMERA_INITIAL_X_ANGLE = -50.0
|
||||
const val CAMERA_INITIAL_Y_ANGLE = 0.0
|
||||
const val CAMERA_INITIAL_Z_ANGLE = -210.0
|
||||
const val CAMERA_NEAR_CLIP = 0.1
|
||||
const val CAMERA_FAR_CLIP = 10000.0
|
||||
val ZERO = Point3D(0.0, 0.0, 0.0)
|
||||
val ONE = Point3D(1.0, 1.0, 1.0)
|
||||
}
|
||||
|
||||
const val PI2: Float = 2 * PI.toFloat()
|
@ -14,8 +14,8 @@ operator fun Point2D.component1() = x
|
||||
operator fun Point2D.component2() = y
|
||||
|
||||
fun Point2D.toMeta() = buildMeta {
|
||||
VisualObject3D.x to x
|
||||
VisualObject3D.y to y
|
||||
VisualObject3D.x put x
|
||||
VisualObject3D.y put y
|
||||
}
|
||||
|
||||
fun Meta.point2D() = Point2D(this["x"].number ?: 0, this["y"].number ?: 0)
|
||||
@ -26,16 +26,16 @@ expect class Point3D(x: Number, y: Number, z: Number) {
|
||||
var z: Double
|
||||
}
|
||||
|
||||
expect operator fun Point3D.plus(other: Point3D): Point3D
|
||||
|
||||
operator fun Point3D.component1() = x
|
||||
operator fun Point3D.component2() = y
|
||||
operator fun Point3D.component3() = z
|
||||
|
||||
fun Meta.point3D() = Point3D(this["x"].number ?: 0, this["y"].number ?: 0, this["y"].number ?: 0)
|
||||
|
||||
val zero = Point3D(0, 0, 0)
|
||||
|
||||
fun Point3D.toMeta() = buildMeta {
|
||||
VisualObject3D.x to x
|
||||
VisualObject3D.y to y
|
||||
VisualObject3D.z to z
|
||||
VisualObject3D.x put x
|
||||
VisualObject3D.y put y
|
||||
VisualObject3D.z put z
|
||||
}
|
@ -1,41 +1,109 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.serialization.descriptor
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.toName
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.internal.DoubleSerializer
|
||||
import kotlinx.serialization.internal.StringDescriptor
|
||||
import kotlinx.serialization.internal.nullable
|
||||
|
||||
@Serializable
|
||||
private data class Point2DSerial(val x: Double, val y: Double)
|
||||
inline fun <R> Decoder.decodeStructure(
|
||||
desc: SerialDescriptor,
|
||||
vararg typeParams: KSerializer<*> = emptyArray(),
|
||||
crossinline block: CompositeDecoder.() -> R
|
||||
): R {
|
||||
val decoder = beginStructure(desc, *typeParams)
|
||||
val res = decoder.block()
|
||||
decoder.endStructure(desc)
|
||||
return res
|
||||
}
|
||||
|
||||
@Serializable
|
||||
private data class Point3DSerial(val x: Double, val y: Double, val z: Double)
|
||||
inline fun Encoder.encodeStructure(
|
||||
desc: SerialDescriptor,
|
||||
vararg typeParams: KSerializer<*> = emptyArray(),
|
||||
block: CompositeEncoder.() -> Unit
|
||||
) {
|
||||
val encoder = beginStructure(desc, *typeParams)
|
||||
encoder.block()
|
||||
encoder.endStructure(desc)
|
||||
}
|
||||
|
||||
@Serializer(Point3D::class)
|
||||
object Point3DSerializer : KSerializer<Point3D> {
|
||||
private val serializer = Point3DSerial.serializer()
|
||||
override val descriptor: SerialDescriptor get() = serializer.descriptor
|
||||
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point3D") {
|
||||
double("x", true)
|
||||
double("y", true)
|
||||
double("z", true)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Point3D {
|
||||
return serializer.deserialize(decoder).let {
|
||||
Point3D(it.x, it.y, it.z)
|
||||
var x: Double? = null
|
||||
var y: Double? = null
|
||||
var z: Double? = null
|
||||
decoder.decodeStructure(descriptor) {
|
||||
loop@ while (true) {
|
||||
when (val i = decodeElementIndex(descriptor)) {
|
||||
CompositeDecoder.READ_DONE -> break@loop
|
||||
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
2 -> z = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
else -> throw SerializationException("Unknown index $i")
|
||||
}
|
||||
}
|
||||
}
|
||||
return Point3D(x?:0.0, y?:0.0, z?:0.0)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, obj: Point3D) {
|
||||
serializer.serialize(encoder, Point3DSerial(obj.x, obj.y, obj.z) )
|
||||
encoder.encodeStructure(descriptor) {
|
||||
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
|
||||
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
|
||||
if (obj.z != 0.0) encodeDoubleElement(descriptor, 2, obj.z)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializer(Point2D::class)
|
||||
object Point2DSerializer : KSerializer<Point2D> {
|
||||
private val serializer = Point2DSerial.serializer()
|
||||
override val descriptor: SerialDescriptor get() = serializer.descriptor
|
||||
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point2D") {
|
||||
double("x", true)
|
||||
double("y", true)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Point2D {
|
||||
return serializer.deserialize(decoder).let {
|
||||
Point2D(it.x, it.y)
|
||||
var x: Double? = null
|
||||
var y: Double? = null
|
||||
decoder.decodeStructure(descriptor) {
|
||||
loop@ while (true) {
|
||||
when (val i = decodeElementIndex(descriptor)) {
|
||||
CompositeDecoder.READ_DONE -> break@loop
|
||||
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
|
||||
else -> throw SerializationException("Unknown index $i")
|
||||
}
|
||||
}
|
||||
}
|
||||
return Point2D(x?:0.0, y?:0.0)
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, obj: Point2D) {
|
||||
serializer.serialize(encoder, Point2DSerial(obj.x, obj.y))
|
||||
encoder.encodeStructure(descriptor) {
|
||||
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
|
||||
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializer(NameToken::class)
|
||||
object NameTokenSerializer : KSerializer<NameToken> {
|
||||
override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken")
|
||||
|
||||
override fun deserialize(decoder: Decoder): NameToken {
|
||||
return decoder.decodeString().toName().first()!!
|
||||
}
|
||||
|
||||
override fun serialize(encoder: Encoder, obj: NameToken) {
|
||||
encoder.encodeString(obj.toString())
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
|
||||
class AxesSpec(override val config: Config) : Specific {
|
||||
var visible by boolean(!config.isEmpty())
|
||||
var size by double(AXIS_SIZE)
|
||||
var width by double(AXIS_WIDTH)
|
||||
|
||||
companion object : Specification<AxesSpec> {
|
||||
override fun wrap(config: Config): AxesSpec = AxesSpec(config)
|
||||
|
||||
const val AXIS_SIZE = 1000.0
|
||||
const val AXIS_WIDTH = 3.0
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import kotlin.math.PI
|
||||
|
||||
class CameraSpec(override val config: Config) : Specific {
|
||||
var fov by int(FIELD_OF_VIEW)
|
||||
//var aspect by double(1.0)
|
||||
var nearClip by double(NEAR_CLIP)
|
||||
var farClip by double(FAR_CLIP)
|
||||
|
||||
var distance by double(INITIAL_DISTANCE)
|
||||
var azimuth by double(INITIAL_AZIMUTH)
|
||||
var latitude by double(INITIAL_LATITUDE)
|
||||
val zenith: Double get() = PI / 2 - latitude
|
||||
|
||||
companion object : Specification<CameraSpec> {
|
||||
override fun wrap(config: Config): CameraSpec = CameraSpec(config)
|
||||
const val INITIAL_DISTANCE = 300.0
|
||||
const val INITIAL_AZIMUTH = 0.0
|
||||
const val INITIAL_LATITUDE = PI/6
|
||||
const val NEAR_CLIP = 0.1
|
||||
const val FAR_CLIP = 10000.0
|
||||
const val FIELD_OF_VIEW = 75
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
|
||||
class CanvasSpec(override val config: Config) : Specific {
|
||||
var axes by spec(AxesSpec)
|
||||
var camera by spec(CameraSpec)
|
||||
var controls by spec(ControlsSpec)
|
||||
var minSize by int(300)
|
||||
|
||||
companion object: Specification<CanvasSpec>{
|
||||
override fun wrap(config: Config): CanvasSpec = CanvasSpec(config)
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.Specific
|
||||
import hep.dataforge.meta.Specification
|
||||
|
||||
class ControlsSpec(override val config: Config) : Specific {
|
||||
companion object : Specification<ControlsSpec> {
|
||||
override fun wrap(config: Config): ControlsSpec = ControlsSpec(config)
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package hep.dataforge.vis.spatial.transform
|
||||
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vis.common.MutableVisualGroup
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.*
|
||||
|
||||
internal fun mergeChild(parent: VisualGroup, child: VisualObject): VisualObject {
|
||||
return child.apply {
|
||||
|
||||
config.update(parent.config)
|
||||
|
||||
//parent.properties?.let { config.update(it) }
|
||||
|
||||
if (this is VisualObject3D && parent is VisualObject3D) {
|
||||
position = (position ?: World.ZERO) + (parent.position ?: World.ZERO)
|
||||
rotation = (parent.rotation ?: World.ZERO) + (parent.rotation ?: World.ZERO)
|
||||
scale = when {
|
||||
scale == null && parent.scale == null -> null
|
||||
scale == null -> parent.scale
|
||||
parent.scale == null -> scale
|
||||
else -> Point3D(
|
||||
scale!!.x * parent.scale!!.x,
|
||||
scale!!.y * parent.scale!!.y,
|
||||
scale!!.z * parent.scale!!.z
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
|
||||
|
||||
override fun VisualGroup3D.transformInPlace() {
|
||||
fun MutableVisualGroup.replaceChildren() {
|
||||
children.forEach { (childName, parent) ->
|
||||
if (parent is Proxy) return@forEach //ignore refs
|
||||
if (parent is MutableVisualGroup) {
|
||||
parent.replaceChildren()
|
||||
}
|
||||
if (parent is VisualGroup && parent.children.size == 1) {
|
||||
val child = parent.children.values.first()
|
||||
val newParent = mergeChild(parent, child)
|
||||
newParent.parent = null
|
||||
set(childName.asName(), newParent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
replaceChildren()
|
||||
prototypes?.replaceChildren()
|
||||
}
|
||||
|
||||
override fun VisualGroup3D.clone(): VisualGroup3D {
|
||||
TODO()
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package hep.dataforge.vis.spatial.transform
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vis.common.MutableVisualGroup
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.spatial.Proxy
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
|
||||
object UnRef : VisualTreeTransform<VisualGroup3D>() {
|
||||
private fun VisualGroup.countRefs(): Map<Name, Int> {
|
||||
return children.values.fold(HashMap()) { reducer, obj ->
|
||||
if (obj is VisualGroup) {
|
||||
val counter = obj.countRefs()
|
||||
counter.forEach { (key, value) ->
|
||||
reducer[key] = (reducer[key] ?: 0) + value
|
||||
}
|
||||
} else if (obj is Proxy) {
|
||||
reducer[obj.templateName] = (reducer[obj.templateName] ?: 0) + 1
|
||||
}
|
||||
|
||||
return reducer
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableVisualGroup.unref(name: Name) {
|
||||
(this as? VisualGroup3D)?.prototypes?.set(name, null)
|
||||
children.filter { (it.value as? Proxy)?.templateName == name }.forEach { (key, value) ->
|
||||
val proxy = value as Proxy
|
||||
val newChild = mergeChild(proxy, proxy.prototype)
|
||||
newChild.parent = null
|
||||
set(key.asName(), newChild) // replace proxy with merged object
|
||||
}
|
||||
|
||||
children.values.filterIsInstance<MutableVisualGroup>().forEach { it.unref(name) }
|
||||
}
|
||||
|
||||
override fun VisualGroup3D.transformInPlace() {
|
||||
val counts = countRefs()
|
||||
counts.filter { it.value <= 1 }.forEach {
|
||||
this.unref(it.key)
|
||||
}
|
||||
}
|
||||
|
||||
override fun VisualGroup3D.clone(): VisualGroup3D {
|
||||
TODO()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package hep.dataforge.vis.spatial.transform
|
||||
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
|
||||
/**
|
||||
* A root class for [VisualObject] tree optimization
|
||||
*/
|
||||
abstract class VisualTreeTransform<T : VisualObject> {
|
||||
protected abstract fun T.transformInPlace()
|
||||
protected abstract fun T.clone(): T
|
||||
|
||||
operator fun invoke(source: T, inPlace: Boolean = true): T {
|
||||
val newSource = if (inPlace) {
|
||||
source
|
||||
} else {
|
||||
source.clone()
|
||||
}
|
||||
newSource.transformInPlace()
|
||||
|
||||
return newSource
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : VisualObject> T.transform(vararg transform: VisualTreeTransform<T>): T {
|
||||
var res = this
|
||||
transform.forEach {
|
||||
res = it(res)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
fun <T : VisualObject> T.transformInPlace(vararg transform: VisualTreeTransform<in T>) {
|
||||
transform.forEach { it(this) }
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.getAll
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.io.toMeta
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.getIndexed
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ConvexTest {
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
@Test
|
||||
fun testConvexBuilder() {
|
||||
val group = VisualGroup3D().apply {
|
||||
@ -25,12 +25,11 @@ class ConvexTest {
|
||||
|
||||
val convex = group.first() as Convex
|
||||
|
||||
val meta = convex.toMeta()
|
||||
val json = Visual3DPlugin.json.toJson(Convex.serializer(), convex)
|
||||
val meta = json.toMeta()
|
||||
|
||||
val pointsNode = convex.toMeta()["points"].node
|
||||
|
||||
assertEquals(8, pointsNode?.items?.count())
|
||||
val points = pointsNode?.getAll("point".toName())
|
||||
val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D()}
|
||||
assertEquals(8, points.count())
|
||||
|
||||
assertEquals(8, convex.points.size)
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.common.get
|
||||
import kotlin.math.PI
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GroupTest {
|
||||
@Test
|
||||
fun testGroupWithComposite(){
|
||||
val group = VisualGroup3D().apply{
|
||||
union {
|
||||
fun testGroupWithComposite() {
|
||||
val group = VisualGroup3D().apply {
|
||||
union("union") {
|
||||
box(100, 100, 100) {
|
||||
z = 100
|
||||
rotationX = PI / 4
|
||||
@ -17,11 +18,11 @@ class GroupTest {
|
||||
}
|
||||
box(100, 100, 100)
|
||||
material {
|
||||
"color" to Colors.lightgreen
|
||||
"opacity" to 0.3
|
||||
color(Colors.lightgreen)
|
||||
opacity = 0.3f
|
||||
}
|
||||
}
|
||||
intersect{
|
||||
intersect("intersect") {
|
||||
box(100, 100, 100) {
|
||||
z = 100
|
||||
rotationX = PI / 4
|
||||
@ -31,7 +32,7 @@ class GroupTest {
|
||||
y = 300
|
||||
color(Colors.red)
|
||||
}
|
||||
subtract{
|
||||
subtract("subtract") {
|
||||
box(100, 100, 100) {
|
||||
z = 100
|
||||
rotationX = PI / 4
|
||||
@ -44,7 +45,7 @@ class GroupTest {
|
||||
}
|
||||
|
||||
assertEquals(3, group.count())
|
||||
assertEquals(300.0,group.toList()[1].y.toDouble())
|
||||
assertEquals(-300.0,group.toList()[2].y.toDouble())
|
||||
assertEquals(300.0, (group["intersect"] as VisualObject3D).y.toDouble())
|
||||
assertEquals(-300.0, (group["subtract"] as VisualObject3D).y.toDouble())
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vis.common.useStyle
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class PropertyTest {
|
||||
@Test
|
||||
fun testInheritedProperty() {
|
||||
var box: Box? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
config["test"] = 22
|
||||
group {
|
||||
box = box(100, 100, 100)
|
||||
}
|
||||
}
|
||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStyleProperty() {
|
||||
var box: Box? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
"test" put 22
|
||||
}
|
||||
}
|
||||
group {
|
||||
box = box(100, 100, 100).apply {
|
||||
useStyle("testStyle")
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testColor() {
|
||||
var box: Box? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
Material3D.MATERIAL_COLOR_KEY put "#555555"
|
||||
}
|
||||
}
|
||||
group {
|
||||
box = box(100, 100, 100) {
|
||||
useStyle("testStyle")
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals("#555555", box?.color)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProxyStyleProperty() {
|
||||
var box: Proxy? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
Material3D.MATERIAL_COLOR_KEY put "#555555"
|
||||
}
|
||||
}
|
||||
prototypes {
|
||||
box(100, 100, 100, name = "box") {
|
||||
styles = listOf("testStyle")
|
||||
}
|
||||
}
|
||||
group {
|
||||
box = ref("box".asName())
|
||||
}
|
||||
}
|
||||
assertEquals("#555555", box?.color)
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin.Companion.json
|
||||
import kotlinx.serialization.ImplicitReflectionSerializer
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
@ -8,13 +8,15 @@ import kotlin.test.assertEquals
|
||||
class SerializationTest {
|
||||
@ImplicitReflectionSerializer
|
||||
@Test
|
||||
fun testCubeSerialization(){
|
||||
val cube = Box(100f,100f,100f).apply{
|
||||
fun testCubeSerialization() {
|
||||
val cube = Box(100f, 100f, 100f).apply {
|
||||
color(222)
|
||||
x = 100
|
||||
z = -100
|
||||
}
|
||||
val meta = cube.toMeta()
|
||||
println(meta)
|
||||
val newCube = Box(Global,null, meta)
|
||||
assertEquals(cube,newCube)
|
||||
val string = json.stringify(Box.serializer(), cube)
|
||||
println(string)
|
||||
val newCube = json.parse(Box.serializer(), string)
|
||||
assertEquals(cube.config, newCube.config)
|
||||
}
|
||||
}
|
@ -1,8 +1,14 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
|
||||
import info.laht.threekt.math.Vector2
|
||||
import info.laht.threekt.math.Vector3
|
||||
import info.laht.threekt.math.plus
|
||||
|
||||
actual typealias Point2D = Vector2
|
||||
|
||||
actual typealias Point3D = Vector3
|
||||
actual typealias Point3D = Vector3
|
||||
|
||||
actual operator fun Point3D.plus(other: Point3D): Point3D {
|
||||
return this.plus(other)
|
||||
}
|
@ -1,63 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import info.laht.threekt.materials.Material
|
||||
import info.laht.threekt.materials.MeshBasicMaterial
|
||||
import info.laht.threekt.materials.MeshPhongMaterial
|
||||
import info.laht.threekt.math.Color
|
||||
|
||||
|
||||
object Materials {
|
||||
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
||||
val DEFAULT = MeshPhongMaterial().apply {
|
||||
this.color.set(DEFAULT_COLOR)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(int)
|
||||
}
|
||||
is MetaItem.NodeItem -> {
|
||||
Color(
|
||||
node["red"]?.int ?: 0,
|
||||
node["green"]?.int ?: 0,
|
||||
node["blue"]?.int ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val materialCache = HashMap<Meta, Material>()
|
||||
|
||||
/**
|
||||
* Infer Three material based on meta item
|
||||
*/
|
||||
fun Meta?.jsMaterial(): Material {
|
||||
return if (this == null) {
|
||||
Materials.DEFAULT
|
||||
} else
|
||||
//TODO add more options for material
|
||||
return materialCache.getOrPut(this) {
|
||||
MeshBasicMaterial().apply {
|
||||
color = get("color")?.color() ?: Materials.DEFAULT_COLOR
|
||||
opacity = get("opacity")?.double ?: 1.0
|
||||
transparent = get("transparent").boolean ?: (opacity < 1.0)
|
||||
//node["specularColor"]?.let { specular = it.color() }
|
||||
side = 2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,119 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.meta.boolean
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.startsWith
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.layer
|
||||
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.geometries.EdgesGeometry
|
||||
import info.laht.threekt.geometries.WireframeGeometry
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Basic geometry-based factory
|
||||
*/
|
||||
abstract class MeshThreeFactory<in T : VisualObject3D>(
|
||||
override val type: KClass<in T>
|
||||
) : ThreeFactory<T> {
|
||||
/**
|
||||
* Build a geometry for an object
|
||||
*/
|
||||
abstract fun buildGeometry(obj: T): BufferGeometry
|
||||
|
||||
override fun invoke(obj: T): Mesh {
|
||||
val geometry = buildGeometry(obj)
|
||||
|
||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
||||
|
||||
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
|
||||
|
||||
val mesh = Mesh(geometry, getMaterial(obj)).apply {
|
||||
matrixAutoUpdate = false
|
||||
applyEdges(obj)
|
||||
applyWireFrame(obj)
|
||||
|
||||
//set position for mesh
|
||||
updatePosition(obj)
|
||||
|
||||
layers.enable(obj.layer)
|
||||
children.forEach {
|
||||
it.layers.enable(obj.layer)
|
||||
}
|
||||
}
|
||||
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
when {
|
||||
name.startsWith(VisualObject3D.GEOMETRY_KEY) -> {
|
||||
val oldGeometry = mesh.geometry as BufferGeometry
|
||||
val newGeometry = buildGeometry(obj)
|
||||
oldGeometry.attributes = newGeometry.attributes
|
||||
mesh.applyWireFrame(obj)
|
||||
mesh.applyEdges(obj)
|
||||
newGeometry.dispose()
|
||||
}
|
||||
name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
|
||||
name.startsWith(EDGES_KEY) -> mesh.applyEdges(obj)
|
||||
else -> mesh.updateProperty(obj, name)
|
||||
}
|
||||
}
|
||||
return mesh
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EDGES_KEY = "edges".asName()
|
||||
val WIREFRAME_KEY = "wireframe".asName()
|
||||
val ENABLED_KEY = "enabled".asName()
|
||||
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
|
||||
val EDGES_MATERIAL_KEY = EDGES_KEY + Material3D.MATERIAL_KEY
|
||||
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
|
||||
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + Material3D.MATERIAL_KEY
|
||||
}
|
||||
}
|
||||
|
||||
fun Mesh.applyEdges(obj: VisualObject3D) {
|
||||
children.find { it.name == "edges" }?.let {
|
||||
remove(it)
|
||||
(it as LineSegments).dispose()
|
||||
}
|
||||
//inherited edges definition, enabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
|
||||
|
||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
|
||||
add(
|
||||
LineSegments(
|
||||
EdgesGeometry(geometry as BufferGeometry),
|
||||
material
|
||||
).apply {
|
||||
name = "edges"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Mesh.applyWireFrame(obj: VisualObject3D) {
|
||||
children.find { it.name == "wireframe" }?.let {
|
||||
remove(it)
|
||||
(it as LineSegments).dispose()
|
||||
}
|
||||
//inherited wireframe definition, disabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node)
|
||||
add(
|
||||
LineSegments(
|
||||
WireframeGeometry(geometry as BufferGeometry),
|
||||
material
|
||||
).apply {
|
||||
name = "wireframe"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.specifications.CameraSpec
|
||||
import hep.dataforge.vis.spatial.specifications.CanvasSpec
|
||||
import hep.dataforge.vis.spatial.specifications.ControlsSpec
|
||||
import hep.dataforge.vis.spatial.three.ThreeMaterials.HIGHLIGHT_MATERIAL
|
||||
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.core.Raycaster
|
||||
import info.laht.threekt.external.controls.OrbitControls
|
||||
import info.laht.threekt.external.controls.TrackballControls
|
||||
import info.laht.threekt.geometries.EdgesGeometry
|
||||
import info.laht.threekt.helpers.AxesHelper
|
||||
import info.laht.threekt.math.Vector2
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import info.laht.threekt.scenes.Scene
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.events.MouseEvent
|
||||
import kotlin.browser.window
|
||||
import kotlin.dom.clear
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sin
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class ThreeCanvas(element: HTMLElement, val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> {
|
||||
|
||||
override val context: Context get() = three.context
|
||||
|
||||
var content: VisualObject3D? = null
|
||||
private set
|
||||
|
||||
private var root: Object3D? = null
|
||||
|
||||
private val raycaster = Raycaster()
|
||||
private val mousePosition: Vector2 = Vector2()
|
||||
|
||||
var clickListener: ((Name) -> Unit)? = null
|
||||
|
||||
val axes = AxesHelper(spec.axes.size.toInt()).apply {
|
||||
visible = spec.axes.visible
|
||||
}
|
||||
|
||||
val scene: Scene = Scene().apply {
|
||||
add(axes)
|
||||
}
|
||||
|
||||
val camera = buildCamera(spec.camera)
|
||||
|
||||
init {
|
||||
element.clear()
|
||||
|
||||
//Attach listener to track mouse changes
|
||||
element.addEventListener("mousemove", { event ->
|
||||
(event as? MouseEvent)?.run {
|
||||
val rect = element.getBoundingClientRect()
|
||||
mousePosition.x = ((event.clientX - rect.left) / element.clientWidth) * 2 - 1
|
||||
mousePosition.y = -((event.clientY - rect.top) / element.clientHeight) * 2 + 1
|
||||
}
|
||||
}, false)
|
||||
|
||||
element.addEventListener("mousedown", { event ->
|
||||
val mesh = pick()
|
||||
if (mesh != null) {
|
||||
val name = mesh.fullName()
|
||||
clickListener?.invoke(name)
|
||||
}
|
||||
}, false)
|
||||
|
||||
camera.aspect = 1.0
|
||||
|
||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||
setClearColor(Colors.skyblue, 1)
|
||||
|
||||
}
|
||||
|
||||
addControls(renderer.domElement, spec.controls)
|
||||
|
||||
fun animate() {
|
||||
val mesh = pick()
|
||||
|
||||
if (mesh != null && highlighted != mesh) {
|
||||
highlighted?.toggleHighlight(false)
|
||||
mesh.toggleHighlight(true)
|
||||
}
|
||||
|
||||
window.requestAnimationFrame {
|
||||
animate()
|
||||
}
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
|
||||
element.appendChild(renderer.domElement)
|
||||
|
||||
renderer.setSize(max(spec.minSize, element.offsetWidth), max(spec.minSize, element.offsetWidth))
|
||||
|
||||
element.onresize = {
|
||||
renderer.setSize(element.offsetWidth, element.offsetWidth)
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
animate()
|
||||
}
|
||||
|
||||
private fun pick(): Mesh? {
|
||||
// update the picking ray with the camera and mouse position
|
||||
raycaster.setFromCamera(mousePosition, camera)
|
||||
|
||||
// calculate objects intersecting the picking ray
|
||||
return root?.let { root ->
|
||||
val intersects = raycaster.intersectObject(root, true)
|
||||
val intersect = intersects.firstOrNull()
|
||||
intersect?.`object` as? Mesh
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve full name of the object relative to the global root
|
||||
*/
|
||||
private fun Object3D.fullName(): Name {
|
||||
if (root == null) error("Can't resolve element name without the root")
|
||||
return if (parent == root) {
|
||||
name.toName()
|
||||
} else {
|
||||
(parent?.fullName() ?: Name.EMPTY) + name.toName()
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildCamera(spec: CameraSpec) = PerspectiveCamera(
|
||||
spec.fov,
|
||||
1.0,
|
||||
spec.nearClip,
|
||||
spec.farClip
|
||||
).apply {
|
||||
translateX(spec.distance * sin(spec.zenith) * sin(spec.azimuth))
|
||||
translateY(spec.distance * cos(spec.zenith))
|
||||
translateZ(spec.distance * sin(spec.zenith) * cos(spec.azimuth))
|
||||
}
|
||||
|
||||
private fun addControls(element: Node, controlsSpec: ControlsSpec) {
|
||||
when (controlsSpec["type"].string) {
|
||||
"trackball" -> TrackballControls(camera, element)
|
||||
else -> OrbitControls(camera, element)
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||
//clear old root
|
||||
scene.children.find { it.name == "@root" }?.let {
|
||||
scene.remove(it)
|
||||
}
|
||||
|
||||
val object3D = three.buildObject3D(obj)
|
||||
object3D.name = "@root"
|
||||
scene.add(object3D)
|
||||
content = obj
|
||||
root = object3D
|
||||
}
|
||||
|
||||
private var highlighted: Mesh? = null
|
||||
|
||||
/**
|
||||
* Toggle highlight for the given [Mesh] object
|
||||
*/
|
||||
private fun Mesh.toggleHighlight(highlight: Boolean) {
|
||||
if (highlight) {
|
||||
val edges = LineSegments(
|
||||
EdgesGeometry(geometry as BufferGeometry),
|
||||
HIGHLIGHT_MATERIAL
|
||||
).apply {
|
||||
name = "@highlight"
|
||||
}
|
||||
add(edges)
|
||||
highlighted = this
|
||||
} else {
|
||||
val highlightEdges = children.find { it.name == "@highlight" }
|
||||
highlightEdges?.let { remove(it) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle highlight for element with given name
|
||||
*/
|
||||
fun highlight(name: Name?) {
|
||||
if (name == null) {
|
||||
highlighted?.toggleHighlight(false)
|
||||
highlighted = null
|
||||
return
|
||||
}
|
||||
val mesh = root?.findChild(name) as? Mesh
|
||||
if (mesh != null && highlighted != mesh) {
|
||||
highlighted?.toggleHighlight(false)
|
||||
mesh.toggleHighlight(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ThreePlugin.output(element: HTMLElement, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas =
|
||||
ThreeCanvas(element, this, spec)
|
@ -0,0 +1,56 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.vis.spatial.Label3D
|
||||
import hep.dataforge.vis.spatial.color
|
||||
import info.laht.threekt.DoubleSide
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.PlaneBufferGeometry
|
||||
import info.laht.threekt.materials.MeshBasicMaterial
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import info.laht.threekt.textures.Texture
|
||||
import org.w3c.dom.CanvasRenderingContext2D
|
||||
import org.w3c.dom.CanvasTextBaseline
|
||||
import org.w3c.dom.HTMLCanvasElement
|
||||
import org.w3c.dom.MIDDLE
|
||||
import kotlin.browser.document
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
|
||||
*/
|
||||
object ThreeCanvasLabelFactory: ThreeFactory<Label3D> {
|
||||
override val type: KClass<in Label3D> get() = Label3D::class
|
||||
|
||||
override fun invoke(obj: Label3D): Object3D {
|
||||
val canvas = document.createElement("canvas") as HTMLCanvasElement
|
||||
val context = canvas.getContext("2d") as CanvasRenderingContext2D
|
||||
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
|
||||
context.fillStyle = obj.color ?: "black"
|
||||
context.textBaseline = CanvasTextBaseline.MIDDLE
|
||||
val metrics = context.measureText(obj.text)
|
||||
//canvas.width = metrics.width.toInt()
|
||||
|
||||
|
||||
context.fillText(obj.text, (canvas.width - metrics.width)/2, 0.5*canvas.height)
|
||||
|
||||
|
||||
// canvas contents will be used for a texture
|
||||
val texture = Texture(canvas)
|
||||
texture.needsUpdate = true
|
||||
|
||||
val material = MeshBasicMaterial().apply {
|
||||
map = texture
|
||||
side = DoubleSide
|
||||
transparent = true
|
||||
}
|
||||
|
||||
val mesh = Mesh(
|
||||
PlaneBufferGeometry(canvas.width, canvas.height),
|
||||
material
|
||||
)
|
||||
|
||||
mesh.updatePosition(obj)
|
||||
|
||||
return mesh
|
||||
}
|
||||
}
|
@ -6,7 +6,7 @@ import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.objects.Mesh
|
||||
|
||||
/**
|
||||
* This should be inner, becaulse it uses object builder
|
||||
* This should be inner, because it uses object builder
|
||||
*/
|
||||
class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) {
|
||||
|
||||
|
@ -1,19 +1,15 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.meta.boolean
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.startsWith
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.vis.common.asName
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
||||
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
|
||||
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.EdgesGeometry
|
||||
import info.laht.threekt.geometries.WireframeGeometry
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@ -21,9 +17,9 @@ import kotlin.reflect.KClass
|
||||
* Builder and updater for three.js object
|
||||
*/
|
||||
@Type(TYPE)
|
||||
interface ThreeFactory<T : VisualObject3D> {
|
||||
interface ThreeFactory<in T : VisualObject> {
|
||||
|
||||
val type: KClass<out T>
|
||||
val type: KClass<in T>
|
||||
|
||||
operator fun invoke(obj: T): Object3D
|
||||
|
||||
@ -35,112 +31,41 @@ interface ThreeFactory<T : VisualObject3D> {
|
||||
/**
|
||||
* Update position, rotation and visibility
|
||||
*/
|
||||
internal fun Object3D.updatePosition(obj: VisualObject3D) {
|
||||
fun Object3D.updatePosition(obj: VisualObject3D) {
|
||||
visible = obj.visible ?: true
|
||||
position.set(obj.x, obj.y, obj.z)
|
||||
// obj.rotation?.let{
|
||||
// rotateZ(it.z)
|
||||
// rotateX(it.x)
|
||||
// rotateY(it.y)
|
||||
// }
|
||||
setRotationFromEuler(obj.euler)
|
||||
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
|
||||
updateMatrix()
|
||||
}
|
||||
|
||||
internal fun <T : VisualObject3D> Mesh.updateFrom(obj: T) {
|
||||
matrixAutoUpdate = false
|
||||
|
||||
//inherited edges definition, enabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
|
||||
val material = obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
|
||||
add(LineSegments(EdgesGeometry(geometry as BufferGeometry), material))
|
||||
}
|
||||
|
||||
//inherited wireframe definition, disabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||
val material = obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
|
||||
add(LineSegments(WireframeGeometry(geometry as BufferGeometry), material))
|
||||
}
|
||||
|
||||
//set position for mesh
|
||||
updatePosition(obj)
|
||||
|
||||
obj.getProperty(MeshThreeFactory.LAYER_KEY).int?.let {
|
||||
layers.set(it)
|
||||
}
|
||||
}
|
||||
///**
|
||||
// * Unsafe invocation of a factory
|
||||
// */
|
||||
//operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
|
||||
// if (type.isInstance(obj)) {
|
||||
// @Suppress("UNCHECKED_CAST")
|
||||
// return invoke(obj as T)
|
||||
// } else {
|
||||
// error("The object of type ${obj::class} could not be rendered by this factory")
|
||||
// }
|
||||
//}
|
||||
|
||||
/**
|
||||
* Unsafe invocation of a factory
|
||||
* Update non-position non-geometry property
|
||||
*/
|
||||
operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
|
||||
if (type.isInstance(obj)) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return invoke(obj as T)
|
||||
} else {
|
||||
error("The object of type ${obj::class} could not be rendered by this factory")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Basic geometry-based factory
|
||||
*/
|
||||
abstract class MeshThreeFactory<T : VisualObject3D>(override val type: KClass<out T>) : ThreeFactory<T> {
|
||||
/**
|
||||
* Build a geometry for an object
|
||||
*/
|
||||
abstract fun buildGeometry(obj: T): BufferGeometry
|
||||
|
||||
|
||||
override fun invoke(obj: T): Mesh {
|
||||
//create mesh from geometry
|
||||
return buildMesh<T>(obj) { buildGeometry(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EDGES_KEY = "edges".asName()
|
||||
val WIREFRAME_KEY = "wireframe".asName()
|
||||
val ENABLED_KEY = "enabled".asName()
|
||||
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
|
||||
val EDGES_MATERIAL_KEY = EDGES_KEY + VisualObject3D.MATERIAL_KEY
|
||||
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
|
||||
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + VisualObject3D.MATERIAL_KEY
|
||||
val LAYER_KEY = "layer".asName()
|
||||
|
||||
fun <T : VisualObject3D> buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh {
|
||||
//TODO add caching for geometries using templates
|
||||
val geometry = geometryBuilder(obj)
|
||||
|
||||
//JS sometimes tries to pass Geometry as BufferGeometry
|
||||
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
|
||||
|
||||
val mesh = Mesh(geometry, obj.material.jsMaterial())
|
||||
|
||||
mesh.updateFrom(obj)
|
||||
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
if (name.startsWith(VisualObject3D.MATERIAL_KEY)) {
|
||||
//updated material
|
||||
mesh.material = obj.material.jsMaterial()
|
||||
} else if (
|
||||
name.startsWith(VisualObject3D.position) ||
|
||||
name.startsWith(VisualObject3D.rotation) ||
|
||||
name.startsWith(VisualObject3D.scale)
|
||||
) {
|
||||
//update position of mesh using this object
|
||||
mesh.updatePosition(obj)
|
||||
} else if (name == VisualObject3D.VISIBLE_KEY) {
|
||||
mesh.visible = obj.visible ?: true
|
||||
} else {
|
||||
//full update
|
||||
mesh.geometry = geometryBuilder(obj)
|
||||
mesh.material = obj.material.jsMaterial()
|
||||
}
|
||||
}
|
||||
return mesh
|
||||
}
|
||||
fun Object3D.updateProperty(source: VisualObject3D, propertyName: Name) {
|
||||
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||
this.material = getMaterial(source)
|
||||
} else if (
|
||||
propertyName.startsWith(VisualObject3D.position)
|
||||
|| propertyName.startsWith(VisualObject3D.rotation)
|
||||
|| propertyName.startsWith(VisualObject3D.scale)
|
||||
) {
|
||||
//update position of mesh using this object
|
||||
updatePosition(source)
|
||||
} else if (propertyName == VisualObject3D.VISIBLE_KEY) {
|
||||
visible = source.visible ?: true
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,9 @@ import info.laht.threekt.core.Face3
|
||||
import info.laht.threekt.core.Geometry
|
||||
import info.laht.threekt.math.Vector3
|
||||
|
||||
/**
|
||||
* An implementation of geometry builder for Three.js [BufferGeometry]
|
||||
*/
|
||||
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
||||
|
||||
private val vertices = ArrayList<Point3D>()
|
||||
@ -31,7 +34,7 @@ class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
|
||||
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
|
||||
val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal ?: Vector3(0, 0, 0))
|
||||
meta["materialIndex"].int?.let { face.materialIndex = it }
|
||||
meta["color"]?.color()?.let { face.color = it }
|
||||
meta["color"]?.getColor()?.let { face.color = it }
|
||||
faces.add(face)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,33 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.js.jsObject
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.vis.spatial.Label3D
|
||||
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.geometries.TextBufferGeometry
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
object ThreeLabelFactory : ThreeFactory<Label3D> {
|
||||
override val type: KClass<in Label3D> get() = Label3D::class
|
||||
|
||||
override fun invoke(obj: Label3D): Object3D {
|
||||
val textGeo = TextBufferGeometry( obj.text, jsObject {
|
||||
font = obj.fontFamily
|
||||
size = 20
|
||||
height = 1
|
||||
curveSegments = 1
|
||||
} )
|
||||
return Mesh(textGeo, getMaterial(obj)).apply {
|
||||
updatePosition(obj)
|
||||
obj.onPropertyChange(this@ThreeLabelFactory){name: Name, before: MetaItem<*>?, after: MetaItem<*>? ->
|
||||
//TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.vis.spatial.PolyLine
|
||||
import hep.dataforge.vis.spatial.color
|
||||
import hep.dataforge.vis.spatial.three.ThreeMaterials.DEFAULT_LINE_COLOR
|
||||
import info.laht.threekt.core.Geometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.math.Color
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object ThreeLineFactory : ThreeFactory<PolyLine> {
|
||||
override val type: KClass<PolyLine> get() = PolyLine::class
|
||||
|
||||
override fun invoke(obj: PolyLine): Object3D {
|
||||
val geometry = Geometry().apply {
|
||||
vertices = obj.points.toTypedArray()
|
||||
}
|
||||
|
||||
val material =
|
||||
ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
|
||||
|
||||
material.linewidth = obj.thickness.toDouble()
|
||||
material.color = obj.color?.let { Color(it) }?: DEFAULT_LINE_COLOR
|
||||
|
||||
return LineSegments(geometry, material).apply {
|
||||
updatePosition(obj)
|
||||
//layers.enable(obj.layer)
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { propertyName, _, _ ->
|
||||
updateProperty(obj, propertyName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import info.laht.threekt.materials.LineBasicMaterial
|
||||
import info.laht.threekt.materials.Material
|
||||
import info.laht.threekt.materials.MeshBasicMaterial
|
||||
import info.laht.threekt.materials.MeshPhongMaterial
|
||||
import info.laht.threekt.math.Color
|
||||
|
||||
|
||||
object ThreeMaterials {
|
||||
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
||||
val DEFAULT = MeshBasicMaterial().apply {
|
||||
color.set(DEFAULT_COLOR)
|
||||
}
|
||||
val DEFAULT_LINE_COLOR = Color(Colors.black)
|
||||
val DEFAULT_LINE = LineBasicMaterial().apply {
|
||||
color.set(DEFAULT_LINE_COLOR)
|
||||
}
|
||||
|
||||
val HIGHLIGHT_MATERIAL = LineBasicMaterial().apply {
|
||||
color.set(Colors.ivory)
|
||||
linewidth = 8.0
|
||||
}
|
||||
|
||||
fun getLineMaterial(meta: Meta?): LineBasicMaterial {
|
||||
if (meta == null) return DEFAULT_LINE
|
||||
return LineBasicMaterial().apply {
|
||||
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_LINE_COLOR
|
||||
opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0
|
||||
transparent = opacity < 1.0
|
||||
linewidth = meta["thickness"].double ?: 1.0
|
||||
}
|
||||
}
|
||||
|
||||
fun getMaterial(visualObject3D: VisualObject3D): Material {
|
||||
val meta = visualObject3D.getProperty(Material3D.MATERIAL_KEY).node ?: return ThreeMaterials.DEFAULT
|
||||
return if (meta[Material3D.SPECULAR_COLOR] != null) {
|
||||
MeshPhongMaterial().apply {
|
||||
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||
specular = meta[Material3D.SPECULAR_COLOR]!!.getColor()
|
||||
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
|
||||
transparent = opacity < 1.0
|
||||
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
|
||||
needsUpdate = true
|
||||
}
|
||||
} else {
|
||||
MeshBasicMaterial().apply {
|
||||
color = meta[Material3D.COLOR_KEY]?.getColor() ?: DEFAULT_COLOR
|
||||
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
|
||||
transparent = opacity < 1.0
|
||||
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
|
||||
needsUpdate = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer color based on meta item
|
||||
*/
|
||||
fun MetaItem<*>.getColor(): Color {
|
||||
return when (this) {
|
||||
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(
|
||||
node[Colors.RED_KEY]?.int ?: 0,
|
||||
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||
node[Colors.BLUE_KEY]?.int ?: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,71 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.output.Output
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.hmr.require
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import info.laht.threekt.WebGLRenderer
|
||||
import info.laht.threekt.helpers.AxesHelper
|
||||
import info.laht.threekt.lights.AmbientLight
|
||||
import info.laht.threekt.scenes.Scene
|
||||
import org.w3c.dom.Element
|
||||
import kotlin.browser.window
|
||||
|
||||
private val elementResizeEvent = require("element-resize-event")
|
||||
|
||||
class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output<VisualObject3D> {
|
||||
|
||||
override val context: Context get() = three.context
|
||||
|
||||
val scene: Scene = Scene().apply {
|
||||
add(AmbientLight())
|
||||
meta["axis"]?.let {
|
||||
val axesHelper = AxesHelper(it.node["size"].int ?: 1)
|
||||
add(axesHelper)
|
||||
}
|
||||
}
|
||||
|
||||
private val camera = three.buildCamera(meta["camera"].node ?: EmptyMeta)
|
||||
|
||||
fun attach(element: Element, computeWidth: Element.() -> Int = { element.clientWidth }) {
|
||||
val width by meta.number(computeWidth(element)).int
|
||||
|
||||
val height: Int = (width / camera.aspect).toInt()
|
||||
|
||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||
setClearColor(Colors.skyblue, 1)
|
||||
setSize(width, height)
|
||||
}
|
||||
|
||||
three.addControls(camera, renderer.domElement, meta["controls"].node ?: EmptyMeta)
|
||||
|
||||
fun animate() {
|
||||
window.requestAnimationFrame {
|
||||
animate()
|
||||
}
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
|
||||
elementResizeEvent(element) {
|
||||
camera.updateProjectionMatrix()
|
||||
val newWidth = computeWidth(element)
|
||||
renderer.setSize(newWidth, (newWidth / camera.aspect).toInt())
|
||||
}
|
||||
|
||||
element.replaceWith(renderer.domElement)
|
||||
animate()
|
||||
}
|
||||
|
||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||
scene.add(three.buildObject3D(obj))
|
||||
}
|
||||
}
|
||||
|
||||
fun ThreePlugin.output(element: Element? = null, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit = {}) =
|
||||
ThreeOutput(this, buildMeta(meta, override)).apply {
|
||||
if(element!=null){
|
||||
attach(element)
|
||||
}
|
||||
}
|
@ -1,19 +1,14 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.context.AbstractPlugin
|
||||
import hep.dataforge.context.PluginFactory
|
||||
import hep.dataforge.context.PluginTag
|
||||
import hep.dataforge.context.content
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.context.*
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import info.laht.threekt.cameras.Camera
|
||||
import info.laht.threekt.cameras.PerspectiveCamera
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.external.controls.OrbitControls
|
||||
import info.laht.threekt.external.controls.TrackballControls
|
||||
import org.w3c.dom.Node
|
||||
import kotlin.collections.set
|
||||
import kotlin.reflect.KClass
|
||||
import info.laht.threekt.objects.Group as ThreeGroup
|
||||
|
||||
class ThreePlugin : AbstractPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
@ -28,31 +23,81 @@ class ThreePlugin : AbstractPlugin() {
|
||||
objectFactories[Convex::class] = ThreeConvexFactory
|
||||
objectFactories[Sphere::class] = ThreeSphereFactory
|
||||
objectFactories[ConeSegment::class] = ThreeCylinderFactory
|
||||
objectFactories[PolyLine::class] = ThreeLineFactory
|
||||
objectFactories[Label3D::class] = ThreeCanvasLabelFactory
|
||||
}
|
||||
|
||||
private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? {
|
||||
return objectFactories[type]
|
||||
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun findObjectFactory(type: KClass<out VisualObject>): ThreeFactory<VisualObject3D>? {
|
||||
return (objectFactories[type]
|
||||
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type })
|
||||
as ThreeFactory<VisualObject3D>?
|
||||
}
|
||||
|
||||
fun buildObject3D(obj: VisualObject3D): Object3D {
|
||||
return when (obj) {
|
||||
is VisualGroup3D -> Group(obj.mapNotNull {
|
||||
try {
|
||||
buildObject3D(it)
|
||||
} catch (ex: Throwable) {
|
||||
console.error(ex)
|
||||
logger.error(ex) { "Failed to render $it" }
|
||||
null
|
||||
is ThreeVisualObject -> obj.toObject3D()
|
||||
is Proxy -> proxyFactory(obj)
|
||||
is VisualGroup3D -> {
|
||||
val group = ThreeGroup()
|
||||
obj.children.forEach { (token, child) ->
|
||||
if (child is VisualObject3D && child.ignore != true) {
|
||||
try {
|
||||
val object3D = buildObject3D(child)
|
||||
group[token] = object3D
|
||||
} catch (ex: Throwable) {
|
||||
logger.error(ex) { "Failed to render $child" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
group.apply {
|
||||
updatePosition(obj)
|
||||
//obj.onChildrenChange()
|
||||
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
if (
|
||||
name.startsWith(VisualObject3D.position) ||
|
||||
name.startsWith(VisualObject3D.rotation) ||
|
||||
name.startsWith(VisualObject3D.scale)
|
||||
) {
|
||||
//update position of mesh using this object
|
||||
updatePosition(obj)
|
||||
} else if (name == VisualObject3D.VISIBLE_KEY) {
|
||||
visible = obj.visible ?: true
|
||||
}
|
||||
}
|
||||
|
||||
obj.onChildrenChange(this) { name, child ->
|
||||
if (name.isEmpty()) {
|
||||
logger.error { "Children change with empty namr on $group" }
|
||||
return@onChildrenChange
|
||||
}
|
||||
|
||||
val parentName = name.cutLast()
|
||||
val childName = name.last()!!
|
||||
|
||||
//removing old object
|
||||
findChild(name)?.let { oldChild ->
|
||||
oldChild.parent?.remove(oldChild)
|
||||
}
|
||||
|
||||
//adding new object
|
||||
if (child != null && child is VisualObject3D) {
|
||||
try {
|
||||
val object3D = buildObject3D(child)
|
||||
set(name, object3D)
|
||||
} catch (ex: Throwable) {
|
||||
logger.error(ex) { "Failed to render $child" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).apply {
|
||||
updatePosition(obj)
|
||||
}
|
||||
is Composite -> compositeFactory(obj)
|
||||
is Proxy -> proxyFactory(obj)
|
||||
else -> {
|
||||
//find specialized factory for this type if it is present
|
||||
val factory = findObjectFactory(obj::class)
|
||||
val factory: ThreeFactory<VisualObject3D>? = findObjectFactory(obj::class)
|
||||
when {
|
||||
factory != null -> factory(obj)
|
||||
obj is Shape -> ThreeShapeFactory(obj)
|
||||
@ -62,30 +107,44 @@ class ThreePlugin : AbstractPlugin() {
|
||||
}
|
||||
}
|
||||
|
||||
fun buildCamera(meta: Meta) = PerspectiveCamera(
|
||||
meta["fov"].int ?: 75,
|
||||
meta["aspect"].double ?: 1.0,
|
||||
meta["nearClip"].double ?: World.CAMERA_NEAR_CLIP,
|
||||
meta["farClip"].double ?: World.CAMERA_FAR_CLIP
|
||||
).apply {
|
||||
position.setZ(World.CAMERA_INITIAL_DISTANCE)
|
||||
rotation.set(
|
||||
World.CAMERA_INITIAL_X_ANGLE,
|
||||
World.CAMERA_INITIAL_Y_ANGLE,
|
||||
World.CAMERA_INITIAL_Z_ANGLE
|
||||
)
|
||||
}
|
||||
|
||||
fun addControls(camera: Camera, element: Node, meta: Meta) {
|
||||
when (meta["type"].string) {
|
||||
"trackball" -> TrackballControls(camera, element)
|
||||
else -> OrbitControls(camera, element)
|
||||
}
|
||||
}
|
||||
|
||||
companion object : PluginFactory<ThreePlugin> {
|
||||
override val tag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
|
||||
override val type = ThreePlugin::class
|
||||
override fun invoke(meta: Meta) = ThreePlugin()
|
||||
override fun invoke(meta: Meta, context: Context) = ThreePlugin()
|
||||
}
|
||||
}
|
||||
|
||||
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
|
||||
object3D.name = token.toString()
|
||||
add(object3D)
|
||||
}
|
||||
|
||||
internal fun Object3D.getOrCreateGroup(name: Name): Object3D {
|
||||
return when {
|
||||
name.isEmpty() -> this
|
||||
name.length == 1 -> {
|
||||
val token = name.first()!!
|
||||
children.find { it.name == token.toString() } ?: info.laht.threekt.objects.Group().also { group ->
|
||||
group.name = token.toString()
|
||||
this.add(group)
|
||||
}
|
||||
}
|
||||
else -> getOrCreateGroup(name.first()!!.asName()).getOrCreateGroup(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
internal operator fun Object3D.set(name: Name, obj: Object3D) {
|
||||
when (name.length) {
|
||||
0 -> error("Can't set object with an empty name")
|
||||
1 -> set(name.first()!!, obj)
|
||||
else -> getOrCreateGroup(name.cutLast())[name.last()!!] = obj
|
||||
}
|
||||
}
|
||||
|
||||
internal fun Object3D.findChild(name: Name): Object3D? {
|
||||
return when {
|
||||
name.isEmpty() -> this
|
||||
name.length == 1 -> this.children.find { it.name == name.first()!!.toString() }
|
||||
else -> findChild(name.first()!!.asName())?.findChild(name.cutFirst())
|
||||
}
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.spatial.Proxy
|
||||
import hep.dataforge.vis.spatial.Proxy.Companion.PROXY_CHILD_PROPERTY_PREFIX
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import info.laht.threekt.core.Object3D
|
||||
|
||||
@ -10,15 +12,27 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
|
||||
override val type = Proxy::class
|
||||
|
||||
override fun invoke(obj: Proxy): Object3D {
|
||||
val template = obj.template
|
||||
val template = obj.prototype
|
||||
val cachedObject = cache.getOrPut(template) {
|
||||
three.buildObject3D(template)
|
||||
}
|
||||
|
||||
//val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material)
|
||||
val mesh = cachedObject.clone()
|
||||
val object3D = cachedObject.clone()
|
||||
object3D.updatePosition(obj)
|
||||
|
||||
mesh.updatePosition(obj)
|
||||
return mesh
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
if (name.first()?.body == 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] as? VisualObject3D ?: error("Proxy child with name '$childName' not found or not a 3D object")
|
||||
val child = object3D.findChild(childName)?: error("Object child with name '$childName' not found")
|
||||
child.updateProperty(proxyChild, propertyName)
|
||||
} else {
|
||||
object3D.updateProperty(obj, name)
|
||||
}
|
||||
}
|
||||
|
||||
return object3D
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
@file:UseSerializers(Point3DSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.spatial.Point3D
|
||||
import hep.dataforge.vis.spatial.Point3DSerializer
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import info.laht.threekt.core.Object3D
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
/**
|
||||
* A custom visual object that has its own Three.js renderer
|
||||
*/
|
||||
interface ThreeVisualObject : VisualObject3D {
|
||||
fun toObject3D(): Object3D
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class CustomThreeVisualObject(val threeFactory: ThreeFactory<VisualObject3D>) : AbstractVisualObject(),
|
||||
ThreeVisualObject {
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
override var scale: Point3D? = null
|
||||
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
|
||||
override fun toObject3D(): Object3D = threeFactory(this)
|
||||
|
||||
}
|
@ -16,6 +16,9 @@ import info.laht.threekt.math.Matrix4
|
||||
import info.laht.threekt.math.Vector3
|
||||
import info.laht.threekt.objects.Mesh
|
||||
|
||||
/**
|
||||
* Constructive Solid Geometry
|
||||
*/
|
||||
open external class CSG {
|
||||
open var polygons: Array<Polygon>
|
||||
open fun clone(): CSG
|
||||
|
@ -0,0 +1,87 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.js.requireJS
|
||||
import hep.dataforge.vis.js.editor.card
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.button
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.*
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.events.Event
|
||||
import org.w3c.files.Blob
|
||||
import org.w3c.files.BlobPropertyBag
|
||||
import kotlin.dom.clear
|
||||
|
||||
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
val fileSaver = requireJS("file-saver")
|
||||
val blob = Blob(arrayOf(dataBuilder()), BlobPropertyBag("$mimeType;charset=utf-8"))
|
||||
fileSaver.saveAs(blob, fileName)
|
||||
}
|
||||
|
||||
fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
|
||||
clear()
|
||||
append {
|
||||
card("Settings") {
|
||||
div("row") {
|
||||
div("col-2") {
|
||||
label("checkbox-inline") {
|
||||
input(type = InputType.checkBox).apply {
|
||||
checked = canvas.axes.visible
|
||||
onChangeFunction = {
|
||||
canvas.axes.visible = checked
|
||||
}
|
||||
}
|
||||
+"Axes"
|
||||
}
|
||||
}
|
||||
div("col-1") {
|
||||
button {
|
||||
+"Export"
|
||||
onClickFunction = {
|
||||
val json = (canvas.content as? VisualGroup3D)?.let { group ->
|
||||
Visual3DPlugin.json.stringify(
|
||||
VisualGroup3D.serializer(),
|
||||
group
|
||||
)
|
||||
}
|
||||
if (json != null) {
|
||||
saveData(it, "object.json", "text/json"){
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
card("Layers") {
|
||||
div("row") {
|
||||
(0..11).forEach { layer ->
|
||||
div("col-1") {
|
||||
label { +layer.toString() }
|
||||
input(type = InputType.checkBox).apply {
|
||||
if (layer == 0) {
|
||||
checked = true
|
||||
}
|
||||
onChangeFunction = {
|
||||
if (checked) {
|
||||
canvas.camera.layers.enable(layer)
|
||||
} else {
|
||||
canvas.camera.layers.disable(layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
@ -6,21 +6,16 @@ import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.DirectGeometry
|
||||
import info.laht.threekt.core.Face3
|
||||
import info.laht.threekt.core.Geometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.external.controls.OrbitControls
|
||||
import info.laht.threekt.materials.Material
|
||||
import info.laht.threekt.math.Euler
|
||||
import info.laht.threekt.math.Vector3
|
||||
|
||||
/**
|
||||
* 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) }
|
||||
}
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import info.laht.threekt.textures.Texture
|
||||
import kotlin.math.PI
|
||||
|
||||
val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
||||
|
||||
@ -28,6 +23,8 @@ val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?:
|
||||
|
||||
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
||||
|
||||
internal fun Double.toRadians() = this * PI / 180
|
||||
|
||||
fun CSG.toGeometry(): Geometry {
|
||||
val geom = Geometry()
|
||||
|
||||
@ -43,7 +40,7 @@ fun CSG.toGeometry(): Geometry {
|
||||
}
|
||||
|
||||
for (j in 3..polygon.vertices.size) {
|
||||
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, zero)
|
||||
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, World.ZERO)
|
||||
fc.vertexNormals = arrayOf(
|
||||
Vector3().copy(pvs[0].normal),
|
||||
Vector3().copy(pvs[j - 2].normal),
|
||||
@ -64,4 +61,19 @@ fun CSG.toGeometry(): Geometry {
|
||||
geom.computeBoundingSphere()
|
||||
geom.computeBoundingBox()
|
||||
return geom
|
||||
}
|
||||
|
||||
internal fun Any.dispose() {
|
||||
when (this) {
|
||||
is Geometry -> dispose()
|
||||
is BufferGeometry -> dispose()
|
||||
is DirectGeometry -> dispose()
|
||||
is Material -> dispose()
|
||||
is Mesh -> {
|
||||
geometry.dispose()
|
||||
material.dispose()
|
||||
}
|
||||
is OrbitControls -> dispose()
|
||||
is Texture -> dispose()
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
/*
|
||||
* The MIT License
|
||||
*
|
||||
* Copyright 2017-2018 Lars Ivar Hatledal
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
@file:JsModule("three")
|
||||
@file:JsNonModule
|
||||
|
||||
package info.laht.threekt
|
||||
|
||||
|
||||
external val REVISION: String
|
||||
external val CullFaceNone: Int
|
||||
external val CullFaceBack: Int
|
||||
external val CullFaceFront: Int
|
||||
external val CullFaceFrontBack: Int
|
||||
external val FrontFaceDirectionCW: Int
|
||||
external val FrontFaceDirectionCCW: Int
|
||||
external val BasicShadowMap: Int
|
||||
external val PCFShadowMap: Int
|
||||
external val PCFSoftShadowMap: Int
|
||||
external val FrontSide: Int
|
||||
external val BackSide: Int
|
||||
external val DoubleSide: Int
|
||||
external val FlatShading: Int
|
||||
external val SmoothShading: Int
|
||||
external val NoColors: Int
|
||||
external val FaceColors: Int
|
||||
external val VertexColors: Int
|
||||
external val NoBlending: Int
|
||||
external val NormalBlending: Int
|
||||
external val AdditiveBlending: Int
|
||||
external val SubtractiveBlending: Int
|
||||
external val MultiplyBlending: Int
|
||||
external val CustomBlending: Int
|
||||
external val AddEquation: Int
|
||||
external val SubtractEquation: Int
|
||||
external val ReverseSubtractEquation: Int
|
||||
external val MinEquation: Int
|
||||
external val MaxEquation: Int
|
||||
external val ZeroFactor: Int
|
||||
external val OneFactor: Int
|
||||
external val SrcColorFactor: Int
|
||||
external val OneMinusSrcColorFactor: Int
|
||||
external val SrcAlphaFactor: Int
|
||||
external val OneMinusSrcAlphaFactor: Int
|
||||
external val DstAlphaFactor: Int
|
||||
external val OneMinusDstAlphaFactor: Int
|
||||
external val DstColorFactor: Int
|
||||
external val OneMinusDstColorFactor: Int
|
||||
external val SrcAlphaSaturateFactor: Int
|
||||
external val NeverDepth: Int
|
||||
external val AlwaysDepth: Int
|
||||
external val LessDepth: Int
|
||||
external val LessEqualDepth: Int
|
||||
external val EqualDepth: Int
|
||||
external val GreaterEqualDepth: Int
|
||||
external val GreaterDepth: Int
|
||||
external val NotEqualDepth: Int
|
||||
external val MultiplyOperation: Int
|
||||
external val MixOperation: Int
|
||||
external val AddOperation: Int
|
||||
external val NoToneMapping: Int
|
||||
external val LinearToneMapping: Int
|
||||
external val ReinhardToneMapping: Int
|
||||
external val Uncharted2ToneMapping: Int
|
||||
external val CineonToneMapping: Int
|
||||
external val UVMapping: Int
|
||||
external val CubeReflectionMapping: Int
|
||||
external val CubeRefractionMapping: Int
|
||||
external val EquirectangularReflectionMapping: Int
|
||||
external val EquirectangularRefractionMapping: Int
|
||||
external val SphericalReflectionMapping: Int
|
||||
external val CubeUVReflectionMapping: Int
|
||||
external val CubeUVRefractionMapping: Int
|
||||
external val RepeatWrapping: Int
|
||||
external val ClampToEdgeWrapping: Int
|
||||
external val MirroredRepeatWrapping: Int
|
||||
external val NearestFilter: Int
|
||||
external val NearestMipMapNearestFilter: Int
|
||||
external val NearestMipMapLinearFilter: Int
|
||||
external val LinearFilter: Int
|
||||
external val LinearMipMapNearestFilter: Int
|
||||
external val LinearMipMapLinearFilter: Int
|
||||
external val UnsignedByteType: Int
|
||||
external val ByteType: Int
|
||||
external val ShortType: Int
|
||||
external val UnsignedShortType: Int
|
||||
external val IntType: Int
|
||||
external val UnsignedIntType: Int
|
||||
external val FloatType: Int
|
||||
external val HalfFloatType: Int
|
||||
external val UnsignedShort4444Type: Int
|
||||
external val UnsignedShort5551Type: Int
|
||||
external val UnsignedShort565Type: Int
|
||||
external val UnsignedInt248Type: Int
|
||||
external val AlphaFormat: Int
|
||||
external val RGBFormat: Int
|
||||
external val RGBAFormat: Int
|
||||
external val LuminanceFormat: Int
|
||||
external val LuminanceAlphaFormat: Int
|
||||
external val RGBEFormat: Int
|
||||
external val DepthFormat: Int
|
||||
external val DepthStencilFormat: Int
|
||||
external val RGB_S3TC_DXT1_Format: Int
|
||||
external val RGBA_S3TC_DXT1_Format: Int
|
||||
external val RGBA_S3TC_DXT3_Format: Int
|
||||
external val RGBA_S3TC_DXT5_Format: Int
|
||||
external val RGB_PVRTC_4BPPV1_Format: Int
|
||||
external val RGB_PVRTC_2BPPV1_Format: Int
|
||||
external val RGBA_PVRTC_4BPPV1_Format: Int
|
||||
external val RGBA_PVRTC_2BPPV1_Format: Int
|
||||
external val RGB_ETC1_Format: Int
|
||||
external val LoopOnce: Int
|
||||
external val LoopRepeat: Int
|
||||
external val LoopPingPong: Int
|
||||
external val InterpolateDiscrete: Int
|
||||
external val InterpolateLinear: Int
|
||||
external val InterpolateSmooth: Int
|
||||
external val ZeroCurvatureEnding: Int
|
||||
external val ZeroSlopeEnding: Int
|
||||
external val WrapAroundEnding: Int
|
||||
external val TrianglesDrawMode: Int
|
||||
external val TriangleStripDrawMode: Int
|
||||
external val TriangleFanDrawMode: Int
|
||||
external val LinearEncoding: Int
|
||||
external val sRGBEncoding: Int
|
||||
external val GammaEncoding: Int
|
||||
external val RGBEEncoding: Int
|
||||
external val LogLuvEncoding: Int
|
||||
external val RGBM7Encoding: Int
|
||||
external val RGBM16Encoding: Int
|
||||
external val RGBDEncoding: Int
|
||||
external val BasicDepthPacking: Int
|
||||
external val RGBADepthPacking: Int
|
||||
|
||||
|
@ -0,0 +1,128 @@
|
||||
@file:JsModule("three")
|
||||
@file:JsNonModule
|
||||
|
||||
package info.laht.threekt.animation
|
||||
|
||||
import info.laht.threekt.core.Object3D
|
||||
|
||||
external class AnimationAction(
|
||||
mixer: AnimationMixer,
|
||||
clip: AnimationClip,
|
||||
localRoot: Object3D
|
||||
) {
|
||||
|
||||
/**
|
||||
* Setting enabled to false disables this action, so that it has no impact. Default is true.
|
||||
*
|
||||
* When the action is re-enabled, the animation continues from its current time
|
||||
* (setting enabled to false doesn't reset the action).
|
||||
*
|
||||
* Note: Setting enabled to true doesn’t automatically restart the animation.
|
||||
* Setting enabled to true will only restart the animation immediately if the following condition
|
||||
* is fulfilled: paused is false, this action has not been deactivated in the meantime
|
||||
* (by executing a stop or reset command), and neither weight nor timeScale is 0.
|
||||
*/
|
||||
var enabled: Boolean
|
||||
|
||||
/**
|
||||
* The looping mode (can be changed with setLoop). Default is THREE.LoopRepeat (with an infinite number of repetitions)
|
||||
*/
|
||||
var loop: Int
|
||||
|
||||
/**
|
||||
* Setting paused to true pauses the execution of the action by setting the effective time scale to 0. Default is false.
|
||||
*/
|
||||
var paused: Boolean
|
||||
|
||||
var repetitions: Int
|
||||
|
||||
var time: Double
|
||||
|
||||
var timeScale: Double
|
||||
|
||||
/**
|
||||
* The degree of influence of this action (in the interval [0, 1]).
|
||||
* Values between 0 (no impact) and 1 (full impact) can be used to blend between several actions. Default is 1.
|
||||
*
|
||||
* Properties/methods concerning weight are: crossFadeFrom, crossFadeTo, enabled, fadeIn, fadeOut,
|
||||
* getEffectiveWeight, setEffectiveWeight, stopFading.
|
||||
*/
|
||||
var weight: Double
|
||||
|
||||
/**
|
||||
* Enables smooth interpolation without separate clips for start, loop and end. Default is true.
|
||||
*/
|
||||
var zeroSlopeAtEnd: Boolean
|
||||
|
||||
/**
|
||||
* Enables smooth interpolation without separate clips for start, loop and end. Default is true.
|
||||
*/
|
||||
var zeroSlopeAtStart: Boolean
|
||||
|
||||
|
||||
// State & Scheduling
|
||||
|
||||
fun play(): AnimationAction
|
||||
|
||||
fun stop(): AnimationAction
|
||||
|
||||
fun reset(): AnimationAction
|
||||
|
||||
fun isRunning(): Boolean
|
||||
|
||||
// return true when play has been called
|
||||
fun isScheduled(): Boolean
|
||||
|
||||
fun startAt(time: Number): AnimationAction
|
||||
fun setLoop(mode: Int, repetitions: Int): AnimationAction
|
||||
|
||||
// Weight
|
||||
|
||||
// set the weight stopping any scheduled fading
|
||||
// although .enabled = false yields an effective weight of zero, this
|
||||
// method does *not* change .enabled, because it would be confusing
|
||||
fun setEffectiveWeight(weight: Number)
|
||||
|
||||
// return the weight considering fading and .enabled
|
||||
fun getEffectiveWeight()
|
||||
|
||||
fun fadeIn(duration: Number): AnimationAction
|
||||
|
||||
fun fadeOut(duration: Number): AnimationAction
|
||||
|
||||
fun crossFadeFrom(fadeOutAction: AnimationAction, duration: Number, warp: Boolean)
|
||||
|
||||
fun crossFadeTo(fadeInAction: AnimationAction, duration: Number, warp: Boolean)
|
||||
|
||||
fun stopFading(): AnimationAction
|
||||
|
||||
// Time Scale Control
|
||||
|
||||
// set the time scale stopping any scheduled warping
|
||||
// although .paused = true yields an effective time scale of zero, this
|
||||
// method does *not* change .paused, because it would be confusing
|
||||
fun setEffectiveTimeScale(timeScale: Number): AnimationAction
|
||||
|
||||
// return the time scale considering warping and .paused
|
||||
fun getEffectiveTimeScale(): Double
|
||||
|
||||
fun setDuration(duration: Number): AnimationAction
|
||||
|
||||
fun syncWith(action: AnimationAction)
|
||||
|
||||
fun halt(duration: Number): AnimationAction
|
||||
|
||||
fun warp(startTimeScale: Number, endTimeScale: Number, duration: Number): AnimationAction
|
||||
|
||||
fun stopWarping(): AnimationAction
|
||||
|
||||
// Object Accessors
|
||||
|
||||
fun getMixer(): AnimationMixer
|
||||
|
||||
fun getClip(): AnimationClip
|
||||
|
||||
fun getRoot(): Object3D
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
@file:JsModule("three")
|
||||
@file:JsNonModule
|
||||
|
||||
package info.laht.threekt.animation
|
||||
|
||||
/**
|
||||
* An AnimationClip is a reusable set of keyframe tracks which represent an animation.
|
||||
|
||||
For an overview of the different elements of the three.js animation system see the "Animation System" article in the "Next Steps" section of the manual.
|
||||
*
|
||||
* @param name a name for this clip.
|
||||
* @param duration the duration of this clip (in seconds). If a negative value is passed, the duration will be calculated from the passed tracks array.
|
||||
* @param tracks an array of KeyframeTracks.
|
||||
*/
|
||||
external class AnimationClip(
|
||||
name: String,
|
||||
duration: Number,
|
||||
tracks: Array<KeyFrameTrack>
|
||||
) {
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Parses the animation.hierarchy format and returns an AnimationClip.
|
||||
*/
|
||||
fun parse(json: String): AnimationClip
|
||||
|
||||
/**
|
||||
* Takes an AnimationClip and returns a JSON object.
|
||||
*/
|
||||
fun toJSON(clip: AnimationClip): dynamic
|
||||
}
|
||||
|
||||
var uuid: String
|
||||
var name: String
|
||||
var duration: Double
|
||||
var tracks: Array<KeyFrameTrack>
|
||||
|
||||
/**
|
||||
* Optimizes each track by removing equivalent sequential keys (which are common in morph target sequences).
|
||||
*/
|
||||
fun optimize(): AnimationClip
|
||||
|
||||
/**
|
||||
* Sets the duration of the clip to the duration of its longest KeyframeTrack.
|
||||
*/
|
||||
fun resetDuration()
|
||||
|
||||
/**
|
||||
* Trims all tracks to the clip's duration.
|
||||
*/
|
||||
fun trim(): AnimationClip
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
@file:JsModule("three")
|
||||
@file:JsNonModule
|
||||
|
||||
package info.laht.threekt.animation
|
||||
|
||||
import info.laht.threekt.core.Object3D
|
||||
|
||||
/**
|
||||
* The AnimationMixer is a player for animations on a particular object in the scene.
|
||||
* When multiple objects in the scene are animated independently, one AnimationMixer may be used for each object.
|
||||
|
||||
* For an overview of the different elements of the three.js animation system see the "Animation System" article
|
||||
* in the "Next Steps" section of the manual.
|
||||
*/
|
||||
external class AnimationMixer(
|
||||
root: Object3D
|
||||
) {
|
||||
|
||||
var time: Double
|
||||
var timeScale: Double
|
||||
|
||||
fun clipAction(clip: AnimationClip, optionalRoot: Object3D = definedExternally): AnimationClip
|
||||
fun existingAction(clip: AnimationClip, optionalRoot: Object3D = definedExternally): AnimationClip
|
||||
|
||||
fun getRoot(): Object3D
|
||||
|
||||
fun stopAllAction()
|
||||
|
||||
fun update(deltaTimeInSeconds: Number)
|
||||
|
||||
fun uncacheClip(clip: AnimationClip)
|
||||
|
||||
fun unchacheRoot(root: Object3D)
|
||||
|
||||
fun uncacheAction(clip: AnimationClip, optionalRoot: Object3D)
|
||||
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
@file:JsModule("three")
|
||||
@file:JsNonModule
|
||||
|
||||
package info.laht.threekt.animation
|
||||
|
||||
external object AnimationUtils {
|
||||
//TODO
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user