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
val dataforgeVersion by extra("0.1.5-dev-6")
plugins {
val kotlinVersion = "1.3.50-eap-5"
val kotlinVersion = "1.3.61"
val toolsVersion = "0.3.2"
kotlin("jvm") version kotlinVersion apply false
id("kotlin2js") version kotlinVersion apply false
id("kotlin-dce-js") version kotlinVersion apply false
id("org.jetbrains.kotlin.frontend") version "0.0.45" apply false
id("scientifik.mpp") version "0.1.4" apply false
id("scientifik.jvm") version "0.1.4" apply false
id("scientifik.js") version "0.1.4" apply false
id("scientifik.publish") version "0.1.4" apply false
id("org.openjfx.javafxplugin") version "0.0.7" apply false
id("scientifik.mpp") version toolsVersion apply false
id("scientifik.jvm") version toolsVersion apply false
id("scientifik.js") version toolsVersion apply false
id("scientifik.publish") version toolsVersion apply false
id("org.openjfx.javafxplugin") version "0.0.8" apply false
}
allprojects {
repositories {
mavenLocal()
jcenter()
maven("https://kotlin.bintray.com/kotlinx")
maven("http://npm.mipt.ru:8081/artifactory/gradle-dev-local")
maven("https://kotlin.bintray.com/js-externals")
maven("https://dl.bintray.com/pdvrieze/maven")
maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("http://maven.jzy3d.org/releases")
maven("https://kotlin.bintray.com/js-externals")
// maven("https://dl.bintray.com/gbaldeck/kotlin")
// maven("https://dl.bintray.com/rjaros/kotlin")
}
group = "hep.dataforge"
version = "0.1.0-dev"
}
subprojects{
this.useSerialization()
}
val githubProject by extra("dataforge-vis")
val bintrayRepo by extra("dataforge")

View File

@ -1,25 +1,50 @@
import org.openjfx.gradle.JavaFXOptions
import scientifik.useSerialization
plugins {
id("scientifik.mpp")
}
scientifik{
serialization = true
id("org.openjfx.javafxplugin")
}
val dataforgeVersion: String by rootProject.extra
//val kvisionVersion: String by rootProject.extra("2.0.0-M1")
useSerialization()
kotlin {
jvm{
withJava()
}
sourceSets {
val commonMain by getting {
commonMain{
dependencies {
api("hep.dataforge:dataforge-output:$dataforgeVersion")
}
}
val jsMain by getting {
jvmMain{
dependencies {
api("no.tornado:tornadofx:1.7.19")
//api("no.tornado:tornadofx-controlsfx:0.1.1")
api("de.jensd:fontawesomefx-fontawesome:4.7.0-11"){
exclude(group = "org.openjfx")
}
api("de.jensd:fontawesomefx-commons:11.0"){
exclude(group = "org.openjfx")
}
}
}
jsMain{
dependencies {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
api(npm("text-encoding"))
api(npm("bootstrap","4.4.1"))
implementation(npm("jsoneditor"))
implementation(npm("file-saver"))
}
}
}
}
configure<JavaFXOptions> {
modules("javafx.controls")
}

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
import hep.dataforge.meta.*
import hep.dataforge.values.ValueType
import hep.dataforge.values.int
import kotlin.math.max
/**
* Taken from https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
* Definitions of common colors. Taken from
* https://github.com/markaren/three.kt/blob/master/threejs-wrapper/src/main/kotlin/info/laht/threekt/math/ColorConstants.kt
*/
object Colors {
const val aliceblue = 0xF0F8FF
@ -174,4 +180,63 @@ object Colors {
const val whitesmoke = 0xF5F5F5
const val yellow = 0xFFFF00
const val yellowgreen = 0x9ACD32
const val RED_KEY = "red"
const val GREEN_KEY = "green"
const val BLUE_KEY = "blue"
/**
* Convert color represented as Meta to string of format #rrggbb
*/
fun fromMeta(item: MetaItem<*>): String {
return when (item) {
is MetaItem.NodeItem<*> -> {
val node = item.node
rgbToString(
node[RED_KEY].number?.toByte()?.toUByte() ?: 0u,
node[GREEN_KEY].number?.toByte()?.toUByte() ?: 0u,
node[BLUE_KEY].number?.toByte()?.toUByte() ?: 0u
)
}
is MetaItem.ValueItem -> {
if (item.value.type == ValueType.NUMBER) {
rgbToString(item.value.int)
} else {
item.value.string
}
}
}
}
/**
* Convert Int color to string of format #rrggbb
*/
fun rgbToString(rgb: Int): String {
val string = rgb.toString(16).padStart(6, '0')
return "#" + string.substring(max(0, string.length - 6))
}
/**
* Convert three bytes representing color to string of format #rrggbb
*/
fun rgbToString(red: UByte, green: UByte, blue: UByte): String {
fun colorToString(color: UByte): String {
return color.toString(16).padStart(2, '0')
}
return buildString {
append("#")
append(colorToString(red))
append(colorToString(green))
append(colorToString(blue))
}
}
/**
* Convert three bytes representing color to Meta
*/
fun rgbToMeta(r: UByte, g: UByte, b: UByte): Meta = buildMeta {
RED_KEY put r.toInt()
GREEN_KEY put g.toInt()
BLUE_KEY put b.toInt()
}
}

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
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.MetaItem
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.names.*
import hep.dataforge.provider.Provider
import kotlinx.serialization.Transient
import kotlin.collections.set
open class VisualGroup<T : VisualObject> : AbstractVisualObject(), Iterable<T>, Provider {
protected open val namedChildren: MutableMap<Name, T> = HashMap()
protected open val unnamedChildren: MutableList<T> = ArrayList()
override var properties: Config? = null
/**
* Represents a group of [VisualObject] instances
*/
interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
/**
* A map of top level named children
*/
val children: Map<NameToken, VisualObject>
override val defaultTarget: String get() = VisualObject.TYPE
override fun iterator(): Iterator<T> = (namedChildren.values + unnamedChildren).iterator()
val styleSheet: StyleSheet?
override fun provideTop(target: String): Map<Name, Any> {
return when (target) {
VisualObject.TYPE -> namedChildren
override fun provideTop(target: String): Map<Name, Any> =
when (target) {
VisualObject.TYPE -> children.flatMap { (key, value) ->
val res: Map<Name, Any> = if (value is VisualGroup) {
value.provideTop(target).mapKeys { key + it.key }
} else {
mapOf(key.asName() to value)
}
res.entries
}.associate { it.toPair() }
STYLE_TARGET -> styleSheet?.items?.mapKeys { it.key.toName() } ?: emptyMap()
else -> emptyMap()
}
}
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
super.propertyChanged(name, before, after)
forEach {
it.propertyChanged(name, before, after)
}
}
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) {
listeners.add(Listener(owner, action))
override fun iterator(): Iterator<VisualObject> = children.values.iterator()
operator fun get(name: Name): VisualObject? {
return when {
name.isEmpty() -> this
name.length == 1 -> children[name.first()!!]
else -> (children[name.first()!!] as? VisualGroup)?.get(name.cutFirst())
}
}
/**
* 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
*/
fun removeChildrenChangeListener(owner: Any?) {
listeners.removeAll { it.owner === owner }
fun removeChildrenChangeListener(owner: Any?)
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 VisualGroup.get(str: String?) = get(str?.toName() ?: Name.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()
}
}
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.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.toName
import hep.dataforge.provider.Type
import hep.dataforge.vis.common.VisualObject.Companion.TYPE
import kotlinx.serialization.Transient
private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
//private fun Laminate.withTop(meta: Meta): Laminate = Laminate(listOf(meta) + layers)
//private fun Laminate.withBottom(meta: Meta): Laminate = Laminate(layers + meta)
/**
* A root type for display hierarchy
*/
@Type(TYPE)
interface VisualObject : MetaRepr, Configurable {
interface VisualObject : Configurable {
/**
* The parent object of this one. If null, this one is a root.
@ -21,10 +23,17 @@ interface VisualObject : MetaRepr, Configurable {
@Transient
var parent: VisualObject?
/**
* All properties including styles and prototypes if present, but without inheritance
*/
fun allProperties(): Laminate
/**
* Set property for this object
*/
fun setProperty(name: Name, value: Any?)
fun setProperty(name: Name, value: Any?) {
config[name] = value
}
/**
* Get property including or excluding parent properties
@ -32,9 +41,11 @@ interface VisualObject : MetaRepr, Configurable {
fun getProperty(name: Name, inherit: Boolean = true): MetaItem<*>?
/**
* Manually trigger property changed event. If [name] is empty, notify that the whole object is changed
* Trigger property invalidation event. If [name] is empty, notify that the whole object is changed
*/
fun propertyChanged(name: Name, before: MetaItem<*>? = null, after: MetaItem<*>? = null): Unit
fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?): Unit
fun propertyInvalidated(name: Name) = propertyChanged(name, null, null)
/**
* Add listener triggering on property change
@ -46,65 +57,67 @@ interface VisualObject : MetaRepr, Configurable {
*/
fun removeChangeListener(owner: Any?)
/**
* List of names of styles applied to this object. Order matters. Not inherited
*/
var styles: List<String>
companion object {
const val TYPE = "visual"
val STYLE_KEY = "@style".asName()
//const val META_KEY = "@meta"
//const val TAGS_KEY = "@tags"
}
}
internal data class MetaListener(
val owner: Any? = null,
val action: (name: Name, oldItem: MetaItem<*>?, newItem: MetaItem<*>?) -> Unit
)
/**
* Get [VisualObject] property using key as a String
*/
fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>? = getProperty(key.toName(), inherit)
abstract class AbstractVisualObject: VisualObject {
/**
* Set [VisualObject] property using key as a String
*/
fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value)
@Transient
override var parent: VisualObject? = null
@Transient
private val listeners = HashSet<MetaListener>()
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
for (l in listeners) {
l.action(name, before, after)
}
/**
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.
*/
fun VisualObject.useStyle(name: String) {
styles = styles + name
}
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
listeners.add(MetaListener(owner, action))
}
//private tailrec fun VisualObject.topGroup(): VisualGroup? {
// val parent = this.parent
// return if (parent == null) {
// this as? VisualGroup
// }
// else {
// parent.topGroup()
// }
//}
//
///**
// * Add or update given style on a top-most reachable parent group and apply it to this object
// */
//fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
// val styleName = name.toName()
// topGroup()?.updateStyle(styleName, builder) ?: error("Can't find parent group for $this")
// useStyle(styleName)
//}
override fun removeChangeListener(owner: Any?) {
listeners.removeAll { it.owner == owner }
}
tailrec fun VisualObject.findStyle(name: String): Meta? =
(this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name)
abstract var properties: Config?
override val config: Config
get() = properties ?: Config().also { config ->
properties = config
config.onChange(this, ::propertyChanged)
}
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
override fun setProperty(name: Name, value: Any?) {
config[name] = value
}
//operator fun VisualObject.get(name: Name): VisualObject?{
// return when {
// name.isEmpty() -> this
// this is VisualGroup -> this[name]
// else -> null
// }
//}
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()
}
}

View File

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

View File

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

View File

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

View File

@ -1,14 +1,10 @@
package hep.dataforge.vis.hmr
package hep.dataforge.js
import kotlin.browser.document
import kotlin.dom.hasClass
external val module: Module
external interface Module {
val hot: Hot?
}
external interface Hot {
val data: dynamic
@ -19,17 +15,31 @@ external interface Hot {
fun dispose(callback: (data: dynamic) -> Unit)
}
external fun require(name: String): dynamic
abstract class ApplicationBase {
open val stateKeys: List<String> get() = emptyList()
abstract fun start(state: Map<String, Any>)
open fun dispose(): Map<String, Any> = emptyMap()
external interface Module {
val hot: Hot?
}
fun startApplication(builder: () -> ApplicationBase) {
fun start(state: dynamic): ApplicationBase? {
/**
* Base interface for applications.
*
* Base interface for applications supporting Hot Module Replacement (HMR).
*/
interface Application {
/**
* Starting point for an application.
* @param state Initial state between Hot Module Replacement (HMR).
*/
fun start(state: Map<String, Any>)
/**
* Ending point for an application.
* @return final state for Hot Module Replacement (HMR).
*/
fun dispose(): Map<String, Any> = emptyMap()
}
fun startApplication(builder: () -> Application) {
fun start(state: dynamic): Application? {
return if (document.body?.hasClass("testApp") == true) {
val application = builder()
@ -42,7 +52,7 @@ fun startApplication(builder: () -> ApplicationBase) {
}
}
var application: ApplicationBase? = null
var application: Application? = null
val state: dynamic = module.hot?.let { hot ->
hot.accept()

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> {
override val type: KClass<out FXPlugin> = FXPlugin::class
override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP)
override fun invoke(meta: Meta): FXPlugin = FXPlugin(meta)
override fun invoke(meta: Meta, context: Context): FXPlugin = FXPlugin(meta)
}
}

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.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Null
import hep.dataforge.values.Value
import hep.dataforge.values.asValue
@ -40,8 +42,9 @@ class ColorValueChooser : ValueChooserBase<ColorPicker>() {
}
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
* and open the template in the editor.
*/
package hep.dataforge.vis.fx.values
package hep.dataforge.vis.fx.editor
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.value
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Value
import hep.dataforge.values.parseValue
import javafx.collections.FXCollections
@ -50,9 +52,10 @@ class ComboBoxValueChooser(val values: Collection<Value>? = null) : ValueChooser
}
companion object : ValueChooser.Factory {
override val name: String = "combo"
override val name: Name = "combo".asName()
override fun invoke(meta: Meta): ValueChooser = ComboBoxValueChooser(meta["values"].value?.list)
override fun invoke(meta: Meta): ValueChooser =
ComboBoxValueChooser(meta["values"].value?.list)
}
}

View File

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

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.NodeDescriptor
@ -84,7 +84,7 @@ class FXMetaNode<M : MetaNode<M>>(
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
!(cfg.descriptor?.tags?.contains(ConfigEditor.NO_CONFIGURATOR_TAG) ?: false)
!(cfg.descriptor?.attributes?.get(ConfigEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
}
val children = object : ListBinding<FXMeta<M>>() {
@ -172,7 +172,7 @@ fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
return if (append && token.index.isNotEmpty()) {
val name = token.asName()
val index = (getAll(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
val index = (getIndexed(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
val newName = name.withIndex(index.toString())
set(newName, EmptyMeta)
get(newName).node!!

View File

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

View File

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

View File

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

View File

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

View File

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

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

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)
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
"_typename" to "TGeoBBox"
"fDX" to xSize
"fDY" to ySize
"fDZ" to zSize
"_typename" put "TGeoBBox"
"fDX" put xSize
"fDY" put ySize
"fDZ" put zSize
}
/**
* Create a GDML union
*/
operator fun Meta.plus(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoUnion"
"fNode.fLeft" put this
"fNode.fRight" put other
"fNode._typename" put "TGeoUnion"
}
/**
* Create a GDML subtraction
*/
operator fun Meta.minus(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoSubtraction"
"fNode.fLeft" put this
"fNode.fRight" put other
"fNode._typename" put "TGeoSubtraction"
}
/**
* Intersect two GDML geometries
*/
infix fun Meta.intersect(other: Meta) = buildMeta {
"fNode.fLeft" to this
"fNode.fRight" to other
"fNode._typename" to "TGeoIntersection"
"fNode.fLeft" put this
"fNode.fRight" put other
"fNode._typename" put "TGeoIntersection"
}
companion object {

View File

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

View File

@ -1,18 +1,19 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.Meta
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.builder
import hep.dataforge.vis.spatial.VisualGroup3D
import scientifik.gdml.GDML
import scientifik.gdml.GDMLGroup
import scientifik.gdml.GDMLMaterial
import scientifik.gdml.GDMLSolid
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.vis.common.useStyle
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import scientifik.gdml.*
import kotlin.random.Random
class GDMLTransformer(val root: GDML) {
private val materialCache = HashMap<GDMLMaterial, Meta>()
private val random = Random(111)
//private val materialCache = HashMap<GDMLMaterial, Meta>()
private val random = Random(222)
enum class Action {
ACCEPT,
@ -23,45 +24,60 @@ class GDMLTransformer(val root: GDML) {
/**
* A special group for local templates
*/
val templates by lazy { VisualGroup3D() }
val proto by lazy { VisualGroup3D() }
private val styleCache = HashMap<Name, Meta>()
var lUnit: LUnit = LUnit.MM
var resolveColor: ColorResolver = { material, _ ->
val materialColor = materialCache.getOrPut(material) {
buildMeta {
"color" to random.nextInt(0, Int.MAX_VALUE)
}
}
if (this?.physVolumes?.isEmpty() != false) {
materialColor
} else {
materialColor.builder().apply { "opacity" to 0.5 }
}
}
var solidAction: (GDMLSolid) -> Action = { Action.CACHE }
var volumeAction: (GDMLGroup) -> Action = { Action.ACCEPT }
var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
fun printStatistics() {
println("Solids:")
solidCounter.entries.sortedByDescending { it.value }.forEach {
println("\t$it")
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { parent, _ ->
lUnit = LUnit.CM
if (parent.physVolumes.isNotEmpty()) {
useStyle("opaque") {
Material3D.MATERIAL_OPACITY_KEY put 0.3
}
}
println("Solids total: ${solidCounter.values.sum()}")
}
private val solidCounter = HashMap<String, Int>()
internal fun solidAdded(solid: GDMLSolid) {
solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
fun VisualObject3D.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
styleCache.getOrPut(name.toName()) {
buildMeta(builder)
}
useStyle(name)
}
internal fun configureSolid(obj: VisualObject3D, parent: GDMLVolume, solid: GDMLSolid) {
val material = parent.materialref.resolve(root) ?: GDMLElement(parent.materialref.ref)
val styleName = "material[${material.name}]"
obj.useStyle(styleName) {
MATERIAL_COLOR_KEY put random.nextInt(16777216)
"gdml.material" put material.name
}
obj.solidConfiguration(parent, solid)
}
//
// internal fun solidAdded(solid: GDMLSolid) {
// solidCounter[solid.name] = (solidCounter[solid.name] ?: 0) + 1
// }
var onFinish: GDMLTransformer.() -> Unit = {}
internal fun finished(final: VisualGroup3D) {
final.templates = templates
internal fun finalize(final: VisualGroup3D): VisualGroup3D {
final.prototypes = proto
styleCache.forEach {
final.styleSheet {
define(it.key.toString(), it.value)
}
}
final.rotationOrder = RotationOrder.ZXY
onFinish(this@GDMLTransformer)
return final
}
}

View File

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

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

View File

@ -1,13 +1,12 @@
import org.openjfx.gradle.JavaFXOptions
import scientifik.useSerialization
plugins {
id("scientifik.mpp")
id("org.openjfx.javafxplugin")
}
scientifik{
serialization = true
}
useSerialization()
kotlin {
jvm {
@ -21,17 +20,20 @@ kotlin {
}
jvmMain {
dependencies {
implementation(project(":dataforge-vis-fx"))
implementation("org.fxyz3d:fxyz3d:0.4.0")
implementation("org.fxyz3d:fxyz3d:0.5.2") {
exclude(module = "slf4j-simple")
}
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${Scientifik.coroutinesVersion}")
implementation("eu.mihosoft.vrl.jcsg:jcsg:0.5.7") {
exclude(module = "slf4j-simple")
}
}
}
jsMain {
dependencies {
api("info.laht.threekt:threejs-wrapper:0.106-npm-3")
// api(project(":wrappers"))
implementation(npm("three", "0.106.2"))
implementation(npm("@hi-level/three-csg", "1.0.6"))
implementation(npm("style-loader"))
implementation(npm("element-resize-event"))
}
}
}

View File

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

View File

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

View File

@ -2,11 +2,13 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlin.math.cos
import kotlin.math.sin
/**
* A cylinder or cut cone segment
@ -18,7 +20,7 @@ class ConeSegment(
var upperRadius: Float,
var startAngle: Float = 0f,
var angle: Float = PI2
) : AbstractVisualObject(), VisualObject3D {
) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
@ -26,12 +28,55 @@ class ConeSegment(
override var position: Point3D? = null
override var rotation: Point3D? = null
override var scale: Point3D? = null
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val segments = detail ?: 8
require(segments >= 4) { "The number of segments in cone segment is too small" }
val angleStep = angle / (segments - 1)
fun shape(r: Float, z: Float): List<Point3D> {
return (0 until segments).map { i ->
Point3D(r * cos(startAngle + angleStep * i), r * sin(startAngle + angleStep * i), z)
}
}
geometryBuilder.apply {
//creating shape in x-y plane with z = 0
val bottomOuterPoints = shape(upperRadius, -height / 2)
val upperOuterPoints = shape(radius, height / 2)
//outer face
(1 until segments).forEach {
face4(bottomOuterPoints[it - 1], bottomOuterPoints[it], upperOuterPoints[it], upperOuterPoints[it - 1])
}
if (angle == PI2) {
face4(bottomOuterPoints.last(), bottomOuterPoints[0], upperOuterPoints[0], upperOuterPoints.last())
}
val zeroBottom = Point3D(0f, 0f, 0f)
val zeroTop = Point3D(0f, 0f, height)
(1 until segments).forEach {
face(bottomOuterPoints[it - 1], zeroBottom, bottomOuterPoints[it])
face(upperOuterPoints[it - 1], upperOuterPoints[it], zeroTop)
}
if (angle == PI2) {
face(bottomOuterPoints.last(), zeroBottom, bottomOuterPoints[0])
face(upperOuterPoints.last(), upperOuterPoints[0], zeroTop)
} else {
face4(zeroTop, zeroBottom, bottomOuterPoints[0], upperOuterPoints[0])
face4(zeroTop, zeroBottom, bottomOuterPoints.last(), upperOuterPoints.last())
}
}
}
}
inline fun VisualGroup3D.cylinder(
r: Number,
height: Number,
name: String? = null,
name: String = "",
block: ConeSegment.() -> Unit = {}
): ConeSegment = ConeSegment(
r.toFloat(),

View File

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

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
@ -62,7 +62,7 @@ class Extruded(
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val shape: Shape2D = shape
if (shape.size < 3) error("Extruded shape requires more than points per layer")
if (shape.size < 3) error("Extruded shape requires more than 2 points per layer")
/**
* Expand the shape for specific layers
@ -110,5 +110,5 @@ class Extruded(
}
}
fun VisualGroup3D.extrude(name: String? = null, action: Extruded.() -> Unit = {}) =
fun VisualGroup3D.extrude(name: String = "", action: Extruded.() -> Unit = {}) =
Extruded().apply(action).also { set(name, it) }

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
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.NameSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.io.serialization.NameSerializer
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.Laminate
import hep.dataforge.meta.MetaItem
import hep.dataforge.meta.get
import hep.dataforge.names.Name
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.names.NameToken
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.vis.common.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.set
/**
* A proxy [VisualObject3D] to reuse a template object
*/
@Serializable
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D {
@SerialName("3d.proxy")
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
override var position: Point3D? = null
override var rotation: Point3D? = null
@ -28,36 +38,133 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualObject3D {
/**
* Recursively search for defined template in the parent
*/
val template: VisualObject3D
get() = (parent as? VisualGroup3D)?.getTemplate(templateName)
val prototype: VisualObject3D
get() = (parent as? VisualGroup3D)?.getPrototype(templateName)
?: error("Template with name $templateName not found in $parent")
override val styleSheet: StyleSheet
get() = (parent as? VisualGroup)?.styleSheet ?: StyleSheet(this)
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
return if (inherit) {
super.getProperty(name, false) ?: template.getProperty(name, false) ?: parent?.getProperty(name, inherit)
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name)
?: parent?.getProperty(name)
} else {
super.getProperty(name, false) ?: template.getProperty(name, false)
properties?.get(name)
?: mergedStyles[name]
?: prototype.getProperty(name, false)
}
}
override fun MetaBuilder.updateMeta() {
//TODO add reference to child
updatePosition()
override val children: Map<NameToken, ProxyChild>
get() = (prototype as? MutableVisualGroup)?.children
?.filter { !it.key.toString().startsWith("@") }
?.mapValues {
ProxyChild(it.key.asName())
} ?: emptyMap()
@Transient
private val propertyCache: HashMap<Name, Config> = HashMap()
fun childPropertyName(childName: Name, propertyName: Name): Name {
return NameToken(PROXY_CHILD_PROPERTY_PREFIX, childName.toString()) + propertyName
}
private fun prototypeFor(name: Name): VisualObject {
return (prototype as? VisualGroup)?.get(name)
?: error("Prototype with name $name not found in $this")
}
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
@Serializable
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
val prototype: VisualObject get() = prototypeFor(name)
override val styleSheet: StyleSheet get() = this@Proxy.styleSheet
override val children: Map<NameToken, VisualObject>
get() = (prototype as? VisualGroup)?.children?.mapValues { (key, _) ->
ProxyChild(
name + key.asName()
)
} ?: emptyMap()
override var properties: Config?
get() = propertyCache[name]
set(value) {
if (value == null) {
propertyCache.remove(name)?.also {
//Removing listener if it is present
removeChangeListener(this@Proxy)
}
} else {
propertyCache[name] = value.also {
onPropertyChange(this@Proxy) { propertyName, before, after ->
this@Proxy.propertyChanged(childPropertyName(name, propertyName), before, after)
}
}
}
}
//fun VisualGroup3D.proxy(
// templateName: Name,
// //name: String? = null,
// builder: VisualGroup3D.() -> Unit
//): Proxy {
// val template = getTemplate(templateName) ?: templates.builder()
// return Proxy(this, templateName).also { set(name, it) }
//}
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"
}
}
val VisualObject.prototype: VisualObject
get() = when (this) {
is Proxy -> prototype
is Proxy.ProxyChild -> prototype
else -> this
}
/**
* Create ref for existing prototype
*/
inline fun VisualGroup3D.ref(
templateName: Name,
name: String? = null,
action: Proxy.() -> Unit = {}
) = Proxy(templateName).apply(action).also { set(name, it) }
name: String = "",
block: Proxy.() -> Unit = {}
) = Proxy(templateName).apply(block).also { set(name, it) }
/**
* Add new proxy wrapping given object and automatically adding it to the prototypes
*/
fun VisualGroup3D.proxy(
templateName: Name,
obj: VisualObject3D,
name: String = "",
block: Proxy.() -> Unit = {}
): Proxy {
val existing = getPrototype(templateName)
if (existing == null) {
setPrototype(templateName, obj)
} else if (existing != obj) {
error("Can't add different prototype on top of existing one")
}
return ref(templateName, name, block)
}

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
@ -16,7 +16,7 @@ class Sphere(
var phi: Float = PI2,
var thetaStart: Float = 0f,
var theta: Float = PI.toFloat()
) : AbstractVisualObject(), VisualObject3D {
) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
@ -24,13 +24,26 @@ class Sphere(
override var position: Point3D? = null
override var rotation: Point3D? = null
override var scale: Point3D? = null
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
TODO("not implemented")
// val segments = this.detail ?: 8
// require(segments >= 4) { "The detail for sphere must be >= 4" }
// val phiStep = phi / segments
// val thetaStep = theta / segments
// for (i in 1 until segments - 1) {
// for (j in 0 until segments - 1) {
// val point1 = Point3D()
// }
// }
}
}
inline fun VisualGroup3D.sphere(
radius: Number,
phi: Number = 2 * PI,
theta: Number = PI,
name: String? = null,
name: String = "",
action: Sphere.() -> Unit = {}
) = Sphere(
radius.toFloat(),

View File

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

View File

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

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

View File

@ -3,12 +3,8 @@ package hep.dataforge.vis.spatial
import kotlin.math.PI
object World {
const val CAMERA_INITIAL_DISTANCE = -500.0
const val CAMERA_INITIAL_X_ANGLE = -50.0
const val CAMERA_INITIAL_Y_ANGLE = 0.0
const val CAMERA_INITIAL_Z_ANGLE = -210.0
const val CAMERA_NEAR_CLIP = 0.1
const val CAMERA_FAR_CLIP = 10000.0
val ZERO = Point3D(0.0, 0.0, 0.0)
val ONE = Point3D(1.0, 1.0, 1.0)
}
const val PI2: Float = 2 * PI.toFloat()

View File

@ -14,8 +14,8 @@ operator fun Point2D.component1() = x
operator fun Point2D.component2() = y
fun Point2D.toMeta() = buildMeta {
VisualObject3D.x to x
VisualObject3D.y to y
VisualObject3D.x put x
VisualObject3D.y put y
}
fun Meta.point2D() = Point2D(this["x"].number ?: 0, this["y"].number ?: 0)
@ -26,16 +26,16 @@ expect class Point3D(x: Number, y: Number, z: Number) {
var z: Double
}
expect operator fun Point3D.plus(other: Point3D): Point3D
operator fun Point3D.component1() = x
operator fun Point3D.component2() = y
operator fun Point3D.component3() = z
fun Meta.point3D() = Point3D(this["x"].number ?: 0, this["y"].number ?: 0, this["y"].number ?: 0)
val zero = Point3D(0, 0, 0)
fun Point3D.toMeta() = buildMeta {
VisualObject3D.x to x
VisualObject3D.y to y
VisualObject3D.z to z
VisualObject3D.x put x
VisualObject3D.y put y
VisualObject3D.z put z
}

View File

@ -1,41 +1,109 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.descriptor
import hep.dataforge.names.NameToken
import hep.dataforge.names.toName
import kotlinx.serialization.*
import kotlinx.serialization.internal.DoubleSerializer
import kotlinx.serialization.internal.StringDescriptor
import kotlinx.serialization.internal.nullable
@Serializable
private data class Point2DSerial(val x: Double, val y: Double)
inline fun <R> Decoder.decodeStructure(
desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
crossinline block: CompositeDecoder.() -> R
): R {
val decoder = beginStructure(desc, *typeParams)
val res = decoder.block()
decoder.endStructure(desc)
return res
}
@Serializable
private data class Point3DSerial(val x: Double, val y: Double, val z: Double)
inline fun Encoder.encodeStructure(
desc: SerialDescriptor,
vararg typeParams: KSerializer<*> = emptyArray(),
block: CompositeEncoder.() -> Unit
) {
val encoder = beginStructure(desc, *typeParams)
encoder.block()
encoder.endStructure(desc)
}
@Serializer(Point3D::class)
object Point3DSerializer : KSerializer<Point3D> {
private val serializer = Point3DSerial.serializer()
override val descriptor: SerialDescriptor get() = serializer.descriptor
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point3D") {
double("x", true)
double("y", true)
double("z", true)
}
override fun deserialize(decoder: Decoder): Point3D {
return serializer.deserialize(decoder).let {
Point3D(it.x, it.y, it.z)
var x: Double? = null
var y: Double? = null
var z: Double? = null
decoder.decodeStructure(descriptor) {
loop@ while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break@loop
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
2 -> z = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
else -> throw SerializationException("Unknown index $i")
}
}
}
return Point3D(x?:0.0, y?:0.0, z?:0.0)
}
override fun serialize(encoder: Encoder, obj: Point3D) {
serializer.serialize(encoder, Point3DSerial(obj.x, obj.y, obj.z) )
encoder.encodeStructure(descriptor) {
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
if (obj.z != 0.0) encodeDoubleElement(descriptor, 2, obj.z)
}
}
}
@Serializer(Point2D::class)
object Point2DSerializer : KSerializer<Point2D> {
private val serializer = Point2DSerial.serializer()
override val descriptor: SerialDescriptor get() = serializer.descriptor
override val descriptor: SerialDescriptor = descriptor("hep.dataforge.vis.spatial.Point2D") {
double("x", true)
double("y", true)
}
override fun deserialize(decoder: Decoder): Point2D {
return serializer.deserialize(decoder).let {
Point2D(it.x, it.y)
var x: Double? = null
var y: Double? = null
decoder.decodeStructure(descriptor) {
loop@ while (true) {
when (val i = decodeElementIndex(descriptor)) {
CompositeDecoder.READ_DONE -> break@loop
0 -> x = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
1 -> y = decodeNullableSerializableElement(descriptor, 0, DoubleSerializer.nullable) ?: 0.0
else -> throw SerializationException("Unknown index $i")
}
}
}
return Point2D(x?:0.0, y?:0.0)
}
override fun serialize(encoder: Encoder, obj: Point2D) {
serializer.serialize(encoder, Point2DSerial(obj.x, obj.y))
encoder.encodeStructure(descriptor) {
if (obj.x != 0.0) encodeDoubleElement(descriptor, 0, obj.x)
if (obj.y != 0.0) encodeDoubleElement(descriptor, 1, obj.y)
}
}
}
@Serializer(NameToken::class)
object NameTokenSerializer : KSerializer<NameToken> {
override val descriptor: SerialDescriptor = StringDescriptor.withName("NameToken")
override fun deserialize(decoder: Decoder): NameToken {
return decoder.decodeString().toName().first()!!
}
override fun serialize(encoder: Encoder, obj: NameToken) {
encoder.encodeString(obj.toString())
}
}

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

View File

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

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
import hep.dataforge.context.Global
import hep.dataforge.vis.spatial.Visual3DPlugin.Companion.json
import kotlinx.serialization.ImplicitReflectionSerializer
import kotlin.test.Test
import kotlin.test.assertEquals
@ -11,10 +11,12 @@ class SerializationTest {
fun testCubeSerialization() {
val cube = Box(100f, 100f, 100f).apply {
color(222)
x = 100
z = -100
}
val meta = cube.toMeta()
println(meta)
val newCube = Box(Global,null, meta)
assertEquals(cube,newCube)
val string = json.stringify(Box.serializer(), cube)
println(string)
val newCube = json.parse(Box.serializer(), string)
assertEquals(cube.config, newCube.config)
}
}

View File

@ -1,8 +1,14 @@
package hep.dataforge.vis.spatial
import info.laht.threekt.math.Vector2
import info.laht.threekt.math.Vector3
import info.laht.threekt.math.plus
actual typealias Point2D = Vector2
actual typealias Point3D = Vector3
actual 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
/**
* 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) {

View File

@ -1,19 +1,15 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.meta.boolean
import hep.dataforge.meta.int
import hep.dataforge.meta.node
import hep.dataforge.names.plus
import hep.dataforge.names.Name
import hep.dataforge.names.startsWith
import hep.dataforge.provider.Type
import hep.dataforge.vis.common.asName
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
import hep.dataforge.vis.spatial.three.ThreeFactory.Companion.TYPE
import hep.dataforge.vis.spatial.three.ThreeMaterials.getMaterial
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.EdgesGeometry
import info.laht.threekt.geometries.WireframeGeometry
import info.laht.threekt.objects.LineSegments
import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass
@ -21,9 +17,9 @@ import kotlin.reflect.KClass
* Builder and updater for three.js object
*/
@Type(TYPE)
interface ThreeFactory<T : VisualObject3D> {
interface ThreeFactory<in T : VisualObject> {
val type: KClass<out T>
val type: KClass<in T>
operator fun invoke(obj: T): Object3D
@ -35,112 +31,41 @@ interface ThreeFactory<T : VisualObject3D> {
/**
* Update position, rotation and visibility
*/
internal fun Object3D.updatePosition(obj: VisualObject3D) {
fun Object3D.updatePosition(obj: VisualObject3D) {
visible = obj.visible ?: true
position.set(obj.x, obj.y, obj.z)
// obj.rotation?.let{
// rotateZ(it.z)
// rotateX(it.x)
// rotateY(it.y)
// }
setRotationFromEuler(obj.euler)
scale.set(obj.scaleX, obj.scaleY, obj.scaleZ)
updateMatrix()
}
internal fun <T : VisualObject3D> Mesh.updateFrom(obj: T) {
matrixAutoUpdate = false
//inherited edges definition, enabled by default
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
val material = obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
add(LineSegments(EdgesGeometry(geometry as BufferGeometry), material))
}
//inherited wireframe definition, disabled by default
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
val material = obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node?.jsMaterial() ?: Materials.DEFAULT
add(LineSegments(WireframeGeometry(geometry as BufferGeometry), material))
}
//set position for mesh
updatePosition(obj)
obj.getProperty(MeshThreeFactory.LAYER_KEY).int?.let {
layers.set(it)
}
}
///**
// * Unsafe invocation of a factory
// */
//operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
// if (type.isInstance(obj)) {
// @Suppress("UNCHECKED_CAST")
// return invoke(obj as T)
// } else {
// error("The object of type ${obj::class} could not be rendered by this factory")
// }
//}
/**
* Unsafe invocation of a factory
* Update non-position non-geometry property
*/
operator fun <T : VisualObject3D> ThreeFactory<T>.invoke(obj: Any): Object3D {
if (type.isInstance(obj)) {
@Suppress("UNCHECKED_CAST")
return invoke(obj as T)
} else {
error("The object of type ${obj::class} could not be rendered by this factory")
}
}
/**
* Basic geometry-based factory
*/
abstract class MeshThreeFactory<T : VisualObject3D>(override val type: KClass<out T>) : ThreeFactory<T> {
/**
* Build a geometry for an object
*/
abstract fun buildGeometry(obj: T): BufferGeometry
override fun invoke(obj: T): Mesh {
//create mesh from geometry
return buildMesh<T>(obj) { buildGeometry(it) }
}
companion object {
val EDGES_KEY = "edges".asName()
val WIREFRAME_KEY = "wireframe".asName()
val ENABLED_KEY = "enabled".asName()
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
val EDGES_MATERIAL_KEY = EDGES_KEY + VisualObject3D.MATERIAL_KEY
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + VisualObject3D.MATERIAL_KEY
val LAYER_KEY = "layer".asName()
fun <T : VisualObject3D> buildMesh(obj: T, geometryBuilder: (T) -> BufferGeometry): Mesh {
//TODO add caching for geometries using templates
val geometry = geometryBuilder(obj)
//JS sometimes tries to pass Geometry as BufferGeometry
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
val mesh = Mesh(geometry, obj.material.jsMaterial())
mesh.updateFrom(obj)
//add listener to object properties
obj.onPropertyChange(this) { name, _, _ ->
if (name.startsWith(VisualObject3D.MATERIAL_KEY)) {
//updated material
mesh.material = obj.material.jsMaterial()
fun Object3D.updateProperty(source: VisualObject3D, propertyName: Name) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
this.material = getMaterial(source)
} else if (
name.startsWith(VisualObject3D.position) ||
name.startsWith(VisualObject3D.rotation) ||
name.startsWith(VisualObject3D.scale)
propertyName.startsWith(VisualObject3D.position)
|| propertyName.startsWith(VisualObject3D.rotation)
|| propertyName.startsWith(VisualObject3D.scale)
) {
//update position of mesh using this object
mesh.updatePosition(obj)
} else if (name == VisualObject3D.VISIBLE_KEY) {
mesh.visible = obj.visible ?: true
} else {
//full update
mesh.geometry = geometryBuilder(obj)
mesh.material = obj.material.jsMaterial()
}
}
return mesh
}
updatePosition(source)
} else if (propertyName == VisualObject3D.VISIBLE_KEY) {
visible = source.visible ?: true
}
}

View File

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

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
import hep.dataforge.context.AbstractPlugin
import hep.dataforge.context.PluginFactory
import hep.dataforge.context.PluginTag
import hep.dataforge.context.content
import hep.dataforge.meta.*
import hep.dataforge.context.*
import hep.dataforge.meta.Meta
import hep.dataforge.names.*
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
import info.laht.threekt.cameras.Camera
import info.laht.threekt.cameras.PerspectiveCamera
import info.laht.threekt.core.Object3D
import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.external.controls.TrackballControls
import org.w3c.dom.Node
import kotlin.collections.set
import kotlin.reflect.KClass
import info.laht.threekt.objects.Group as ThreeGroup
class ThreePlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag
@ -28,31 +23,81 @@ class ThreePlugin : AbstractPlugin() {
objectFactories[Convex::class] = ThreeConvexFactory
objectFactories[Sphere::class] = ThreeSphereFactory
objectFactories[ConeSegment::class] = ThreeCylinderFactory
objectFactories[PolyLine::class] = ThreeLineFactory
objectFactories[Label3D::class] = ThreeCanvasLabelFactory
}
private fun findObjectFactory(type: KClass<out VisualObject3D>): ThreeFactory<*>? {
return objectFactories[type]
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type }
@Suppress("UNCHECKED_CAST")
private fun findObjectFactory(type: KClass<out VisualObject>): ThreeFactory<VisualObject3D>? {
return (objectFactories[type]
?: context.content<ThreeFactory<*>>(ThreeFactory.TYPE).values.find { it.type == type })
as ThreeFactory<VisualObject3D>?
}
fun buildObject3D(obj: VisualObject3D): Object3D {
return when (obj) {
is VisualGroup3D -> Group(obj.mapNotNull {
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 {
buildObject3D(it)
val object3D = buildObject3D(child)
group[token] = object3D
} catch (ex: Throwable) {
console.error(ex)
logger.error(ex) { "Failed to render $it" }
null
logger.error(ex) { "Failed to render $child" }
}
}).apply {
}
}
group.apply {
updatePosition(obj)
//obj.onChildrenChange()
obj.onPropertyChange(this) { name, _, _ ->
if (
name.startsWith(VisualObject3D.position) ||
name.startsWith(VisualObject3D.rotation) ||
name.startsWith(VisualObject3D.scale)
) {
//update position of mesh using this object
updatePosition(obj)
} else if (name == VisualObject3D.VISIBLE_KEY) {
visible = obj.visible ?: true
}
}
obj.onChildrenChange(this) { name, child ->
if (name.isEmpty()) {
logger.error { "Children change with empty namr on $group" }
return@onChildrenChange
}
val parentName = name.cutLast()
val childName = name.last()!!
//removing old object
findChild(name)?.let { oldChild ->
oldChild.parent?.remove(oldChild)
}
//adding new object
if (child != null && child is VisualObject3D) {
try {
val object3D = buildObject3D(child)
set(name, object3D)
} catch (ex: Throwable) {
logger.error(ex) { "Failed to render $child" }
}
}
}
}
}
is Composite -> compositeFactory(obj)
is Proxy -> proxyFactory(obj)
else -> {
//find specialized factory for this type if it is present
val factory = findObjectFactory(obj::class)
val factory: ThreeFactory<VisualObject3D>? = findObjectFactory(obj::class)
when {
factory != null -> factory(obj)
obj is Shape -> ThreeShapeFactory(obj)
@ -62,30 +107,44 @@ class ThreePlugin : AbstractPlugin() {
}
}
fun buildCamera(meta: Meta) = PerspectiveCamera(
meta["fov"].int ?: 75,
meta["aspect"].double ?: 1.0,
meta["nearClip"].double ?: World.CAMERA_NEAR_CLIP,
meta["farClip"].double ?: World.CAMERA_FAR_CLIP
).apply {
position.setZ(World.CAMERA_INITIAL_DISTANCE)
rotation.set(
World.CAMERA_INITIAL_X_ANGLE,
World.CAMERA_INITIAL_Y_ANGLE,
World.CAMERA_INITIAL_Z_ANGLE
)
}
fun addControls(camera: Camera, element: Node, meta: Meta) {
when (meta["type"].string) {
"trackball" -> TrackballControls(camera, element)
else -> OrbitControls(camera, element)
}
}
companion object : PluginFactory<ThreePlugin> {
override val tag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
override val type = ThreePlugin::class
override fun invoke(meta: Meta) = ThreePlugin()
override fun invoke(meta: Meta, context: Context) = ThreePlugin()
}
}
internal operator fun Object3D.set(token: NameToken, object3D: Object3D) {
object3D.name = token.toString()
add(object3D)
}
internal fun Object3D.getOrCreateGroup(name: Name): Object3D {
return when {
name.isEmpty() -> this
name.length == 1 -> {
val token = name.first()!!
children.find { it.name == token.toString() } ?: info.laht.threekt.objects.Group().also { group ->
group.name = token.toString()
this.add(group)
}
}
else -> getOrCreateGroup(name.first()!!.asName()).getOrCreateGroup(name.cutFirst())
}
}
internal operator fun Object3D.set(name: Name, obj: Object3D) {
when (name.length) {
0 -> error("Can't set object with an empty name")
1 -> set(name.first()!!, obj)
else -> getOrCreateGroup(name.cutLast())[name.last()!!] = obj
}
}
internal fun Object3D.findChild(name: Name): Object3D? {
return when {
name.isEmpty() -> this
name.length == 1 -> this.children.find { it.name == name.first()!!.toString() }
else -> findChild(name.first()!!.asName())?.findChild(name.cutFirst())
}
}

View File

@ -1,6 +1,8 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.names.toName
import hep.dataforge.vis.spatial.Proxy
import hep.dataforge.vis.spatial.Proxy.Companion.PROXY_CHILD_PROPERTY_PREFIX
import hep.dataforge.vis.spatial.VisualObject3D
import info.laht.threekt.core.Object3D
@ -10,15 +12,27 @@ class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
override val type = Proxy::class
override fun invoke(obj: Proxy): Object3D {
val template = obj.template
val template = obj.prototype
val cachedObject = cache.getOrPut(template) {
three.buildObject3D(template)
}
//val mesh = Mesh(templateMesh.geometry as BufferGeometry, templateMesh.material)
val mesh = cachedObject.clone()
val object3D = cachedObject.clone()
object3D.updatePosition(obj)
mesh.updatePosition(obj)
return mesh
obj.onPropertyChange(this) { name, _, _ ->
if (name.first()?.body == PROXY_CHILD_PROPERTY_PREFIX) {
val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
val propertyName = name.cutFirst()
val proxyChild = obj[childName] as? VisualObject3D ?: error("Proxy child with name '$childName' not found or not a 3D object")
val child = object3D.findChild(childName)?: error("Object child with name '$childName' not found")
child.updateProperty(proxyChild, propertyName)
} else {
object3D.updateProperty(obj, name)
}
}
return object3D
}
}

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.objects.Mesh
/**
* Constructive Solid Geometry
*/
open external class CSG {
open var polygons: Array<Polygon>
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.vis.spatial.*
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.DirectGeometry
import info.laht.threekt.core.Face3
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D
import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.materials.Material
import info.laht.threekt.math.Euler
import info.laht.threekt.math.Vector3
/**
* Utility methods for three.kt.
* TODO move to three project
*/
@Suppress("FunctionName")
fun Group(children: Collection<Object3D>) = info.laht.threekt.objects.Group().apply {
children.forEach { this.add(it) }
}
import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture
import kotlin.math.PI
val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
@ -28,6 +23,8 @@ val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?:
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
internal fun Double.toRadians() = this * PI / 180
fun CSG.toGeometry(): Geometry {
val geom = Geometry()
@ -43,7 +40,7 @@ fun CSG.toGeometry(): Geometry {
}
for (j in 3..polygon.vertices.size) {
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, zero)
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, World.ZERO)
fc.vertexNormals = arrayOf(
Vector3().copy(pvs[0].normal),
Vector3().copy(pvs[j - 2].normal),
@ -65,3 +62,18 @@ fun CSG.toGeometry(): Geometry {
geom.computeBoundingBox()
return geom
}
internal fun Any.dispose() {
when (this) {
is Geometry -> dispose()
is BufferGeometry -> dispose()
is DirectGeometry -> dispose()
is Material -> dispose()
is Mesh -> {
geometry.dispose()
material.dispose()
}
is OrbitControls -> dispose()
is Texture -> dispose()
}
}

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