Merge pull request #17 from mipt-npm/dev

Dev
This commit is contained in:
Alexander Nozik 2020-01-13 08:03:25 +03:00 committed by GitHub
commit 8094f02f06
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
316 changed files with 18364 additions and 1929 deletions

17
.github/workflows/gradle.yml vendored Normal file
View 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

View File

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

View File

@ -1,34 +1,38 @@
val dataforgeVersion by extra("0.1.3") import scientifik.useSerialization
plugins{ val dataforgeVersion by extra("0.1.5-dev-6")
val kotlinVersion = "1.3.50-eap-5"
plugins {
val kotlinVersion = "1.3.61"
val toolsVersion = "0.3.2"
kotlin("jvm") version kotlinVersion apply false kotlin("jvm") version kotlinVersion apply false
id("kotlin2js") version kotlinVersion apply false
id("kotlin-dce-js") 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 toolsVersion apply false
id("scientifik.mpp") version "0.1.4" apply false id("scientifik.jvm") version toolsVersion apply false
id("scientifik.jvm") version "0.1.4" apply false id("scientifik.js") version toolsVersion apply false
id("scientifik.js") version "0.1.4" apply false id("scientifik.publish") version toolsVersion apply false
id("scientifik.publish") version "0.1.4" apply false id("org.openjfx.javafxplugin") version "0.0.8" apply false
id("org.openjfx.javafxplugin") version "0.0.7" apply false
} }
allprojects { allprojects {
repositories { repositories {
mavenLocal() 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/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" group = "hep.dataforge"
version = "0.1.0-dev" version = "0.1.0-dev"
} }
subprojects{
this.useSerialization()
}
val githubProject by extra("dataforge-vis") val githubProject by extra("dataforge-vis")
val bintrayRepo by extra("dataforge") val bintrayRepo by extra("dataforge")

View File

@ -1,25 +1,50 @@
import org.openjfx.gradle.JavaFXOptions
import scientifik.useSerialization
plugins { plugins {
id("scientifik.mpp") id("scientifik.mpp")
} id("org.openjfx.javafxplugin")
scientifik{
serialization = true
} }
val dataforgeVersion: String by rootProject.extra val dataforgeVersion: String by rootProject.extra
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
useSerialization()
kotlin { kotlin {
jvm{
withJava()
}
sourceSets { sourceSets {
val commonMain by getting { commonMain{
dependencies { dependencies {
api("hep.dataforge:dataforge-output:$dataforgeVersion") 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 { dependencies {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion") 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")
}

View File

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

View File

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

View File

@ -1,7 +1,13 @@
package hep.dataforge.vis.common 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 { object Colors {
const val aliceblue = 0xF0F8FF const val aliceblue = 0xF0F8FF
@ -174,4 +180,63 @@ object Colors {
const val whitesmoke = 0xF5F5F5 const val whitesmoke = 0xF5F5F5
const val yellow = 0xFFFF00 const val yellow = 0xFFFF00
const val yellowgreen = 0x9ACD32 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()
}
} }

View File

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

View File

@ -1,131 +1,89 @@
package hep.dataforge.vis.common package hep.dataforge.vis.common
import hep.dataforge.meta.Config import hep.dataforge.names.*
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.provider.Provider import hep.dataforge.provider.Provider
import kotlinx.serialization.Transient
import kotlin.collections.set
open class VisualGroup<T : VisualObject> : AbstractVisualObject(), Iterable<T>, Provider { /**
* Represents a group of [VisualObject] instances
protected open val namedChildren: MutableMap<Name, T> = HashMap() */
protected open val unnamedChildren: MutableList<T> = ArrayList() interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
/**
override var properties: Config? = null * A map of top level named children
*/
val children: Map<NameToken, VisualObject>
override val defaultTarget: String get() = VisualObject.TYPE 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> { override fun provideTop(target: String): Map<Name, Any> =
return when (target) { when (target) {
VisualObject.TYPE -> namedChildren 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() else -> emptyMap()
} }
}
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
super.propertyChanged(name, before, after)
forEach {
it.propertyChanged(name, before, after)
}
}
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 * Iterate over children of this group
*/ */
fun onChildrenChange(owner: Any?, action: (Name?, T?) -> Unit) { override fun iterator(): Iterator<VisualObject> = children.values.iterator()
listeners.add(Listener(owner, action))
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())
}
} }
/**
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
*/
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 * Remove children change listener
*/ */
fun removeChildrenChangeListener(owner: Any?) { fun removeChildrenChangeListener(owner: Any?)
listeners.removeAll { it.owner === owner }
}
/** operator fun set(name: Name, child: VisualObject?)
* 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(key: String?, child: T?) = set(key?.asName(), child)
/**
* 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()
}
} }
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: Name.EMPTY)
fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null }

View File

@ -2,18 +2,20 @@ package hep.dataforge.vis.common
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.vis.common.VisualObject.Companion.TYPE import hep.dataforge.vis.common.VisualObject.Companion.TYPE
import kotlinx.serialization.Transient import kotlinx.serialization.Transient
private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers) //private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta) //private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
/** /**
* A root type for display hierarchy * A root type for display hierarchy
*/ */
@Type(TYPE) @Type(TYPE)
interface VisualObject : MetaRepr, Configurable { interface VisualObject : Configurable {
/** /**
* The parent object of this one. If null, this one is a root. * The parent object of this one. If null, this one is a root.
@ -21,10 +23,17 @@ interface VisualObject : MetaRepr, Configurable {
@Transient @Transient
var parent: VisualObject? var parent: VisualObject?
/**
* All properties including styles and prototypes if present, but without inheritance
*/
fun allProperties(): Laminate
/** /**
* Set property for this object * 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 * Get property including or excluding parent properties
@ -32,9 +41,11 @@ interface VisualObject : MetaRepr, Configurable {
fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>? 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 * Add listener triggering on property change
@ -46,65 +57,67 @@ interface VisualObject : MetaRepr, Configurable {
*/ */
fun removeChangeListener(owner: Any?) fun removeChangeListener(owner: Any?)
/**
* List of names of styles applied to this object. Order matters. Not inherited
*/
var styles: List<String>
companion object { companion object {
const val TYPE = "visual" const val TYPE = "visual"
val STYLE_KEY = "@style".asName()
//const val META_KEY = "@meta" //const val META_KEY = "@meta"
//const val TAGS_KEY = "@tags" //const val TAGS_KEY = "@tags"
} }
} }
internal data class MetaListener( /**
val owner: Any? = null, * Get [VisualObject] property using key as a String
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit */
) 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.
*/
@Transient fun VisualObject.useStyle(name: String) {
private val listeners = HashSet<MetaListener>() styles = styles + name
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
for (l in listeners) {
l.action(name, before, after)
}
}
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
listeners.add(MetaListener(owner, action))
}
override fun removeChangeListener(owner: Any?) {
listeners.removeAll { it.owner == owner }
}
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()
}
} }
//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)
//}
tailrec fun VisualObject.findStyle(name: String): Meta? =
(this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name)
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
//operator fun VisualObject.get(name: Name): VisualObject?{
// return when {
// name.isEmpty() -> this
// this is VisualGroup -> this[name]
// else -> null
// }
//}

View File

@ -4,18 +4,18 @@ import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.names.asName import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.values.Value import hep.dataforge.values.Value
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadOnlyProperty
import kotlin.properties.ReadWriteProperty import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty import kotlin.reflect.KProperty
fun String.asName() = NameToken(this).asName()
/** /**
* A delegate for display object properties * A delegate for display object properties
*/ */
class DisplayObjectDelegate( class VisualObjectDelegate(
val key: Name?, val key: Name?,
val default: MetaItem<*>?, val default: MetaItem<*>?,
val inherited: Boolean val inherited: Boolean
@ -35,82 +35,84 @@ class DisplayObjectDelegate(
} }
} }
class DisplayObjectDelegateWrapper<T>( class VisualObjectDelegateWrapper<T>(
val obj: VisualObject,
val key: Name?, val key: Name?,
val default: T, val default: T,
val inherited: Boolean, val inherited: Boolean,
val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) }, val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) },
val read: (MetaItem<*>?) -> T? val read: (MetaItem<*>?) -> T?
) : ReadWriteProperty<VisualObject, T> { ) : ReadWriteProperty<Any?, T> {
//private var cachedName: Name? = null //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() val name = key ?: property.name.asName()
return if (inherited) { return read(obj.getProperty(name,inherited))?:default
read(thisRef.getProperty(name))
} else {
read(thisRef.config[name])
} ?: 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() 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) = 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) = 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) = 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) = 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) = 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) = 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) = 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) = 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) } //fun <T : Configurable> Configurable.spec(spec: Specification<T>, key: String? = null) = ChildConfigDelegate<T>(key) { spec.wrap(this) }
@JvmName("safeString") @JvmName("safeString")
fun VisualObject.string(default: String, key: String? = null, inherited: Boolean = false) = 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") @JvmName("safeBoolean")
fun VisualObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = false) = 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") @JvmName("safeNumber")
fun VisualObject.number(default: Number, key: String? = null, inherited: Boolean = false) = 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") @JvmName("safeDouble")
fun VisualObject.double(default: Double, key: String? = null, inherited: Boolean = false) = 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") @JvmName("safeInt")
fun VisualObject.int(default: Int, key: String? = null, inherited: Boolean = false) = 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) = 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 //merge properties
@ -118,11 +120,11 @@ fun <T> VisualObject.merge(
key: String? = null, key: String? = null,
transformer: (Sequence<MetaItem<*>>) -> T transformer: (Sequence<MetaItem<*>>) -> T
): ReadOnlyProperty<VisualObject, T> { ): ReadOnlyProperty<VisualObject, T> {
return object : ReadOnlyProperty<VisualObject, T> { return object : ReadOnlyProperty<Any?, T> {
override fun getValue(thisRef: VisualObject, property: KProperty<*>): T { override fun getValue(thisRef: Any?, property: KProperty<*>): T {
val name = key?.asName() ?: property.name.asName() val name = key?.toName() ?: property.name.asName()
val sequence = sequence<MetaItem<*>> { val sequence = sequence<MetaItem<*>> {
var thisObj: VisualObject? = thisRef var thisObj: VisualObject? = this@merge
while (thisObj != null) { while (thisObj != null) {
thisObj.config[name]?.let { yield(it) } thisObj.config[name]?.let { yield(it) }
thisObj = thisObj.parent thisObj = thisObj.parent

View File

@ -30,7 +30,8 @@ class VisualPlugin(meta: Meta) : AbstractPlugin(meta) {
companion object : PluginFactory<VisualPlugin> { companion object : PluginFactory<VisualPlugin> {
override val tag: PluginTag = PluginTag(name = "visual", group = PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag(name = "visual", group = PluginTag.DATAFORGE_GROUP)
override val type: KClass<out VisualPlugin> = VisualPlugin::class 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" const val VISUAL_FACTORY_TYPE = "visual.factory"
} }

View File

@ -3,12 +3,18 @@ package hep.dataforge.vis.common
import hep.dataforge.descriptors.ValueDescriptor import hep.dataforge.descriptors.ValueDescriptor
import hep.dataforge.meta.* import hep.dataforge.meta.*
/**
* Extension property to access the "widget" key of [ValueDescriptor]
*/
var ValueDescriptor.widget: Meta var ValueDescriptor.widget: Meta
get() = this.config["widget"].node?: EmptyMeta get() = this.config["widget"].node?: EmptyMeta
set(value) { set(value) {
this.config["widget"] = value this.config["widget"] = value
} }
/**
* Extension property to access the "widget.type" key of [ValueDescriptor]
*/
var ValueDescriptor.widgetType: String? var ValueDescriptor.widgetType: String?
get() = this["widget.type"].string get() = this["widget.type"].string
set(value) { set(value) {

View File

@ -1,14 +1,10 @@
package hep.dataforge.vis.hmr package hep.dataforge.js
import kotlin.browser.document import kotlin.browser.document
import kotlin.dom.hasClass import kotlin.dom.hasClass
external val module: Module external val module: Module
external interface Module {
val hot: Hot?
}
external interface Hot { external interface Hot {
val data: dynamic val data: dynamic
@ -19,17 +15,31 @@ external interface Hot {
fun dispose(callback: (data: dynamic) -> Unit) fun dispose(callback: (data: dynamic) -> Unit)
} }
external fun require(name: String): dynamic external interface Module {
val hot: Hot?
abstract class ApplicationBase {
open val stateKeys: List<String> get() = emptyList()
abstract fun start(state: Map<String, Any>)
open fun dispose(): Map<String, Any> = emptyMap()
} }
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) { return if (document.body?.hasClass("testApp") == true) {
val application = builder() 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 -> val state: dynamic = module.hot?.let { hot ->
hot.accept() hot.accept()

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@ -96,7 +96,7 @@ class FXPlugin(meta: Meta = EmptyMeta) : AbstractPlugin(meta) {
companion object : PluginFactory<FXPlugin> { companion object : PluginFactory<FXPlugin> {
override val type: KClass<out FXPlugin> = FXPlugin::class override val type: KClass<out FXPlugin> = FXPlugin::class
override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP) 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)
} }
} }

View File

@ -1,6 +1,8 @@
package hep.dataforge.vis.fx.values package hep.dataforge.vis.fx.editor
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Null import hep.dataforge.values.Null
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.asValue import hep.dataforge.values.asValue
@ -39,9 +41,10 @@ class ColorValueChooser : ValueChooserBase<ColorPicker>() {
return node return node
} }
companion object: ValueChooser.Factory{ companion object : ValueChooser.Factory {
override val name: String = "color" override val name: Name = "color".asName()
override fun invoke(meta: Meta): ValueChooser = ColorValueChooser() override fun invoke(meta: Meta): ValueChooser =
ColorValueChooser()
} }
} }

View File

@ -3,11 +3,13 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * 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.Meta
import hep.dataforge.meta.get import hep.dataforge.meta.get
import hep.dataforge.meta.value import hep.dataforge.meta.value
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Value import hep.dataforge.values.Value
import hep.dataforge.values.parseValue import hep.dataforge.values.parseValue
import javafx.collections.FXCollections import javafx.collections.FXCollections
@ -50,9 +52,10 @@ class ComboBoxValueChooser(val values: Collection<Value>? = null) : ValueChooser
} }
companion object : ValueChooser.Factory { 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)
} }
} }

View File

@ -3,20 +3,22 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * 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.context.Global
import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.names.NameToken import hep.dataforge.names.NameToken
import hep.dataforge.vis.fx.dfIconView 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.*
import javafx.scene.control.cell.TextFieldTreeTableCell import javafx.scene.control.cell.TextFieldTreeTableCell
import javafx.scene.layout.HBox
import javafx.scene.layout.Priority import javafx.scene.layout.Priority
import javafx.scene.paint.Color import javafx.scene.paint.Color
import javafx.scene.text.Text import javafx.scene.text.Text
import org.controlsfx.glyphfont.Glyph
import tornadofx.* import tornadofx.*
/** /**
@ -29,8 +31,9 @@ class ConfigEditor(
val allowNew: Boolean = true, val allowNew: Boolean = true,
title: String = "Configuration editor" title: String = "Configuration editor"
) : Fragment(title = title, icon = dfIconView) { ) : 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) this(FXMeta.root(config, descriptor = descriptor), title = title)
override val root = borderpane { override val root = borderpane {
@ -133,8 +136,9 @@ class ConfigEditor(
is FXMetaNode<Config> -> { is FXMetaNode<Config> -> {
if (allowNew) { if (allowNew) {
text = null text = null
graphic = hbox { graphic = HBox().apply {
button("node", Glyph("FontAwesome", "PLUS_CIRCLE")) { val glyph: Node = FontAwesomeIconView(FontAwesomeIcon.PLUS_CIRCLE)
button("node", graphic = glyph) {
hgrow = Priority.ALWAYS hgrow = Priority.ALWAYS
maxWidth = Double.POSITIVE_INFINITY maxWidth = Double.POSITIVE_INFINITY
action { action {
@ -143,7 +147,7 @@ class ConfigEditor(
} }
} }
} }
button("value", Glyph("FontAwesome", "PLUS_SQUARE")) { button("value", graphic = FontAwesomeIconView(FontAwesomeIcon.PLUS_SQUARE)) {
hgrow = Priority.ALWAYS hgrow = Priority.ALWAYS
maxWidth = Double.POSITIVE_INFINITY maxWidth = Double.POSITIVE_INFINITY
action { action {

View File

@ -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.ItemDescriptor
import hep.dataforge.descriptors.NodeDescriptor import hep.dataforge.descriptors.NodeDescriptor
@ -84,7 +84,7 @@ class FXMetaNode<M : MetaNode<M>>(
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null } override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
private val filter: (FXMeta<M>) -> Boolean = { cfg -> 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>>() { 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 { private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
return if (append && token.index.isNotEmpty()) { return if (append && token.index.isNotEmpty()) {
val name = token.asName() 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()) val newName = name.withIndex(index.toString())
set(newName, EmptyMeta) set(newName, EmptyMeta)
get(newName).node!! get(newName).node!!

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package hep.dataforge.vis.fx.meta package hep.dataforge.vis.fx.editor
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.vis.fx.dfIconView import hep.dataforge.vis.fx.dfIconView

View File

@ -3,9 +3,11 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * 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.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.* import hep.dataforge.values.*
import javafx.beans.value.ObservableValue import javafx.beans.value.ObservableValue
import javafx.scene.control.TextField import javafx.scene.control.TextField
@ -100,7 +102,8 @@ class TextValueChooser : ValueChooserBase<TextField>() {
} }
companion object : ValueChooser.Factory { companion object : ValueChooser.Factory {
override val name: String = "text" override val name: Name = "text".asName()
override fun invoke(meta: Meta): ValueChooser = TextValueChooser() override fun invoke(meta: Meta): ValueChooser =
TextValueChooser()
} }
} }

View File

@ -3,7 +3,7 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * and open the template in the editor.
*/ */
package hep.dataforge.vis.fx.values package hep.dataforge.vis.fx.editor
import hep.dataforge.values.Value import hep.dataforge.values.Value

View File

@ -3,13 +3,14 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * 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.Context
import hep.dataforge.context.Named import hep.dataforge.context.Named
import hep.dataforge.descriptors.ValueDescriptor import hep.dataforge.descriptors.ValueDescriptor
import hep.dataforge.meta.EmptyMeta import hep.dataforge.meta.EmptyMeta
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.names.toName
import hep.dataforge.provider.Type import hep.dataforge.provider.Type
import hep.dataforge.provider.provideByType import hep.dataforge.provider.provideByType
import hep.dataforge.values.Null import hep.dataforge.values.Null
@ -71,7 +72,7 @@ interface ValueChooser {
companion object { companion object {
private fun findWidgetByType(context: Context, type: String): Factory? { private fun findWidgetByType(context: Context, type: String): Factory? {
return when (type) { return when (type.toName()) {
TextValueChooser.name -> TextValueChooser TextValueChooser.name -> TextValueChooser
ColorValueChooser.name -> ColorValueChooser ColorValueChooser.name -> ColorValueChooser
ComboBoxValueChooser.name -> ComboBoxValueChooser ComboBoxValueChooser.name -> ComboBoxValueChooser
@ -86,7 +87,10 @@ interface ValueChooser {
val widgetType = descriptor.widgetType val widgetType = descriptor.widgetType
val chooser: ValueChooser = when { val chooser: ValueChooser = when {
widgetType != null -> { widgetType != null -> {
findWidgetByType(context, widgetType)?.invoke( findWidgetByType(
context,
widgetType
)?.invoke(
descriptor.widget descriptor.widget
) ?: TextValueChooser() ) ?: TextValueChooser()
} }
@ -104,7 +108,8 @@ interface ValueChooser {
descriptor: ValueDescriptor? = null, descriptor: ValueDescriptor? = null,
setter: (Value) -> Unit setter: (Value) -> Unit
): ValueChooser { ): ValueChooser {
val chooser = build(context, descriptor) val chooser =
build(context, descriptor)
chooser.setDisplayValue(value.value ?: Null) chooser.setDisplayValue(value.value ?: Null)
value.onChange { value.onChange {
chooser.setDisplayValue(it ?: Null) chooser.setDisplayValue(it ?: Null)
@ -114,7 +119,11 @@ interface ValueChooser {
setter(result) setter(result)
ValueCallbackResponse(true, result, "OK") ValueCallbackResponse(true, result, "OK")
} else { } else {
ValueCallbackResponse(false, value.value ?: Null, "Not allowed") ValueCallbackResponse(
false,
value.value ?: Null,
"Not allowed"
)
} }
} }
return chooser return chooser

View File

@ -3,7 +3,7 @@
* To change this template file, choose Tools | Templates * To change this template file, choose Tools | Templates
* and open the template in the editor. * 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.descriptors.ValueDescriptor
import hep.dataforge.values.Null import hep.dataforge.values.Null

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -4,9 +4,9 @@ import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.toConfig import hep.dataforge.meta.toConfig
import hep.dataforge.values.ValueType import hep.dataforge.values.ValueType
import hep.dataforge.vis.fx.meta.ConfigEditor import hep.dataforge.vis.fx.editor.ConfigEditor
import hep.dataforge.vis.fx.meta.FXMeta import hep.dataforge.vis.fx.editor.FXMeta
import hep.dataforge.vis.fx.meta.MetaViewer import hep.dataforge.vis.fx.editor.MetaViewer
import javafx.geometry.Orientation import javafx.geometry.Orientation
import tornadofx.* import tornadofx.*
@ -16,16 +16,16 @@ class MetaEditorDemoApp : App(MetaEditorDemo::class)
class MetaEditorDemo : View("Meta editor demo") { class MetaEditorDemo : View("Meta editor demo") {
val meta = buildMeta { val meta = buildMeta {
"aNode" to { "aNode" put {
"innerNode" to { "innerNode" put {
"innerValue" to true "innerValue" put true
} }
"b" to 223 "b" put 223
"c" to "StringValue" "c" put "StringValue"
} }
}.toConfig() }.toConfig()
val descriptor = NodeDescriptor.build { val descriptor = NodeDescriptor {
node("aNode") { node("aNode") {
info = "A root demo node" info = "A root demo node"
value("b") { value("b") {

View File

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

View File

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

View File

@ -15,37 +15,37 @@ class JSRootGeometry(parent: VisualObject?, meta: Meta) : DisplayLeaf(parent, me
var facesLimit by int(0) var facesLimit by int(0)
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta { fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
"_typename" to "TGeoBBox" "_typename" put "TGeoBBox"
"fDX" to xSize "fDX" put xSize
"fDY" to ySize "fDY" put ySize
"fDZ" to zSize "fDZ" put zSize
} }
/** /**
* Create a GDML union * Create a GDML union
*/ */
operator fun Meta.plus(other: Meta) = buildMeta { operator fun Meta.plus(other: Meta) = buildMeta {
"fNode.fLeft" to this "fNode.fLeft" put this
"fNode.fRight" to other "fNode.fRight" put other
"fNode._typename" to "TGeoUnion" "fNode._typename" put "TGeoUnion"
} }
/** /**
* Create a GDML subtraction * Create a GDML subtraction
*/ */
operator fun Meta.minus(other: Meta) = buildMeta { operator fun Meta.minus(other: Meta) = buildMeta {
"fNode.fLeft" to this "fNode.fLeft" put this
"fNode.fRight" to other "fNode.fRight" put other
"fNode._typename" to "TGeoSubtraction" "fNode._typename" put "TGeoSubtraction"
} }
/** /**
* Intersect two GDML geometries * Intersect two GDML geometries
*/ */
infix fun Meta.intersect(other: Meta) = buildMeta { infix fun Meta.intersect(other: Meta) = buildMeta {
"fNode.fLeft" to this "fNode.fLeft" put this
"fNode.fRight" to other "fNode.fRight" put other
"fNode._typename" to "TGeoIntersection" "fNode._typename" put "TGeoIntersection"
} }
companion object { companion object {

View File

@ -7,13 +7,14 @@ kotlin {
val commonMain by getting { val commonMain by getting {
dependencies { dependencies {
api(project(":dataforge-vis-spatial")) api(project(":dataforge-vis-spatial"))
api("scientifik:gdml:0.1.4-dev-1") api("scientifik:gdml:0.1.4")
}
}
val jsMain by getting {
dependencies {
api(project(":dataforge-vis-spatial"))
} }
} }
} }
} }
//tasks{
// val jsBrowserWebpack by getting(KotlinWebpack::class) {
// sourceMaps = false
// }
//}

View File

@ -1,20 +1,21 @@
package hep.dataforge.vis.spatial.gdml package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.Meta import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.builder import hep.dataforge.names.Name
import hep.dataforge.vis.spatial.VisualGroup3D import hep.dataforge.names.toName
import scientifik.gdml.GDML import hep.dataforge.vis.common.useStyle
import scientifik.gdml.GDMLGroup import hep.dataforge.vis.spatial.*
import scientifik.gdml.GDMLMaterial import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import scientifik.gdml.GDMLSolid import scientifik.gdml.*
import kotlin.random.Random import kotlin.random.Random
class GDMLTransformer(val root: GDML) { class GDMLTransformer(val root: GDML) {
private val materialCache = HashMap<GDMLMaterial, Meta>() //private val materialCache = HashMap<GDMLMaterial, Meta>()
private val random = Random(111) private val random = Random(222)
enum class Action{ enum class Action {
ACCEPT, ACCEPT,
REJECT, REJECT,
CACHE CACHE
@ -23,45 +24,60 @@ class GDMLTransformer(val root: GDML) {
/** /**
* A special group for local templates * 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 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 solidAction: (GDMLSolid) -> Action = { Action.CACHE }
var volumeAction: (GDMLGroup) -> Action = { Action.ACCEPT } var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
fun printStatistics() {
println("Solids:") var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { parent, _ ->
solidCounter.entries.sortedByDescending { it.value }.forEach { lUnit = LUnit.CM
println("\t$it") 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>() fun VisualObject3D.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
styleCache.getOrPut(name.toName()) {
internal fun solidAdded(solid: GDMLSolid) { buildMeta(builder)
solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
} }
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 = {} var onFinish: GDMLTransformer.() -> Unit = {}
internal fun finished(final: VisualGroup3D) { internal fun finalize(final: VisualGroup3D): VisualGroup3D {
final.templates = templates final.prototypes = proto
styleCache.forEach {
final.styleSheet {
define(it.key.toString(), it.value)
}
}
final.rotationOrder = RotationOrder.ZXY
onFinish(this@GDMLTransformer) onFinish(this@GDMLTransformer)
return final
} }
} }

View File

@ -1,9 +1,13 @@
package hep.dataforge.vis.spatial.gdml 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.names.plus
import hep.dataforge.vis.common.asName import hep.dataforge.vis.common.get
import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.World.ONE
import hep.dataforge.vis.spatial.World.ZERO
import scientifik.gdml.* import scientifik.gdml.*
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
@ -11,41 +15,45 @@ import kotlin.math.sin
private fun VisualObject3D.withPosition( private fun VisualObject3D.withPosition(
lUnit: LUnit, lUnit: LUnit,
pos: GDMLPosition? = null, newPos: GDMLPosition? = null,
rotation: GDMLRotation? = null, newRotation: GDMLRotation? = null,
scale: GDMLScale? = null newScale: GDMLScale? = null
): VisualObject3D = apply { ): VisualObject3D = apply {
pos?.let { newPos?.let {
this@withPosition.x = pos.x(lUnit) val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit))
this@withPosition.y = pos.y(lUnit) if (position != null || point != ZERO) {
this@withPosition.z = pos.z(lUnit) position = point
} }
rotation?.let {
this@withPosition.rotationX = rotation.x()
this@withPosition.rotationY = rotation.y()
this@withPosition.rotationZ = rotation.z()
this@withPosition.rotationOrder=RotationOrder.ZXY
} }
scale?.let { newRotation?.let {
this@withPosition.scaleX = scale.x.toFloat() val point = Point3D(it.x(), it.y(), it.z())
this@withPosition.scaleY = scale.y.toFloat() if (rotation != null || point != ZERO) {
this@withPosition.scaleZ = scale.z.toFloat() rotation = point
}
//this@withPosition.rotationOrder = RotationOrder.ZXY
}
newScale?.let {
val point = Point3D(it.x, it.y, it.z)
if (scale != null || point != ONE) {
scale = point
}
} }
//TODO convert units if needed //TODO convert units if needed
} }
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline operator fun Number.times(d: Double) = toDouble() * d private inline operator fun Number.times(d: Double) = toDouble() * d
@Suppress("NOTHING_TO_INLINE") @Suppress("NOTHING_TO_INLINE")
private inline operator fun Number.times(f: Float) = toFloat() * f private inline operator fun Number.times(f: Float) = toFloat() * f
private fun VisualGroup3D.addSolid( private fun VisualGroup3D.addSolid(
context: GDMLTransformer, context: GDMLTransformer,
solid: GDMLSolid, solid: GDMLSolid,
name: String? = null, name: String = "",
block: VisualObject3D.() -> Unit = {} block: VisualObject3D.() -> Unit = {}
): VisualObject3D { ): VisualObject3D {
context.solidAdded(solid) //context.solidAdded(solid)
val lScale = solid.lscale(context.lUnit) val lScale = solid.lscale(context.lUnit)
val aScale = solid.ascale() val aScale = solid.ascale()
return when (solid) { return when (solid) {
@ -78,7 +86,7 @@ private fun VisualGroup3D.addSolid(
val innerSolid = solid.solidref.resolve(context.root) val innerSolid = solid.solidref.resolve(context.root)
?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined") ?: error("Solid with tag ${solid.solidref.ref} for scaled solid ${solid.name} not defined")
addSolid(context, innerSolid) { addSolid(context, innerSolid, name) {
block() block()
scaleX *= solid.scale.x.toFloat() scaleX *= solid.scale.x.toFloat()
scaleY *= solid.scale.y.toFloat() scaleY *= solid.scale.y.toFloat()
@ -147,7 +155,8 @@ private fun VisualGroup3D.addPhysicalVolume(
when (context.volumeAction(volume)) { when (context.volumeAction(volume)) {
GDMLTransformer.Action.ACCEPT -> { GDMLTransformer.Action.ACCEPT -> {
this[physVolume.name] = volume(context, volume).apply { val group = volume(context, volume)
this[physVolume.name ?: ""] = group.apply {
withPosition( withPosition(
context.lUnit, context.lUnit,
physVolume.resolvePosition(context.root), physVolume.resolvePosition(context.root),
@ -157,12 +166,12 @@ private fun VisualGroup3D.addPhysicalVolume(
} }
} }
GDMLTransformer.Action.CACHE -> { GDMLTransformer.Action.CACHE -> {
val name = volumesName + volume.name.asName() val fullName = volumesName + volume.name.asName()
if (context.templates[name] == null) { if (context.proto[fullName] == null) {
context.templates[name] = volume(context, volume) context.proto[fullName] = volume(context, volume)
} }
this[physVolume.name] = Proxy(name).apply { this[physVolume.name ?: ""] = Proxy(fullName).apply {
withPosition( withPosition(
context.lUnit, context.lUnit,
physVolume.resolvePosition(context.root), physVolume.resolvePosition(context.root),
@ -185,7 +194,8 @@ private fun VisualGroup3D.addDivisionVolume(
?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved") ?: error("Volume with ref ${divisionVolume.volumeref.ref} could not be resolved")
//TODO add divisions //TODO add divisions
add( set(
Name.EMPTY,
volume( volume(
context, context,
volume volume
@ -193,12 +203,7 @@ private fun VisualGroup3D.addDivisionVolume(
) )
} }
private fun VisualGroup3D.addVolume( private val solidsName = "solids".asName()
context: GDMLTransformer,
group: GDMLGroup
) {
this[group.name] = volume(context, group)
}
private fun volume( private fun volume(
context: GDMLTransformer, context: GDMLTransformer,
@ -208,18 +213,17 @@ private fun volume(
if (group is GDMLVolume) { if (group is GDMLVolume) {
val solid = group.solidref.resolve(context.root) val solid = group.solidref.resolve(context.root)
?: error("Solid with tag ${group.solidref.ref} for volume ${group.name} not defined") ?: 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)) { when (context.solidAction(solid)) {
GDMLTransformer.Action.ACCEPT -> { GDMLTransformer.Action.ACCEPT -> {
addSolid(context, solid, solid.name) { addSolid(context, solid, solid.name) {
this.material = context.resolveColor(group, material, solid) context.configureSolid(this, group, solid)
} }
} }
GDMLTransformer.Action.CACHE -> { GDMLTransformer.Action.CACHE -> {
if (context.templates[solid.name] == null) { if (context.proto[solid.name] == null) {
context.templates.addSolid(context, solid, solid.name) { context.proto.addSolid(context, solid, solid.name) {
this.material = context.resolveColor(group, material, solid) context.configureSolid(this, group, solid)
} }
} }
ref(solid.name.asName(), solid.name) 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 { fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
val context = GDMLTransformer(this).apply(block) val context = GDMLTransformer(this).apply(block)
return volume(context, world).also { return context.finalize(volume(context, world))
context.finished(it) }
}
/**
* 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)
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,10 +5,12 @@ import org.junit.Test
import scientifik.gdml.GDML import scientifik.gdml.GDML
import java.io.File import java.io.File
import java.net.URL import java.net.URL
import kotlin.test.Ignore
class TestConvertor { class TestConvertor {
@Test @Test
@Ignore
fun testBMNGeometry() { fun testBMNGeometry() {
val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO") val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml") val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N.gdml")
@ -24,6 +26,7 @@ class TestConvertor {
} }
@Test @Test
@Ignore
fun testCubes() { fun testCubes() {
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml ") val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml ")
val stream = if (file.exists()) { val stream = if (file.exists()) {

View File

@ -1,16 +1,15 @@
import org.openjfx.gradle.JavaFXOptions import org.openjfx.gradle.JavaFXOptions
import scientifik.useSerialization
plugins { plugins {
id("scientifik.mpp") id("scientifik.mpp")
id("org.openjfx.javafxplugin") id("org.openjfx.javafxplugin")
} }
scientifik{ useSerialization()
serialization = true
}
kotlin { kotlin {
jvm{ jvm {
withJava() withJava()
} }
sourceSets { sourceSets {
@ -19,19 +18,22 @@ kotlin {
api(project(":dataforge-vis-common")) api(project(":dataforge-vis-common"))
} }
} }
jvmMain{ jvmMain {
dependencies { dependencies {
implementation(project(":dataforge-vis-fx")) implementation("org.fxyz3d:fxyz3d:0.5.2") {
implementation("org.fxyz3d:fxyz3d:0.4.0") 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 { dependencies {
api("info.laht.threekt:threejs-wrapper:0.106-npm-3") // api(project(":wrappers"))
implementation(npm("three", "0.106.2")) implementation(npm("three", "0.106.2"))
implementation(npm("@hi-level/three-csg", "1.0.6")) implementation(npm("@hi-level/three-csg", "1.0.6"))
implementation(npm("style-loader"))
implementation(npm("element-resize-event"))
} }
} }
} }

View File

@ -2,17 +2,22 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.context.Context import hep.dataforge.context.Context
import hep.dataforge.io.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.* 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.AbstractVisualObject
import hep.dataforge.vis.common.VisualFactory import hep.dataforge.vis.common.VisualFactory
import hep.dataforge.vis.common.VisualObject import hep.dataforge.vis.common.VisualObject
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.reflect.KClass import kotlin.reflect.KClass
@Serializable @Serializable
data class Box( @SerialName("3d.box")
class Box(
val xSize: Float, val xSize: Float,
val ySize: Float, val ySize: Float,
val zSize: Float val zSize: Float
@ -27,9 +32,9 @@ data class Box(
//TODO add helper for color configuration //TODO add helper for color configuration
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val dx = xSize.toFloat() / 2 val dx = xSize / 2
val dy = ySize.toFloat() / 2 val dy = ySize / 2
val dz = zSize.toFloat() / 2 val dz = zSize / 2
val node1 = Point3D(-dx, -dy, -dz) val node1 = Point3D(-dx, -dy, -dz)
val node2 = Point3D(dx, -dy, -dz) val node2 = Point3D(dx, -dy, -dz)
val node3 = Point3D(dx, dy, -dz) val node3 = Point3D(dx, dy, -dz)
@ -46,17 +51,6 @@ data class Box(
geometryBuilder.face4(node8, node5, node6, node7) 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> { companion object : VisualFactory<Box> {
const val TYPE = "geometry.3d.box" const val TYPE = "geometry.3d.box"
@ -76,6 +70,6 @@ inline fun VisualGroup3D.box(
xSize: Number, xSize: Number,
ySize: Number, ySize: Number,
zSize: Number, zSize: Number,
name: String? = null, name: String = "",
action: Box.() -> Unit = {} action: Box.() -> Unit = {}
) = Box(xSize.toFloat(), ySize.toFloat(), zSize.toFloat()).apply(action).also { set(name, it) } ) = Box(xSize.toFloat(), ySize.toFloat(), zSize.toFloat()).apply(action).also { set(name, it) }

View File

@ -1,9 +1,9 @@
@file:UseSerializers(Point3DSerializer::class) @file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial 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.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.update import hep.dataforge.meta.update
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -22,46 +22,49 @@ class Composite(
val second: VisualObject3D val second: VisualObject3D
) : AbstractVisualObject(), VisualObject3D { ) : AbstractVisualObject(), VisualObject3D {
init {
first.parent = this
second.parent = this
}
override var position: Point3D? = null override var position: Point3D? = null
override var rotation: Point3D? = null override var rotation: Point3D? = null
override var scale: Point3D? = null override var scale: Point3D? = null
@Serializable(ConfigSerializer::class) @Serializable(ConfigSerializer::class)
override var properties: Config? = null 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( inline fun VisualGroup3D.composite(
type: CompositeType, type: CompositeType,
name: String? = null, name: String = "",
builder: VisualGroup3D.() -> Unit builder: VisualGroup3D.() -> Unit
): Composite { ): Composite {
val group = VisualGroup3D().apply(builder) val group = VisualGroup3D().apply(builder)
val children = group.filterIsInstance<VisualObject3D>() val children = group.filterIsInstance<VisualObject3D>()
if (children.size != 2) error("Composite requires exactly two children") if (children.size != 2) error("Composite requires exactly two children")
return Composite(type, children[0], children[1]).also { return Composite(type, children[0], children[1]).also {
if (group.properties != null) {
it.config.update(group.config) it.config.update(group.config)
it.material = group.material //it.material = group.material
}
if(group.position!=null) {
it.position = group.position it.position = group.position
}
if(group.rotation!=null) {
it.rotation = group.rotation it.rotation = group.rotation
}
if(group.scale!=null) {
it.scale = group.scale it.scale = group.scale
}
set(name, it) 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) 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) 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) composite(CompositeType.INTERSECT, name, builder = builder)

View File

@ -2,11 +2,13 @@
package hep.dataforge.vis.spatial 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.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.math.cos
import kotlin.math.sin
/** /**
* A cylinder or cut cone segment * A cylinder or cut cone segment
@ -18,7 +20,7 @@ class ConeSegment(
var upperRadius: Float, var upperRadius: Float,
var startAngle: Float = 0f, var startAngle: Float = 0f,
var angle: Float = PI2 var angle: Float = PI2
) : AbstractVisualObject(), VisualObject3D { ) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class) @Serializable(ConfigSerializer::class)
override var properties: Config? = null override var properties: Config? = null
@ -26,12 +28,55 @@ class ConeSegment(
override var position: Point3D? = null override var position: Point3D? = null
override var rotation: Point3D? = null override var rotation: Point3D? = null
override var scale: 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( inline fun VisualGroup3D.cylinder(
r: Number, r: Number,
height: Number, height: Number,
name: String? = null, name: String = "",
block: ConeSegment.() -> Unit = {} block: ConeSegment.() -> Unit = {}
): ConeSegment = ConeSegment( ): ConeSegment = ConeSegment(
r.toFloat(), r.toFloat(),

View File

@ -2,9 +2,8 @@
package hep.dataforge.vis.spatial 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.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
@ -19,19 +18,12 @@ class Convex(val points: List<Point3D>) : AbstractVisualObject(), VisualObject3D
override var rotation: Point3D? = null override var rotation: Point3D? = null
override var scale: Point3D? = null override var scale: Point3D? = null
override fun MetaBuilder.updateMeta() {
"points" to {
"point" to points.map { it.toMeta() }
}
updatePosition()
}
companion object { companion object {
const val TYPE = "geometry.3d.convex" 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) } ConvexBuilder().apply(action).build().also { set(name, it) }
class ConvexBuilder { class ConvexBuilder {

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class) @file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class)
package hep.dataforge.vis.spatial 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.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -62,7 +62,7 @@ class Extruded(
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val shape: Shape2D = shape 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 * 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) } Extruded().apply(action).also { set(name, it) }

View File

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

View File

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

View File

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

View File

@ -1,22 +1,32 @@
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class) @file:UseSerializers(Point3DSerializer::class, NameSerializer::class, ConfigSerializer::class)
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.NameSerializer import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.meta.Config import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder import hep.dataforge.meta.Laminate
import hep.dataforge.meta.MetaItem import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.get
import hep.dataforge.names.Name 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.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
/** /**
* A proxy [VisualObject3D] to reuse a template object * A proxy [VisualObject3D] to reuse a template object
*/ */
@Serializable @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 position: Point3D? = null
override var rotation: 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 * Recursively search for defined template in the parent
*/ */
val template: VisualObject3D val prototype: VisualObject3D
get() = (parent as? VisualGroup3D)?.getTemplate(templateName) get() = (parent as? VisualGroup3D)?.getPrototype(templateName)
?: error("Template with name $templateName not found in $parent") ?: 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<*>? { override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) { 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 { } else {
super.getProperty(name, false) ?: template.getProperty(name, false) properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name, false)
} }
} }
override fun MetaBuilder.updateMeta() { override val children: Map<NameToken, ProxyChild>
//TODO add reference to child get() = (prototype as? MutableVisualGroup)?.children
updatePosition() ?.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( val VisualObject.prototype: VisualObject
// templateName: Name, get() = when (this) {
// //name: String? = null, is Proxy -> prototype
// builder: VisualGroup3D.() -> Unit is Proxy.ProxyChild -> prototype
//): Proxy { else -> this
// val template = getTemplate(templateName) ?: templates.builder() }
// return Proxy(this, templateName).also { set(name, it) }
//}
/**
* Create ref for existing prototype
*/
inline fun VisualGroup3D.ref( inline fun VisualGroup3D.ref(
templateName: Name, templateName: Name,
name: String? = null, name: String = "",
action: Proxy.() -> Unit = {} block: Proxy.() -> Unit = {}
) = Proxy(templateName).apply(action).also { set(name, it) } ) = 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)
}

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial 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.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -16,7 +16,7 @@ class Sphere(
var phi: Float = PI2, var phi: Float = PI2,
var thetaStart: Float = 0f, var thetaStart: Float = 0f,
var theta: Float = PI.toFloat() var theta: Float = PI.toFloat()
) : AbstractVisualObject(), VisualObject3D { ) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class) @Serializable(ConfigSerializer::class)
override var properties: Config? = null override var properties: Config? = null
@ -24,13 +24,26 @@ class Sphere(
override var position: Point3D? = null override var position: Point3D? = null
override var rotation: Point3D? = null override var rotation: Point3D? = null
override var scale: 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( inline fun VisualGroup3D.sphere(
radius: Number, radius: Number,
phi: Number = 2 * PI, phi: Number = 2 * PI,
theta: Number = PI, theta: Number = PI,
name: String? = null, name: String = "",
action: Sphere.() -> Unit = {} action: Sphere.() -> Unit = {}
) = Sphere( ) = Sphere(
radius.toFloat(), radius.toFloat(),

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point3DSerializer::class) @file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial 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.Config
import hep.dataforge.vis.common.AbstractVisualObject import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -123,6 +123,7 @@ class Tube(
} }
} }
} }
} }
inline fun VisualGroup3D.tube( inline fun VisualGroup3D.tube(
@ -131,7 +132,7 @@ inline fun VisualGroup3D.tube(
innerRadius: Number = 0f, innerRadius: Number = 0f,
startAngle: Number = 0f, startAngle: Number = 0f,
angle: Number = 2 * PI, angle: Number = 2 * PI,
name: String? = null, name: String = "",
block: Tube.() -> Unit = {} block: Tube.() -> Unit = {}
): Tube = Tube( ): Tube = Tube(
r.toFloat(), r.toFloat(),

View File

@ -1,14 +1,20 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.context.AbstractPlugin import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.Context
import hep.dataforge.context.PluginFactory import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag 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.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.Name
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.VisualPlugin import hep.dataforge.vis.common.VisualPlugin
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonConfiguration import kotlinx.serialization.json.JsonConfiguration
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.contextual
import kotlin.reflect.KClass import kotlin.reflect.KClass
class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) { class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
@ -25,16 +31,26 @@ class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
companion object : PluginFactory<Visual3DPlugin> { companion object : PluginFactory<Visual3DPlugin> {
override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag(name = "visual.spatial", group = PluginTag.DATAFORGE_GROUP)
override val type: KClass<out Visual3DPlugin> = Visual3DPlugin::class 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 { 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() VisualGroup3D::class with VisualGroup3D.serializer()
Proxy::class with Proxy.serializer() Proxy::class with Proxy.serializer()
Composite::class with Composite.serializer() Composite::class with Composite.serializer()
Tube::class with Tube.serializer() Tube::class with Tube.serializer()
Box::class with Box.serializer() Box::class with Box.serializer()
Convex::class with Convex.serializer() Convex::class with Convex.serializer()
Extruded::class with Extruded.serializer()
addSubclass(PolyLine.serializer())
addSubclass(Label3D.serializer())
} }
} }

View File

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

View File

@ -1,22 +1,22 @@
@file:UseSerializers(Point3DSerializer::class, NameSerializer::class) @file:UseSerializers(Point3DSerializer::class, NameSerializer::class, NameTokenSerializer::class)
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.io.NameSerializer
import hep.dataforge.meta.* import hep.dataforge.meta.*
import hep.dataforge.names.Name import hep.dataforge.names.asName
import hep.dataforge.names.plus import hep.dataforge.names.plus
import hep.dataforge.output.Output import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject 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.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 hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers import kotlinx.serialization.UseSerializers
/**
* Interface for 3-dimensional [VisualObject]
*/
interface VisualObject3D : VisualObject { interface VisualObject3D : VisualObject {
var position: Point3D? var position: Point3D?
var rotation: Point3D? var rotation: Point3D?
@ -35,9 +35,14 @@ interface VisualObject3D : VisualObject {
} }
companion object { companion object {
val MATERIAL_KEY = "material".asName()
val VISIBLE_KEY = "visible".asName() val VISIBLE_KEY = "visible".asName()
val SELECTED_KEY = "selected".asName()
val DETAIL_KEY = "detail".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 x = "x".asName()
val y = "y".asName() val y = "y".asName()
@ -65,44 +70,16 @@ interface VisualObject3D : VisualObject {
} }
} }
@Serializable /**
class VisualGroup3D : VisualGroup<VisualObject3D>(), VisualObject3D, Configurable { * Count number of layers to the top object. Return 1 if this is top layer
/**
* A container for templates visible inside this group
*/ */
var templates: VisualGroup3D? = null var VisualObject3D.layer: Int
get() = getProperty(LAYER_KEY).int ?: 0
set(value) { set(value) {
value?.parent = this setProperty(LAYER_KEY, value)
field = value
} }
@Serializable(ConfigSerializer::class) fun Renderer<VisualObject3D>.render(meta: Meta = EmptyMeta, action: VisualGroup3D.() -> Unit) =
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()
}
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) =
render(VisualGroup3D().apply(action), meta) render(VisualGroup3D().apply(action), meta)
// Common properties // Common properties
@ -131,27 +108,21 @@ var VisualObject3D.detail: Int?
get() = getProperty(DETAIL_KEY, false).int get() = getProperty(DETAIL_KEY, false).int
set(value) = setProperty(DETAIL_KEY, value) set(value) = setProperty(DETAIL_KEY, value)
var VisualObject3D.material: Meta? var VisualObject.visible: Boolean?
get() = getProperty(MATERIAL_KEY).node
set(value) = setProperty(MATERIAL_KEY, value)
var VisualObject3D.visible: Boolean?
get() = getProperty(VISIBLE_KEY).boolean get() = getProperty(VISIBLE_KEY).boolean
set(value) = setProperty(VISIBLE_KEY, value) 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) { //var VisualObject.selected: Boolean?
material = buildMeta(builder) // get() = getProperty(SELECTED_KEY).boolean
} // set(value) = setProperty(SELECTED_KEY, value)
fun VisualObject3D.color(r: Int, g: Int, b: Int) = material {
"red" to r
"green" to g
"blue" to b
}
private fun VisualObject3D.position(): Point3D = private fun VisualObject3D.position(): Point3D =
position ?: Point3D(0.0, 0.0, 0.0).also { position = it } position ?: Point3D(0.0, 0.0, 0.0).also { position = it }
@ -160,21 +131,21 @@ var VisualObject3D.x: Number
get() = position?.x ?: 0f get() = position?.x ?: 0f
set(value) { set(value) {
position().x = value.toDouble() position().x = value.toDouble()
propertyChanged(VisualObject3D.xPos) propertyInvalidated(VisualObject3D.xPos)
} }
var VisualObject3D.y: Number var VisualObject3D.y: Number
get() = position?.y ?: 0f get() = position?.y ?: 0f
set(value) { set(value) {
position().y = value.toDouble() position().y = value.toDouble()
propertyChanged(VisualObject3D.yPos) propertyInvalidated(VisualObject3D.yPos)
} }
var VisualObject3D.z: Number var VisualObject3D.z: Number
get() = position?.z ?: 0f get() = position?.z ?: 0f
set(value) { set(value) {
position().z = value.toDouble() position().z = value.toDouble()
propertyChanged(VisualObject3D.zPos) propertyInvalidated(VisualObject3D.zPos)
} }
private fun VisualObject3D.rotation(): Point3D = private fun VisualObject3D.rotation(): Point3D =
@ -184,21 +155,21 @@ var VisualObject3D.rotationX: Number
get() = rotation?.x ?: 0f get() = rotation?.x ?: 0f
set(value) { set(value) {
rotation().x = value.toDouble() rotation().x = value.toDouble()
propertyChanged(VisualObject3D.xRotation) propertyInvalidated(VisualObject3D.xRotation)
} }
var VisualObject3D.rotationY: Number var VisualObject3D.rotationY: Number
get() = rotation?.y ?: 0f get() = rotation?.y ?: 0f
set(value) { set(value) {
rotation().y = value.toDouble() rotation().y = value.toDouble()
propertyChanged(VisualObject3D.yRotation) propertyInvalidated(VisualObject3D.yRotation)
} }
var VisualObject3D.rotationZ: Number var VisualObject3D.rotationZ: Number
get() = rotation?.z ?: 0f get() = rotation?.z ?: 0f
set(value) { set(value) {
rotation().z = value.toDouble() rotation().z = value.toDouble()
propertyChanged(VisualObject3D.zRotation) propertyInvalidated(VisualObject3D.zRotation)
} }
private fun VisualObject3D.scale(): Point3D = private fun VisualObject3D.scale(): Point3D =
@ -208,19 +179,19 @@ var VisualObject3D.scaleX: Number
get() = scale?.x ?: 1f get() = scale?.x ?: 1f
set(value) { set(value) {
scale().x = value.toDouble() scale().x = value.toDouble()
propertyChanged(VisualObject3D.xScale) propertyInvalidated(VisualObject3D.xScale)
} }
var VisualObject3D.scaleY: Number var VisualObject3D.scaleY: Number
get() = scale?.y ?: 1f get() = scale?.y ?: 1f
set(value) { set(value) {
scale().y = value.toDouble() scale().y = value.toDouble()
propertyChanged(VisualObject3D.yScale) propertyInvalidated(VisualObject3D.yScale)
} }
var VisualObject3D.scaleZ: Number var VisualObject3D.scaleZ: Number
get() = scale?.z ?: 1f get() = scale?.z ?: 1f
set(value) { set(value) {
scale().z = value.toDouble() scale().z = value.toDouble()
propertyChanged(VisualObject3D.zScale) propertyInvalidated(VisualObject3D.zScale)
} }

View File

@ -3,12 +3,8 @@ package hep.dataforge.vis.spatial
import kotlin.math.PI import kotlin.math.PI
object World { object World {
const val CAMERA_INITIAL_DISTANCE = -500.0 val ZERO = Point3D(0.0, 0.0, 0.0)
const val CAMERA_INITIAL_X_ANGLE = -50.0 val ONE = Point3D(1.0, 1.0, 1.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
} }
const val PI2: Float = 2 * PI.toFloat() const val PI2: Float = 2 * PI.toFloat()

View File

@ -14,8 +14,8 @@ operator fun Point2D.component1() = x
operator fun Point2D.component2() = y operator fun Point2D.component2() = y
fun Point2D.toMeta() = buildMeta { fun Point2D.toMeta() = buildMeta {
VisualObject3D.x to x VisualObject3D.x put x
VisualObject3D.y to y VisualObject3D.y put y
} }
fun Meta.point2D() = Point2D(this["x"].number ?: 0, this["y"].number ?: 0) 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 var z: Double
} }
expect operator fun Point3D.plus(other: Point3D): Point3D
operator fun Point3D.component1() = x operator fun Point3D.component1() = x
operator fun Point3D.component2() = y operator fun Point3D.component2() = y
operator fun Point3D.component3() = z operator fun Point3D.component3() = z
fun Meta.point3D() = Point3D(this["x"].number ?: 0, this["y"].number ?: 0, this["y"].number ?: 0) 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 { fun Point3D.toMeta() = buildMeta {
VisualObject3D.x to x VisualObject3D.x put x
VisualObject3D.y to y VisualObject3D.y put y
VisualObject3D.z to z VisualObject3D.z put z
} }

View File

@ -1,41 +1,109 @@
package hep.dataforge.vis.spatial 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.*
import kotlinx.serialization.internal.DoubleSerializer
import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.internal.nullable
@Serializable inline fun <R> Decoder.decodeStructure(
private data class Point2DSerial(val x: Double, val y: Double) 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 inline fun Encoder.encodeStructure(
private data class Point3DSerial(val x: Double, val y: Double, val z: Double) desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
block: CompositeEncoder.() -> Unit
) {
val encoder = beginStructure(desc, *typeParams)
encoder.block()
encoder.endStructure(desc)
}
@Serializer(Point3D::class) @Serializer(Point3D::class)
object Point3DSerializer : KSerializer<Point3D> { object Point3DSerializer : KSerializer<Point3D> {
private val serializer = Point3DSerial.serializer() override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point3D") {
override val descriptor: SerialDescriptor get() = serializer.descriptor double("x", true)
double("y", true)
double("z", true)
}
override fun deserialize(decoder: Decoder): Point3D { override fun deserialize(decoder: Decoder): Point3D {
return serializer.deserialize(decoder).let { var x: Double? = null
Point3D(it.x, it.y, it.z) 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) { 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) @Serializer(Point2D::class)
object Point2DSerializer : KSerializer<Point2D> { object Point2DSerializer : KSerializer<Point2D> {
private val serializer = Point2DSerial.serializer() override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point2D") {
override val descriptor: SerialDescriptor get() = serializer.descriptor double("x", true)
double("y", true)
}
override fun deserialize(decoder: Decoder): Point2D { override fun deserialize(decoder: Decoder): Point2D {
return serializer.deserialize(decoder).let { var x: Double? = null
Point2D(it.x, it.y) 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) { 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())
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,13 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.meta.get import hep.dataforge.io.toMeta
import hep.dataforge.meta.getAll import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.node import hep.dataforge.meta.getIndexed
import hep.dataforge.names.toName
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class ConvexTest { class ConvexTest {
@Suppress("UNUSED_VARIABLE")
@Test @Test
fun testConvexBuilder() { fun testConvexBuilder() {
val group = VisualGroup3D().apply { val group = VisualGroup3D().apply {
@ -25,12 +25,11 @@ class ConvexTest {
val convex = group.first() as Convex 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 val points = meta.getIndexed("points").values.map { (it as MetaItem.NodeItem<*>).node.point3D()}
assertEquals(8, points.count())
assertEquals(8, pointsNode?.items?.count())
val points = pointsNode?.getAll("point".toName())
assertEquals(8, convex.points.size) assertEquals(8, convex.points.size)
} }

View File

@ -1,15 +1,16 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.vis.common.Colors import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.get
import kotlin.math.PI import kotlin.math.PI
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
class GroupTest { class GroupTest {
@Test @Test
fun testGroupWithComposite(){ fun testGroupWithComposite() {
val group = VisualGroup3D().apply{ val group = VisualGroup3D().apply {
union { union("union") {
box(100, 100, 100) { box(100, 100, 100) {
z = 100 z = 100
rotationX = PI / 4 rotationX = PI / 4
@ -17,11 +18,11 @@ class GroupTest {
} }
box(100, 100, 100) box(100, 100, 100)
material { material {
"color" to Colors.lightgreen color(Colors.lightgreen)
"opacity" to 0.3 opacity = 0.3f
} }
} }
intersect{ intersect("intersect") {
box(100, 100, 100) { box(100, 100, 100) {
z = 100 z = 100
rotationX = PI / 4 rotationX = PI / 4
@ -31,7 +32,7 @@ class GroupTest {
y = 300 y = 300
color(Colors.red) color(Colors.red)
} }
subtract{ subtract("subtract") {
box(100, 100, 100) { box(100, 100, 100) {
z = 100 z = 100
rotationX = PI / 4 rotationX = PI / 4
@ -44,7 +45,7 @@ class GroupTest {
} }
assertEquals(3, group.count()) assertEquals(3, group.count())
assertEquals(300.0,group.toList()[1].y.toDouble()) assertEquals(300.0, (group["intersect"] as VisualObject3D).y.toDouble())
assertEquals(-300.0,group.toList()[2].y.toDouble()) assertEquals(-300.0, (group["subtract"] as VisualObject3D).y.toDouble())
} }
} }

View File

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

View File

@ -1,6 +1,6 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import hep.dataforge.context.Global import hep.dataforge.vis.spatial.Visual3DPlugin.Companion.json
import kotlinx.serialization.ImplicitReflectionSerializer import kotlinx.serialization.ImplicitReflectionSerializer
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
@ -8,13 +8,15 @@ import kotlin.test.assertEquals
class SerializationTest { class SerializationTest {
@ImplicitReflectionSerializer @ImplicitReflectionSerializer
@Test @Test
fun testCubeSerialization(){ fun testCubeSerialization() {
val cube = Box(100f,100f,100f).apply{ val cube = Box(100f, 100f, 100f).apply {
color(222) color(222)
x = 100
z = -100
} }
val meta = cube.toMeta() val string = json.stringify(Box.serializer(), cube)
println(meta) println(string)
val newCube = Box(Global,null, meta) val newCube = json.parse(Box.serializer(), string)
assertEquals(cube,newCube) assertEquals(cube.config, newCube.config)
} }
} }

View File

@ -1,8 +1,14 @@
package hep.dataforge.vis.spatial package hep.dataforge.vis.spatial
import info.laht.threekt.math.Vector2 import info.laht.threekt.math.Vector2
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
import info.laht.threekt.math.plus
actual typealias Point2D = Vector2 actual typealias Point2D = Vector2
actual typealias Point3D = Vector3 actual typealias Point3D = Vector3
actual operator fun Point3D.plus(other: Point3D): Point3D {
return this.plus(other)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.objects.Mesh 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) { class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) {

View File

@ -1,19 +1,15 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean import hep.dataforge.names.Name
import hep.dataforge.meta.int
import hep.dataforge.meta.node
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith import hep.dataforge.names.startsWith
import hep.dataforge.provider.Type 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.*
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE 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.BufferGeometry
import info.laht.threekt.core.Object3D 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 info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -21,9 +17,9 @@ import kotlin.reflect.KClass
* Builder and updater for three.js object * Builder and updater for three.js object
*/ */
@Type(TYPE) @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 operator fun invoke(obj: T): Object3D
@ -35,112 +31,41 @@ interface ThreeFactory<T : VisualObject3D> {
/** /**
* Update position, rotation and visibility * Update position, rotation and visibility
*/ */
internal fun Object3D.updatePosition(obj: VisualObject3D) { fun Object3D.updatePosition(obj: VisualObject3D) {
visible = obj.visible ?: true visible = obj.visible ?: true
position.set(obj.x, obj.y, obj.z) position.set(obj.x, obj.y, obj.z)
// obj.rotation?.let{
// rotateZ(it.z)
// rotateX(it.x)
// rotateY(it.y)
// }
setRotationFromEuler(obj.euler) setRotationFromEuler(obj.euler)
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ) scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
updateMatrix() updateMatrix()
} }
internal fun <T : VisualObject3D> Mesh.updateFrom(obj: T) { ///**
matrixAutoUpdate = false // * Unsafe invocation of a factory
// */
//inherited edges definition, enabled by default //operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) { // if (type.isInstance(obj)) {
val material = obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT // @Suppress("UNCHECKED_CAST")
add(LineSegments(EdgesGeometry(geometry as BufferGeometry), material)) // return invoke(obj as T)
} // } else {
// error("The object of type ${obj::class} could not be rendered by this factory")
//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 * Update non-position non-geometry property
*/ */
operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D { fun Object3D.updateProperty(source: VisualObject3D, propertyName: Name) {
if (type.isInstance(obj)) { if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
@Suppress("UNCHECKED_CAST") this.material = getMaterial(source)
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 ( } else if (
name.startsWith(VisualObject3D.position) || propertyName.startsWith(VisualObject3D.position)
name.startsWith(VisualObject3D.rotation) || || propertyName.startsWith(VisualObject3D.rotation)
name.startsWith(VisualObject3D.scale) || propertyName.startsWith(VisualObject3D.scale)
) { ) {
//update position of mesh using this object //update position of mesh using this object
mesh.updatePosition(obj) updatePosition(source)
} else if (name == VisualObject3D.VISIBLE_KEY) { } else if (propertyName == VisualObject3D.VISIBLE_KEY) {
mesh.visible = obj.visible ?: true visible = source.visible ?: true
} else {
//full update
mesh.geometry = geometryBuilder(obj)
mesh.material = obj.material.jsMaterial()
}
}
return mesh
}
} }
} }

View File

@ -10,6 +10,9 @@ import info.laht.threekt.core.Face3
import info.laht.threekt.core.Geometry import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
/**
* An implementation of geometry builder for Three.js [BufferGeometry]
*/
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> { class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
private val vertices = ArrayList<Point3D>() 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) { 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)) val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal ?: Vector3(0, 0, 0))
meta["materialIndex"].int?.let { face.materialIndex = it } meta["materialIndex"].int?.let { face.materialIndex = it }
meta["color"]?.color()?.let { face.color = it } meta["color"]?.getColor()?.let { face.color = it }
faces.add(face) faces.add(face)
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +1,14 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.context.AbstractPlugin import hep.dataforge.context.*
import hep.dataforge.context.PluginFactory import hep.dataforge.meta.Meta
import hep.dataforge.context.PluginTag import hep.dataforge.names.*
import hep.dataforge.context.content import hep.dataforge.vis.common.VisualObject
import hep.dataforge.meta.*
import hep.dataforge.vis.spatial.* 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.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.collections.set
import kotlin.reflect.KClass import kotlin.reflect.KClass
import info.laht.threekt.objects.Group as ThreeGroup
class ThreePlugin : AbstractPlugin() { class ThreePlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
@ -28,31 +23,81 @@ class ThreePlugin : AbstractPlugin() {
objectFactories[Convex::class] = ThreeConvexFactory objectFactories[Convex::class] = ThreeConvexFactory
objectFactories[Sphere::class] = ThreeSphereFactory objectFactories[Sphere::class] = ThreeSphereFactory
objectFactories[ConeSegment::class] = ThreeCylinderFactory objectFactories[ConeSegment::class] = ThreeCylinderFactory
objectFactories[PolyLine::class] = ThreeLineFactory
objectFactories[Label3D::class] = ThreeCanvasLabelFactory
} }
private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? { @Suppress("UNCHECKED_CAST")
return objectFactories[type] private fun findObjectFactory(type: KClass<out VisualObject>): ThreeFactory<VisualObject3D>? {
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type } return (objectFactories[type]
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type })
as ThreeFactory<VisualObject3D>?
} }
fun buildObject3D(obj: VisualObject3D): Object3D { fun buildObject3D(obj: VisualObject3D): Object3D {
return when (obj) { return when (obj) {
is VisualGroup3D -> Group(obj.mapNotNull { 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 { try {
buildObject3D(it) val object3D = buildObject3D(child)
group[token] = object3D
} catch (ex: Throwable) { } catch (ex: Throwable) {
console.error(ex) logger.error(ex) { "Failed to render $child" }
logger.error(ex) { "Failed to render $it" }
null
} }
}).apply { }
}
group.apply {
updatePosition(obj) 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" }
}
}
}
}
} }
is Composite -> compositeFactory(obj) is Composite -> compositeFactory(obj)
is Proxy -> proxyFactory(obj)
else -> { else -> {
//find specialized factory for this type if it is present //find specialized factory for this type if it is present
val factory = findObjectFactory(obj::class) val factory: ThreeFactory<VisualObject3D>? = findObjectFactory(obj::class)
when { when {
factory != null -> factory(obj) factory != null -> factory(obj)
obj is Shape -> ThreeShapeFactory(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> { companion object : PluginFactory<ThreePlugin> {
override val tag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP) override val tag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
override val type = ThreePlugin::class 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())
} }
} }

View File

@ -1,6 +1,8 @@
package hep.dataforge.vis.spatial.three package hep.dataforge.vis.spatial.three
import hep.dataforge.names.toName
import hep.dataforge.vis.spatial.Proxy import hep.dataforge.vis.spatial.Proxy
import hep.dataforge.vis.spatial.Proxy.Companion.PROXY_CHILD_PROPERTY_PREFIX
import hep.dataforge.vis.spatial.VisualObject3D import hep.dataforge.vis.spatial.VisualObject3D
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
@ -10,15 +12,27 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
override val type = Proxy::class override val type = Proxy::class
override fun invoke(obj: Proxy): Object3D { override fun invoke(obj: Proxy): Object3D {
val template = obj.template val template = obj.prototype
val cachedObject = cache.getOrPut(template) { val cachedObject = cache.getOrPut(template) {
three.buildObject3D(template) three.buildObject3D(template)
} }
//val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material) //val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material)
val mesh = cachedObject.clone() val object3D = cachedObject.clone()
object3D.updatePosition(obj)
mesh.updatePosition(obj) obj.onPropertyChange(this) { name, _, _ ->
return mesh 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
} }
} }

View File

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

View File

@ -16,6 +16,9 @@ import info.laht.threekt.math.Matrix4
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
/**
* Constructive Solid Geometry
*/
open external class CSG { open external class CSG {
open var polygons: Array<Polygon> open var polygons: Array<Polygon>
open fun clone(): CSG open fun clone(): CSG

View File

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

View File

@ -6,21 +6,16 @@ import hep.dataforge.meta.get
import hep.dataforge.meta.node import hep.dataforge.meta.node
import hep.dataforge.vis.spatial.* import hep.dataforge.vis.spatial.*
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.DirectGeometry
import info.laht.threekt.core.Face3 import info.laht.threekt.core.Face3
import info.laht.threekt.core.Geometry 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.Euler
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
import info.laht.threekt.objects.Mesh
/** import info.laht.threekt.textures.Texture
* Utility methods for three.kt. import kotlin.math.PI
* TODO move to three project
*/
@Suppress("FunctionName")
fun Group(children: Collection<Object3D>) = info.laht.threekt.objects.Group().apply {
children.forEach { this.add(it) }
}
val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name) 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) } fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
internal fun Double.toRadians() = this * PI / 180
fun CSG.toGeometry(): Geometry { fun CSG.toGeometry(): Geometry {
val geom = Geometry() val geom = Geometry()
@ -43,7 +40,7 @@ fun CSG.toGeometry(): Geometry {
} }
for (j in 3..polygon.vertices.size) { 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( fc.vertexNormals = arrayOf(
Vector3().copy(pvs[0].normal), Vector3().copy(pvs[0].normal),
Vector3().copy(pvs[j - 2].normal), Vector3().copy(pvs[j - 2].normal),
@ -65,3 +62,18 @@ fun CSG.toGeometry(): Geometry {
geom.computeBoundingBox() geom.computeBoundingBox()
return geom 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()
}
}

View File

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

View File

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

View File

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

View File

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

View File

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