forked from kscience/visionforge
Merge branch 'dev' into doc
# Conflicts: # README.md # dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/Colors.kt # dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualGroup.kt # dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/common/VisualObject.kt # dataforge-vis-spatial/src/jsMain/kotlin/hep/dataforge/vis/spatial/editor/jsVisualTree.kt
This commit is contained in:
commit
6bb6a82b09
@ -1,4 +1,6 @@
|
||||
# DataForge Plugins for Visualisation
|
||||
[![JetBrains Research](https://jb.gg/badges/research.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
|
||||
|
||||
# DataForge plugins for visualisation
|
||||
|
||||
This repository contains [DataForge](http://npm.mipt.ru/dataforge/)
|
||||
(also [here](https://github.com/mipt-npm/dataforge-core)) components useful for visualization in
|
||||
|
@ -1,8 +1,10 @@
|
||||
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"
|
||||
val toolsVersion = "0.2.0"
|
||||
val kotlinVersion = "1.3.61"
|
||||
val toolsVersion = "0.3.2"
|
||||
|
||||
kotlin("jvm") version kotlinVersion apply false
|
||||
id("kotlin-dce-js") version kotlinVersion apply false
|
||||
@ -15,14 +17,22 @@ plugins {
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
maven("https://dl.bintray.com/pdvrieze/maven")
|
||||
maven("http://maven.jzy3d.org/releases")
|
||||
maven("https://kotlin.bintray.com/js-externals")
|
||||
// maven("https://dl.bintray.com/gbaldeck/kotlin")
|
||||
// maven("https://dl.bintray.com/rjaros/kotlin")
|
||||
}
|
||||
|
||||
group = "hep.dataforge"
|
||||
version = "0.1.0-dev"
|
||||
}
|
||||
|
||||
subprojects{
|
||||
this.useSerialization()
|
||||
}
|
||||
|
||||
val githubProject by extra("dataforge-vis")
|
||||
val bintrayRepo by extra("dataforge")
|
||||
|
||||
|
@ -1,27 +1,50 @@
|
||||
import org.openjfx.gradle.JavaFXOptions
|
||||
import scientifik.useSerialization
|
||||
|
||||
plugins {
|
||||
id("scientifik.mpp")
|
||||
}
|
||||
|
||||
scientifik{
|
||||
withSerialization()
|
||||
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("org.jetbrains:kotlin-extensions:1.0.1-pre.83-kotlin-1.3.50")
|
||||
api(npm("core-js"))
|
||||
api(npm("bootstrap","4.4.1"))
|
||||
implementation(npm("jsoneditor"))
|
||||
implementation(npm("file-saver"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configure<JavaFXOptions> {
|
||||
modules("javafx.controls")
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.names.*
|
||||
import kotlinx.serialization.Transient
|
||||
@ -17,46 +15,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
||||
/**
|
||||
* A map of top level named children
|
||||
*/
|
||||
abstract override val children: Map<NameToken, VisualObject> //get() = _children
|
||||
|
||||
/**
|
||||
* Styles, defined in this group. A style could be defined but not applied
|
||||
* TODO replace by custom object with get/set functionality
|
||||
*/
|
||||
protected abstract val styleSheet: MutableMap<Name, Meta>
|
||||
|
||||
override fun getStyle(name: Name): Meta? = styleSheet[name]
|
||||
|
||||
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
|
||||
fun VisualObject.applyStyle(name: Name, meta: Meta) {
|
||||
if (styles.contains(name)) {
|
||||
//full update
|
||||
//TODO do a fine grained update
|
||||
if (this is AbstractVisualObject) {
|
||||
styleChanged()
|
||||
} else {
|
||||
propertyChanged(EmptyName)
|
||||
}
|
||||
}
|
||||
if (this is VisualGroup) {
|
||||
this.children.forEach { (_, child) ->
|
||||
child.applyStyle(name, meta)
|
||||
}
|
||||
}
|
||||
}
|
||||
styleSheet[name] = meta
|
||||
if (apply) {
|
||||
applyStyle(name, meta)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// init {
|
||||
// //Do after deserialization
|
||||
// children.values.forEach {
|
||||
// it.parent = this
|
||||
// }
|
||||
// }
|
||||
abstract override val children: Map<NameToken, VisualObject>
|
||||
|
||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||
super.propertyChanged(name, before, after)
|
||||
@ -114,7 +73,7 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
||||
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
|
||||
* 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?) {
|
||||
@ -141,22 +100,13 @@ abstract class AbstractVisualGroup : AbstractVisualObject(), MutableVisualGroup
|
||||
structureChangeListeners.forEach { it.callback(name, child) }
|
||||
}
|
||||
|
||||
operator fun set(key: String, child: VisualObject?) = if (key.isBlank()) {
|
||||
child?.let { addStatic(child) }
|
||||
} else {
|
||||
set(key.asName(), child)
|
||||
}
|
||||
|
||||
// operator fun set(key: String?, child: VisualObject?) = set(key ?: "", child)
|
||||
|
||||
protected fun MetaBuilder.updateChildren() {
|
||||
//adding named children
|
||||
children.forEach {
|
||||
"children[${it.key}]" to it.value.toMeta()
|
||||
operator fun set(key: String, child: VisualObject?): Unit {
|
||||
if (key.isBlank()) {
|
||||
if(child!= null) {
|
||||
addStatic(child)
|
||||
}
|
||||
} else {
|
||||
set(key.toName(), child)
|
||||
}
|
||||
}
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
updateChildren()
|
||||
}
|
||||
}
|
@ -1,9 +1,8 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.EmptyName
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vis.common.VisualObject.Companion.STYLE_KEY
|
||||
import kotlinx.serialization.Transient
|
||||
|
||||
@ -17,15 +16,25 @@ abstract class AbstractVisualObject : VisualObject {
|
||||
@Transient
|
||||
override var parent: VisualObject? = null
|
||||
|
||||
abstract override var properties: Config?
|
||||
protected abstract var properties: Config?
|
||||
|
||||
override var styles: List<Name>
|
||||
get() = properties?.get(STYLE_KEY).stringList.map(String::toName)
|
||||
override var styles: List<String>
|
||||
get() = properties?.get(STYLE_KEY).stringList
|
||||
set(value) {
|
||||
setProperty(STYLE_KEY, value.map { it.toString() })
|
||||
styleChanged()
|
||||
//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.
|
||||
@ -39,8 +48,10 @@ abstract class AbstractVisualObject : VisualObject {
|
||||
private val listeners = HashSet<PropertyListener>()
|
||||
|
||||
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
|
||||
for (l in listeners) {
|
||||
l.action(name, before, after)
|
||||
if (before != after) {
|
||||
for (l in listeners) {
|
||||
l.action(name, before, after)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,10 +63,6 @@ abstract class AbstractVisualObject : VisualObject {
|
||||
listeners.removeAll { it.owner == owner }
|
||||
}
|
||||
|
||||
override fun setProperty(name: Name, value: Any?) {
|
||||
config[name] = value
|
||||
}
|
||||
|
||||
private var styleCache: Meta? = null
|
||||
|
||||
/**
|
||||
@ -66,14 +73,7 @@ abstract class AbstractVisualObject : VisualObject {
|
||||
styleCache = it
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to reset style cache
|
||||
*/
|
||||
protected fun styleChanged() {
|
||||
styleCache = null
|
||||
propertyChanged(EmptyName)
|
||||
}
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles)
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
@ -82,20 +82,12 @@ abstract class AbstractVisualObject : VisualObject {
|
||||
properties?.get(name) ?: mergedStyles[name]
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun MetaBuilder.updateMeta() {}
|
||||
|
||||
override fun toMeta(): Meta = buildMeta {
|
||||
"type" to this::class.simpleName
|
||||
"properties" to properties
|
||||
updateMeta()
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualObject.findStyle(styleName: Name): Meta? {
|
||||
if (this is VisualGroup) {
|
||||
val style = getStyle(styleName)
|
||||
if (style != null) return style
|
||||
}
|
||||
return parent?.findStyle(styleName)
|
||||
}
|
||||
//fun VisualObject.findStyle(styleName: Name): Meta? {
|
||||
// if (this is VisualGroup) {
|
||||
// val style = resolveStyle(styleName)
|
||||
// if (style != null) return style
|
||||
// }
|
||||
// return parent?.findStyle(styleName)
|
||||
//}
|
@ -1,5 +1,8 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.values.int
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
@ -178,6 +181,33 @@ object Colors {
|
||||
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
|
||||
*/
|
||||
@ -190,8 +220,8 @@ object Colors {
|
||||
* 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')
|
||||
fun colorToString(color: UByte): String {
|
||||
return color.toString(16).padStart(2, '0')
|
||||
}
|
||||
return buildString {
|
||||
append("#")
|
||||
@ -200,4 +230,13 @@ object Colors {
|
||||
append(colorToString(blue))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert three bytes representing color to Meta
|
||||
*/
|
||||
fun rgbToMeta(r: UByte, g: UByte, b: UByte): Meta = buildMeta {
|
||||
RED_KEY put r.toInt()
|
||||
GREEN_KEY put g.toInt()
|
||||
BLUE_KEY put b.toInt()
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
@file:UseSerializers(MetaSerializer::class)
|
||||
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.io.serialization.MetaSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import kotlinx.serialization.UseSerializers
|
||||
|
||||
@Serializable
|
||||
class StyleSheet() {
|
||||
@Transient
|
||||
internal var owner: VisualObject? = null
|
||||
|
||||
constructor(owner: VisualObject) : this() {
|
||||
this.owner = owner
|
||||
}
|
||||
|
||||
private val styleMap = HashMap<String, Meta>()
|
||||
|
||||
val items: Map<String, Meta> get() = styleMap
|
||||
|
||||
operator fun get(key: String): Meta? {
|
||||
return styleMap[key] ?: (owner?.parent as? VisualGroup)?.styleSheet?.get(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a style without notifying
|
||||
*/
|
||||
fun define(key: String, style: Meta?) {
|
||||
if (style == null) {
|
||||
styleMap.remove(key)
|
||||
} else {
|
||||
styleMap[key] = style
|
||||
}
|
||||
}
|
||||
|
||||
operator fun set(key: String, style: Meta?) {
|
||||
val oldStyle = styleMap[key]
|
||||
define(key, style)
|
||||
owner?.styleChanged(key, oldStyle, style)
|
||||
}
|
||||
|
||||
operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
|
||||
val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder)
|
||||
set(key, newStyle.seal())
|
||||
}
|
||||
}
|
||||
|
||||
private fun VisualObject.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {
|
||||
if (styles.contains(key)) {
|
||||
//TODO optimize set concatenation
|
||||
val tokens: Collection<Name> = ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
|
||||
.map { it.asName() }
|
||||
tokens.forEach { parent?.propertyChanged(it, oldStyle?.get(it), newStyle?.get(it)) }
|
||||
}
|
||||
if (this is VisualGroup) {
|
||||
this.forEach { it.styleChanged(key, oldStyle, newStyle) }
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
package hep.dataforge.vis.common
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.*
|
||||
import hep.dataforge.provider.Provider
|
||||
|
||||
@ -15,6 +14,8 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||
|
||||
override val defaultTarget: String get() = VisualObject.TYPE
|
||||
|
||||
val styleSheet: StyleSheet?
|
||||
|
||||
override fun provideTop(target: String): Map<Name, Any> =
|
||||
when (target) {
|
||||
VisualObject.TYPE -> children.flatMap { (key, value) ->
|
||||
@ -25,7 +26,7 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||
}
|
||||
res.entries
|
||||
}.associate { it.toPair() }
|
||||
//TODO add styles
|
||||
STYLE_TARGET -> styleSheet?.items?.mapKeys { it.key.toName() } ?: emptyMap()
|
||||
else -> emptyMap()
|
||||
}
|
||||
|
||||
@ -35,17 +36,6 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||
*/
|
||||
override fun iterator(): Iterator<VisualObject> = children.values.iterator()
|
||||
|
||||
/**
|
||||
* Resolve style by its name
|
||||
* TODO change to Config?
|
||||
*/
|
||||
fun getStyle(name: Name): Meta?
|
||||
|
||||
/**
|
||||
* Add or replace style with given name
|
||||
*/
|
||||
fun addStyle(name: Name, meta: Meta, apply: Boolean = true)
|
||||
|
||||
operator fun get(name: Name): VisualObject? {
|
||||
return when {
|
||||
name.isEmpty() -> this
|
||||
@ -53,8 +43,27 @@ interface VisualGroup : Provider, Iterable<VisualObject>, VisualObject {
|
||||
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]
|
||||
*/
|
||||
@ -75,4 +84,6 @@ interface MutableVisualGroup : VisualGroup {
|
||||
operator fun set(name: Name, child: VisualObject?)
|
||||
}
|
||||
|
||||
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: EmptyName)
|
||||
operator fun VisualGroup.get(str: String?) = get(str?.toName() ?: Name.EMPTY)
|
||||
|
||||
fun MutableVisualGroup.removeAll() = children.keys.map { it.asName() }.forEach { this[it] = null }
|
@ -15,7 +15,7 @@ import kotlinx.serialization.Transient
|
||||
* 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.
|
||||
@ -24,14 +24,16 @@ interface VisualObject : MetaRepr, Configurable {
|
||||
var parent: VisualObject?
|
||||
|
||||
/**
|
||||
* Direct properties access
|
||||
* All properties including styles and prototypes if present, but without inheritance
|
||||
*/
|
||||
val properties: Config?
|
||||
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
|
||||
@ -39,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
|
||||
@ -54,11 +58,9 @@ interface VisualObject : MetaRepr, Configurable {
|
||||
fun removeChangeListener(owner: Any?)
|
||||
|
||||
/**
|
||||
* List of names of styles applied to this object
|
||||
* List of names of styles applied to this object. Order matters. Not inherited
|
||||
*/
|
||||
var styles: List<Name>
|
||||
|
||||
fun findAllStyles(): Laminate = Laminate(styles.distinct().mapNotNull(::findStyle))
|
||||
var styles: List<String>
|
||||
|
||||
companion object {
|
||||
const val TYPE = "visual"
|
||||
@ -66,6 +68,7 @@ interface VisualObject : MetaRepr, Configurable {
|
||||
|
||||
//const val META_KEY = "@meta"
|
||||
//const val TAGS_KEY = "@tags"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,8 +83,33 @@ fun VisualObject.getProperty(key: String, inherit: Boolean = true): MetaItem<*>?
|
||||
fun VisualObject.setProperty(key: String, value: Any?) = setProperty(key.toName(), value)
|
||||
|
||||
/**
|
||||
* Apply style to [VisualObject] by adding it to the [style] list
|
||||
* 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.applyStyle(name: String) {
|
||||
styles = styles + name.toName()
|
||||
fun VisualObject.useStyle(name: String) {
|
||||
styles = styles + name
|
||||
}
|
||||
|
||||
//private tailrec fun VisualObject.topGroup(): VisualGroup? {
|
||||
// val parent = this.parent
|
||||
// return if (parent == null) {
|
||||
// this as? VisualGroup
|
||||
// }
|
||||
// else {
|
||||
// parent.topGroup()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
///**
|
||||
// * Add or update given style on a top-most reachable parent group and apply it to this object
|
||||
// */
|
||||
//fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||
// val styleName = name.toName()
|
||||
// topGroup()?.updateStyle(styleName, builder) ?: error("Can't find parent group for $this")
|
||||
// useStyle(styleName)
|
||||
//}
|
||||
|
||||
tailrec fun VisualObject.findStyle(name: String): Meta? =
|
||||
(this as? VisualGroup)?.styleSheet?.get(name) ?: parent?.findStyle(name)
|
||||
|
||||
fun VisualObject.findAllStyles(): Laminate = Laminate(styles.mapNotNull(::findStyle))
|
||||
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -1,14 +1,10 @@
|
||||
package hep.dataforge.vis
|
||||
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()
|
@ -1,4 +1,7 @@
|
||||
package hep.dataforge.vis
|
||||
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
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.spatial.editor
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import kotlinx.html.TagConsumer
|
||||
import kotlinx.html.js.div
|
@ -0,0 +1,74 @@
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import hep.dataforge.names.NameToken
|
||||
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.objectTree(
|
||||
token: NameToken,
|
||||
obj: VisualObject,
|
||||
clickCallback: (VisualObject) -> Unit = {}
|
||||
) {
|
||||
clear()
|
||||
append {
|
||||
card("Object tree") {
|
||||
subTree(token, obj, clickCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun TagConsumer<HTMLElement>.subTree(
|
||||
token: NameToken,
|
||||
obj: VisualObject,
|
||||
clickCallback: (VisualObject) -> Unit
|
||||
) {
|
||||
|
||||
if (obj is VisualGroup && !obj.isEmpty) {
|
||||
lateinit var toggle: HTMLSpanElement
|
||||
div("d-inline-block text-truncate") {
|
||||
toggle = span("objTree-caret")
|
||||
label("objTree-label") {
|
||||
+token.toString()
|
||||
onClickFunction = { clickCallback(obj) }
|
||||
}
|
||||
}
|
||||
val subtree = ul("objTree-subtree")
|
||||
toggle.onclick = {
|
||||
toggle.classList.toggle("objTree-caret-down")
|
||||
subtree.apply {
|
||||
if (toggle.classList.contains("objTree-caret-down")) {
|
||||
obj.children.entries
|
||||
.filter { !it.key.toString().startsWith("@") }
|
||||
.sortedBy { (it.value as? VisualGroup)?.isEmpty ?: true }
|
||||
.forEach { (token, child) ->
|
||||
append {
|
||||
li().apply {
|
||||
subTree(token, child, clickCallback)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.clear()
|
||||
}
|
||||
}
|
||||
//jQuery(subtree).asDynamic().collapse("toggle")
|
||||
}
|
||||
} else {
|
||||
div("d-inline-block text-truncate") {
|
||||
span("objTree-leaf")
|
||||
label("objTree-label") {
|
||||
+token.toString()
|
||||
onClickFunction = { clickCallback(obj) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,17 +1,12 @@
|
||||
package hep.dataforge.vis.spatial.editor
|
||||
package hep.dataforge.vis.js.editor
|
||||
|
||||
import hep.dataforge.io.toJson
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.js.jsObject
|
||||
import hep.dataforge.meta.DynamicMeta
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.findStyle
|
||||
import hep.dataforge.vis.jsObject
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||
import hep.dataforge.vis.spatial.color
|
||||
import hep.dataforge.vis.spatial.opacity
|
||||
import hep.dataforge.vis.spatial.prototype
|
||||
import hep.dataforge.vis.spatial.visible
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.div
|
||||
import kotlinx.html.js.h4
|
||||
@ -21,19 +16,13 @@ import kotlin.dom.clear
|
||||
//FIXME something rotten in JS-Meta converter
|
||||
fun Meta.toDynamic() = JSON.parse<dynamic>(toJson().toString())
|
||||
|
||||
|
||||
fun Element.propertyEditor(item: VisualObject?, name: String?) {
|
||||
//TODO add node descriptor instead of configuring property selector
|
||||
fun Element.propertyEditor(item: VisualObject?, propertySelector: (VisualObject) -> Meta = { it.config }) {
|
||||
clear()
|
||||
if (item != null) {
|
||||
append {
|
||||
card("Properties") {
|
||||
val config = (item.properties ?: item.prototype?.properties) ?: EmptyMeta
|
||||
val metaToEdit = config.builder().apply {
|
||||
VISIBLE_KEY to (item.visible ?: true)
|
||||
COLOR_KEY to (item.color ?: "#ffffff")
|
||||
OPACITY_KEY to (item.opacity ?: 1.0)
|
||||
}
|
||||
val dMeta: dynamic = metaToEdit.toDynamic()
|
||||
val dMeta: dynamic = propertySelector(item).toDynamic()
|
||||
val options: JSONEditorOptions = jsObject {
|
||||
mode = "form"
|
||||
onChangeJSON = { item.config.update(DynamicMeta(it.asDynamic())) }
|
||||
@ -46,13 +35,17 @@ fun Element.propertyEditor(item: VisualObject?, name: String?) {
|
||||
card("Styles") {
|
||||
item.styles.forEach { style ->
|
||||
val styleMeta = item.findStyle(style)
|
||||
h4("container") { +style.toString() }
|
||||
h4("container") { +style }
|
||||
if (styleMeta != null) {
|
||||
div("container").apply {
|
||||
val options: JSONEditorOptions = jsObject {
|
||||
mode = "view"
|
||||
}
|
||||
JSONEditor(this, options, styleMeta.toDynamic())
|
||||
JSONEditor(
|
||||
this,
|
||||
options,
|
||||
styleMeta.toDynamic()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
23
dataforge-vis-common/src/jsMain/resources/css/common.css
Normal file
23
dataforge-vis-common/src/jsMain/resources/css/common.css
Normal file
@ -0,0 +1,23 @@
|
||||
/* Remove default bullets */
|
||||
ul, .objTree-subtree {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
/* Style the caret/arrow */
|
||||
.objTree-caret {
|
||||
cursor: pointer;
|
||||
user-select: none; /* Prevent text selection */
|
||||
}
|
||||
|
||||
/* Create the caret/arrow with a unicode, and style it */
|
||||
.objTree-caret::before {
|
||||
content: "\25B6";
|
||||
color: black;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
|
||||
.objTree-caret-down::before {
|
||||
transform: rotate(90deg);
|
||||
}
|
@ -96,7 +96,7 @@ class FXPlugin(meta: Meta = EmptyMeta) : AbstractPlugin(meta) {
|
||||
companion object : PluginFactory<FXPlugin> {
|
||||
override val type: KClass<out FXPlugin> = FXPlugin::class
|
||||
override val tag: PluginTag = PluginTag("vis.fx", group = PluginTag.DATAFORGE_GROUP)
|
||||
override fun invoke(meta: Meta): FXPlugin = FXPlugin(meta)
|
||||
override fun invoke(meta: Meta, context: Context): FXPlugin = FXPlugin(meta)
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Null
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.asValue
|
||||
@ -39,9 +41,10 @@ class ColorValueChooser : ValueChooserBase<ColorPicker>() {
|
||||
return node
|
||||
}
|
||||
|
||||
companion object: ValueChooser.Factory{
|
||||
override val name: String = "color"
|
||||
companion object : ValueChooser.Factory {
|
||||
override val name: Name = "color".asName()
|
||||
|
||||
override fun invoke(meta: Meta): ValueChooser = ColorValueChooser()
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
ColorValueChooser()
|
||||
}
|
||||
}
|
@ -3,11 +3,13 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.value
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.Value
|
||||
import hep.dataforge.values.parseValue
|
||||
import javafx.collections.FXCollections
|
||||
@ -50,9 +52,10 @@ class ComboBoxValueChooser(val values: Collection<Value>? = null) : ValueChooser
|
||||
}
|
||||
|
||||
companion object : ValueChooser.Factory {
|
||||
override val name: String = "combo"
|
||||
override val name: Name = "combo".asName()
|
||||
|
||||
override fun invoke(meta: Meta): ValueChooser = ComboBoxValueChooser(meta["values"].value?.list)
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
ComboBoxValueChooser(meta["values"].value?.list)
|
||||
}
|
||||
|
||||
}
|
@ -3,20 +3,22 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.meta
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
import hep.dataforge.context.Global
|
||||
import hep.dataforge.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.vis.fx.dfIconView
|
||||
import hep.dataforge.vis.fx.values.ValueChooser
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.control.*
|
||||
import javafx.scene.control.cell.TextFieldTreeTableCell
|
||||
import javafx.scene.layout.HBox
|
||||
import javafx.scene.layout.Priority
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.text.Text
|
||||
import org.controlsfx.glyphfont.Glyph
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
@ -29,8 +31,9 @@ class ConfigEditor(
|
||||
val allowNew: Boolean = true,
|
||||
title: String = "Configuration editor"
|
||||
) : Fragment(title = title, icon = dfIconView) {
|
||||
//TODO replace parameters by properties
|
||||
|
||||
constructor(config: Config, descriptor: NodeDescriptor, title: String = "Configuration editor") :
|
||||
constructor(config: Config, descriptor: NodeDescriptor?, title: String = "Configuration editor") :
|
||||
this(FXMeta.root(config, descriptor = descriptor), title = title)
|
||||
|
||||
override val root = borderpane {
|
||||
@ -133,8 +136,9 @@ class ConfigEditor(
|
||||
is FXMetaNode<Config> -> {
|
||||
if (allowNew) {
|
||||
text = null
|
||||
graphic = hbox {
|
||||
button("node", Glyph("FontAwesome", "PLUS_CIRCLE")) {
|
||||
graphic = HBox().apply {
|
||||
val glyph: Node = FontAwesomeIconView(FontAwesomeIcon.PLUS_CIRCLE)
|
||||
button("node", graphic = glyph) {
|
||||
hgrow = Priority.ALWAYS
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
action {
|
||||
@ -143,7 +147,7 @@ class ConfigEditor(
|
||||
}
|
||||
}
|
||||
}
|
||||
button("value", Glyph("FontAwesome", "PLUS_SQUARE")) {
|
||||
button("value", graphic = FontAwesomeIconView(FontAwesomeIcon.PLUS_SQUARE)) {
|
||||
hgrow = Priority.ALWAYS
|
||||
maxWidth = Double.POSITIVE_INFINITY
|
||||
action {
|
@ -1,4 +1,4 @@
|
||||
package hep.dataforge.vis.fx.meta
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.descriptors.ItemDescriptor
|
||||
import hep.dataforge.descriptors.NodeDescriptor
|
||||
@ -84,7 +84,7 @@ class FXMetaNode<M : MetaNode<M>>(
|
||||
override val hasValue: ObservableBooleanValue = nodeProperty.booleanBinding { it != null }
|
||||
|
||||
private val filter: (FXMeta<M>) -> Boolean = { cfg ->
|
||||
!(cfg.descriptor?.tags?.contains(ConfigEditor.NO_CONFIGURATOR_TAG) ?: false)
|
||||
!(cfg.descriptor?.attributes?.get(ConfigEditor.NO_CONFIGURATOR_TAG)?.boolean ?: false)
|
||||
}
|
||||
|
||||
val children = object : ListBinding<FXMeta<M>>() {
|
||||
@ -172,7 +172,7 @@ fun <M : MutableMeta<M>> FXMetaNode<M>.remove(name: NameToken) {
|
||||
private fun <M : MutableMeta<M>> M.createEmptyNode(token: NameToken, append: Boolean): M {
|
||||
return if (append && token.index.isNotEmpty()) {
|
||||
val name = token.asName()
|
||||
val index = (getAll(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
|
||||
val index = (getIndexed(name).keys.mapNotNull { it.toIntOrNull() }.max() ?: -1) + 1
|
||||
val newName = name.withIndex(index.toString())
|
||||
set(newName, EmptyMeta)
|
||||
get(newName).node!!
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package hep.dataforge.vis.fx.meta
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.vis.fx.dfIconView
|
@ -3,9 +3,11 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.values.*
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.scene.control.TextField
|
||||
@ -100,7 +102,8 @@ class TextValueChooser : ValueChooserBase<TextField>() {
|
||||
}
|
||||
|
||||
companion object : ValueChooser.Factory {
|
||||
override val name: String = "text"
|
||||
override fun invoke(meta: Meta): ValueChooser = TextValueChooser()
|
||||
override val name: Name = "text".asName()
|
||||
override fun invoke(meta: Meta): ValueChooser =
|
||||
TextValueChooser()
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.values.Value
|
||||
|
@ -3,13 +3,14 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.Named
|
||||
import hep.dataforge.descriptors.ValueDescriptor
|
||||
import hep.dataforge.meta.EmptyMeta
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.provider.provideByType
|
||||
import hep.dataforge.values.Null
|
||||
@ -71,7 +72,7 @@ interface ValueChooser {
|
||||
companion object {
|
||||
|
||||
private fun findWidgetByType(context: Context, type: String): Factory? {
|
||||
return when (type) {
|
||||
return when (type.toName()) {
|
||||
TextValueChooser.name -> TextValueChooser
|
||||
ColorValueChooser.name -> ColorValueChooser
|
||||
ComboBoxValueChooser.name -> ComboBoxValueChooser
|
||||
@ -86,7 +87,10 @@ interface ValueChooser {
|
||||
val widgetType = descriptor.widgetType
|
||||
val chooser: ValueChooser = when {
|
||||
widgetType != null -> {
|
||||
findWidgetByType(context, widgetType)?.invoke(
|
||||
findWidgetByType(
|
||||
context,
|
||||
widgetType
|
||||
)?.invoke(
|
||||
descriptor.widget
|
||||
) ?: TextValueChooser()
|
||||
}
|
||||
@ -104,7 +108,8 @@ interface ValueChooser {
|
||||
descriptor: ValueDescriptor? = null,
|
||||
setter: (Value) -> Unit
|
||||
): ValueChooser {
|
||||
val chooser = build(context, descriptor)
|
||||
val chooser =
|
||||
build(context, descriptor)
|
||||
chooser.setDisplayValue(value.value ?: Null)
|
||||
value.onChange {
|
||||
chooser.setDisplayValue(it ?: Null)
|
||||
@ -114,7 +119,11 @@ interface ValueChooser {
|
||||
setter(result)
|
||||
ValueCallbackResponse(true, result, "OK")
|
||||
} else {
|
||||
ValueCallbackResponse(false, value.value ?: Null, "Not allowed")
|
||||
ValueCallbackResponse(
|
||||
false,
|
||||
value.value ?: Null,
|
||||
"Not allowed"
|
||||
)
|
||||
}
|
||||
}
|
||||
return chooser
|
@ -3,7 +3,7 @@
|
||||
* To change this template file, choose Tools | Templates
|
||||
* and open the template in the editor.
|
||||
*/
|
||||
package hep.dataforge.vis.fx.values
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.descriptors.ValueDescriptor
|
||||
import hep.dataforge.values.Null
|
@ -0,0 +1,74 @@
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.update
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.findStyle
|
||||
import javafx.beans.binding.Binding
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.layout.VBox
|
||||
import tornadofx.*
|
||||
|
||||
class VisualObjectEditorFragment(val selector: (VisualObject) -> Meta) : Fragment() {
|
||||
|
||||
val itemProperty = SimpleObjectProperty<VisualObject>()
|
||||
var item: VisualObject? by itemProperty
|
||||
val descriptorProperty = SimpleObjectProperty<NodeDescriptor>()
|
||||
|
||||
constructor(
|
||||
item: VisualObject?,
|
||||
descriptor: NodeDescriptor?,
|
||||
selector: (VisualObject) -> Config = { it.config }
|
||||
) : this(selector) {
|
||||
this.item = item
|
||||
this.descriptorProperty.set(descriptor)
|
||||
}
|
||||
|
||||
private var currentConfig: Config? = null
|
||||
|
||||
private val configProperty: Binding<Config?> = itemProperty.objectBinding { visualObject ->
|
||||
if (visualObject == null) return@objectBinding null
|
||||
val meta = selector(visualObject)
|
||||
val config = Config().apply {
|
||||
update(meta)
|
||||
onChange(this@VisualObjectEditorFragment) { key, _, after ->
|
||||
visualObject.setProperty(key, after)
|
||||
}
|
||||
}
|
||||
//remember old config reference to cleanup listeners
|
||||
currentConfig?.removeListener(this)
|
||||
currentConfig = config
|
||||
config
|
||||
}
|
||||
|
||||
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
|
||||
it?.let {
|
||||
ConfigEditor(it, descriptorProperty.get()).root
|
||||
}
|
||||
}
|
||||
|
||||
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding() {
|
||||
VBox().apply {
|
||||
item?.styles?.forEach { styleName ->
|
||||
val styleMeta = item?.findStyle(styleName)
|
||||
if (styleMeta != null) {
|
||||
titledpane(styleName, node = MetaViewer(styleMeta).root)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val root: Parent = vbox {
|
||||
titledpane("Properties", collapsible = false) {
|
||||
contentProperty().bind(configEditorProperty)
|
||||
}
|
||||
titledpane("Styles", collapsible = false) {
|
||||
visibleWhen(itemProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false })
|
||||
contentProperty().bind(styleBoxProperty)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package hep.dataforge.vis.fx.editor
|
||||
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.control.SelectionMode
|
||||
import javafx.scene.control.TreeItem
|
||||
import tornadofx.*
|
||||
|
||||
private fun toTreeItem(visualObject: VisualObject, title: String): TreeItem<Pair<String, VisualObject>> {
|
||||
return object : TreeItem<Pair<String, VisualObject>>(title to visualObject) {
|
||||
init {
|
||||
if (visualObject is VisualGroup) {
|
||||
//lazy populate the tree
|
||||
expandedProperty().onChange { expanded ->
|
||||
if (expanded && children.isEmpty()) {
|
||||
children.setAll(visualObject.children.map {
|
||||
toTreeItem(it.value, it.key.toString())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun isLeaf(): Boolean {
|
||||
return !(visualObject is VisualGroup && visualObject.children.isNotEmpty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class VisualObjectTreeFragment : Fragment() {
|
||||
val itemProperty = SimpleObjectProperty<VisualObject>()
|
||||
var item: VisualObject? by itemProperty
|
||||
|
||||
val selectedProperty = SimpleObjectProperty<VisualObject>()
|
||||
|
||||
override val root = vbox {
|
||||
titledpane("Object tree", collapsible = false) {
|
||||
treeview<Pair<String, VisualObject>> {
|
||||
cellFormat {
|
||||
text = item.first
|
||||
}
|
||||
itemProperty.onChange { rootObject ->
|
||||
if (rootObject != null) {
|
||||
root = toTreeItem(rootObject, "world")
|
||||
}
|
||||
}
|
||||
selectionModel.selectionMode = SelectionMode.SINGLE
|
||||
val selectedValue = selectionModel.selectedItemProperty().objectBinding { it?.value?.second }
|
||||
selectedProperty.bind(selectedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
@ -4,9 +4,9 @@ import hep.dataforge.descriptors.NodeDescriptor
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.meta.toConfig
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.fx.meta.ConfigEditor
|
||||
import hep.dataforge.vis.fx.meta.FXMeta
|
||||
import hep.dataforge.vis.fx.meta.MetaViewer
|
||||
import hep.dataforge.vis.fx.editor.ConfigEditor
|
||||
import hep.dataforge.vis.fx.editor.FXMeta
|
||||
import hep.dataforge.vis.fx.editor.MetaViewer
|
||||
import javafx.geometry.Orientation
|
||||
import tornadofx.*
|
||||
|
||||
@ -16,16 +16,16 @@ class MetaEditorDemoApp : App(MetaEditorDemo::class)
|
||||
class MetaEditorDemo : View("Meta editor demo") {
|
||||
|
||||
val meta = buildMeta {
|
||||
"aNode" to {
|
||||
"innerNode" to {
|
||||
"innerValue" to true
|
||||
"aNode" put {
|
||||
"innerNode" put {
|
||||
"innerValue" put true
|
||||
}
|
||||
"b" to 223
|
||||
"c" to "StringValue"
|
||||
"b" put 223
|
||||
"c" put "StringValue"
|
||||
}
|
||||
}.toConfig()
|
||||
|
||||
val descriptor = NodeDescriptor.build {
|
||||
val descriptor = NodeDescriptor {
|
||||
node("aNode") {
|
||||
info = "A root demo node"
|
||||
value("b") {
|
@ -1,23 +0,0 @@
|
||||
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||
import org.openjfx.gradle.JavaFXOptions
|
||||
|
||||
plugins {
|
||||
id("scientifik.jvm")
|
||||
id("org.openjfx.javafxplugin")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-common"))
|
||||
api("no.tornado:tornadofx:1.7.19")
|
||||
api("no.tornado:tornadofx-controlsfx:0.1")
|
||||
}
|
||||
|
||||
configure<JavaFXOptions> {
|
||||
modules("javafx.controls")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
kotlinOptions{
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
@ -74,5 +74,5 @@ class JSRootDemoApp : ApplicationBase() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" to presenter.dispose())
|
||||
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" put presenter.dispose())
|
||||
}
|
@ -15,37 +15,37 @@ class JSRootGeometry(parent: VisualObject?, meta: Meta) : DisplayLeaf(parent, me
|
||||
var facesLimit by int(0)
|
||||
|
||||
fun box(xSize: Number, ySize: Number, zSize: Number) = buildMeta {
|
||||
"_typename" to "TGeoBBox"
|
||||
"fDX" to xSize
|
||||
"fDY" to ySize
|
||||
"fDZ" to zSize
|
||||
"_typename" put "TGeoBBox"
|
||||
"fDX" put xSize
|
||||
"fDY" put ySize
|
||||
"fDZ" put zSize
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GDML union
|
||||
*/
|
||||
operator fun Meta.plus(other: Meta) = buildMeta {
|
||||
"fNode.fLeft" to this
|
||||
"fNode.fRight" to other
|
||||
"fNode._typename" to "TGeoUnion"
|
||||
"fNode.fLeft" put this
|
||||
"fNode.fRight" put other
|
||||
"fNode._typename" put "TGeoUnion"
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a GDML subtraction
|
||||
*/
|
||||
operator fun Meta.minus(other: Meta) = buildMeta {
|
||||
"fNode.fLeft" to this
|
||||
"fNode.fRight" to other
|
||||
"fNode._typename" to "TGeoSubtraction"
|
||||
"fNode.fLeft" put this
|
||||
"fNode.fRight" put other
|
||||
"fNode._typename" put "TGeoSubtraction"
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect two GDML geometries
|
||||
*/
|
||||
infix fun Meta.intersect(other: Meta) = buildMeta {
|
||||
"fNode.fLeft" to this
|
||||
"fNode.fRight" to other
|
||||
"fNode._typename" to "TGeoIntersection"
|
||||
"fNode.fLeft" put this
|
||||
"fNode.fRight" put other
|
||||
"fNode._typename" put "TGeoIntersection"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -2,10 +2,6 @@ plugins {
|
||||
id("scientifik.mpp")
|
||||
}
|
||||
|
||||
scientifik{
|
||||
withSerialization()
|
||||
}
|
||||
|
||||
kotlin {
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
@ -14,11 +10,11 @@ kotlin {
|
||||
api("scientifik:gdml:0.1.4")
|
||||
}
|
||||
}
|
||||
val jsMain by getting {
|
||||
dependencies {
|
||||
api(project(":dataforge-vis-spatial"))
|
||||
//api("kotlin.js.externals:kotlin-js-jquery:3.2.0-0")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//tasks{
|
||||
// val jsBrowserWebpack by getting(KotlinWebpack::class) {
|
||||
// sourceMaps = false
|
||||
// }
|
||||
//}
|
@ -5,20 +5,15 @@ import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.buildMeta
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.applyStyle
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
||||
import hep.dataforge.vis.spatial.RotationOrder
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.rotationOrder
|
||||
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,
|
||||
@ -29,7 +24,7 @@ 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
|
||||
@ -38,13 +33,20 @@ class GDMLTransformer(val root: GDML) {
|
||||
var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
|
||||
|
||||
|
||||
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { _, _ -> }
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualObject.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||
styleCache.getOrPut(name.toName()){
|
||||
fun VisualObject3D.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
|
||||
styleCache.getOrPut(name.toName()) {
|
||||
buildMeta(builder)
|
||||
}
|
||||
applyStyle(name)
|
||||
useStyle(name)
|
||||
}
|
||||
|
||||
internal fun configureSolid(obj: VisualObject3D, parent: GDMLVolume, solid: GDMLSolid) {
|
||||
@ -52,9 +54,9 @@ class GDMLTransformer(val root: GDML) {
|
||||
|
||||
val styleName = "material[${material.name}]"
|
||||
|
||||
obj.useStyle(styleName){
|
||||
COLOR_KEY to Colors.rgbToString(random.nextInt(0, Int.MAX_VALUE))
|
||||
"gdml.material" to material.name
|
||||
obj.useStyle(styleName) {
|
||||
MATERIAL_COLOR_KEY put random.nextInt(16777216)
|
||||
"gdml.material" put material.name
|
||||
}
|
||||
|
||||
obj.solidConfiguration(parent, solid)
|
||||
@ -67,9 +69,11 @@ class GDMLTransformer(val root: GDML) {
|
||||
var onFinish: GDMLTransformer.() -> Unit = {}
|
||||
|
||||
internal fun finalize(final: VisualGroup3D): VisualGroup3D {
|
||||
final.templates = templates
|
||||
final.prototypes = proto
|
||||
styleCache.forEach {
|
||||
final.addStyle(it.key, it.value, false)
|
||||
final.styleSheet {
|
||||
define(it.key.toString(), it.value)
|
||||
}
|
||||
}
|
||||
final.rotationOrder = RotationOrder.ZXY
|
||||
onFinish(this@GDMLTransformer)
|
||||
|
@ -1,10 +1,13 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.names.EmptyName
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
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
|
||||
@ -12,25 +15,28 @@ 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()
|
||||
newRotation?.let {
|
||||
val point = Point3D(it.x(), it.y(), it.z())
|
||||
if (rotation != null || point != ZERO) {
|
||||
rotation = point
|
||||
}
|
||||
//this@withPosition.rotationOrder = RotationOrder.ZXY
|
||||
}
|
||||
scale?.let {
|
||||
this@withPosition.scaleX = scale.x.toFloat()
|
||||
this@withPosition.scaleY = scale.y.toFloat()
|
||||
this@withPosition.scaleZ = scale.z.toFloat()
|
||||
newScale?.let {
|
||||
val point = Point3D(it.x, it.y, it.z)
|
||||
if (scale != null || point != ONE) {
|
||||
scale = point
|
||||
}
|
||||
}
|
||||
//TODO convert units if needed
|
||||
}
|
||||
@ -161,8 +167,8 @@ private fun VisualGroup3D.addPhysicalVolume(
|
||||
}
|
||||
GDMLTransformer.Action.CACHE -> {
|
||||
val fullName = volumesName + volume.name.asName()
|
||||
if (context.templates[fullName] == null) {
|
||||
context.templates[fullName] = volume(context, volume)
|
||||
if (context.proto[fullName] == null) {
|
||||
context.proto[fullName] = volume(context, volume)
|
||||
}
|
||||
|
||||
this[physVolume.name ?: ""] = Proxy(fullName).apply {
|
||||
@ -189,7 +195,7 @@ private fun VisualGroup3D.addDivisionVolume(
|
||||
|
||||
//TODO add divisions
|
||||
set(
|
||||
EmptyName,
|
||||
Name.EMPTY,
|
||||
volume(
|
||||
context,
|
||||
volume
|
||||
@ -215,8 +221,8 @@ private fun volume(
|
||||
}
|
||||
}
|
||||
GDMLTransformer.Action.CACHE -> {
|
||||
if (context.templates[solid.name] == null) {
|
||||
context.templates.addSolid(context, solid, solid.name) {
|
||||
if (context.proto[solid.name] == null) {
|
||||
context.proto.addSolid(context, solid, solid.name) {
|
||||
context.configureSolid(this, group, solid)
|
||||
}
|
||||
}
|
||||
@ -245,3 +251,12 @@ fun GDML.toVisual(block: GDMLTransformer.() -> Unit = {}): VisualGroup3D {
|
||||
|
||||
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)
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,13 +0,0 @@
|
||||
.loader {
|
||||
border: 16px solid #f3f3f3; /* Light grey */
|
||||
border-top: 16px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import kotlinx.serialization.*
|
||||
import kotlinx.serialization.json.*
|
||||
import kotlinx.serialization.modules.SerialModule
|
||||
import kotlinx.serialization.modules.SerialModuleCollector
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal val SerialDescriptor.jsonType
|
||||
get() = when (this.kind) {
|
||||
StructureKind.LIST -> "array"
|
||||
PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG,
|
||||
PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> "number"
|
||||
PrimitiveKind.STRING, PrimitiveKind.CHAR, UnionKind.ENUM_KIND -> "string"
|
||||
PrimitiveKind.BOOLEAN -> "boolean"
|
||||
else -> "object"
|
||||
}
|
||||
|
||||
|
||||
private fun SerialModule.enumerate(type: KClass<*>): Sequence<SerialDescriptor> {
|
||||
val list = ArrayList<SerialDescriptor>()
|
||||
fun send(descriptor: SerialDescriptor) = list.add(descriptor)
|
||||
|
||||
val enumerator = object : SerialModuleCollector {
|
||||
override fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>) {
|
||||
if (kClass == type) {
|
||||
send(serializer.descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun <Base : Any, Sub : Base> polymorphic(
|
||||
baseClass: KClass<Base>,
|
||||
actualClass: KClass<Sub>,
|
||||
actualSerializer: KSerializer<Sub>
|
||||
) {
|
||||
if (baseClass == type) {
|
||||
send(actualSerializer.descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
dumpTo(enumerator)
|
||||
return list.asSequence()
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an [JsonObject] which contains Json Schema of given [descriptor].
|
||||
*
|
||||
* Schema can contain following fields:
|
||||
* `description`, `type` for all descriptors;
|
||||
* `properties` and `required` for objects;
|
||||
* `enum` for enums;
|
||||
* `items` for arrays.
|
||||
*
|
||||
* User can modify this schema to add additional validation keywords
|
||||
* (as per [https://json-schema.org/latest/json-schema-validation.html])
|
||||
* if they want.
|
||||
*/
|
||||
private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): JsonObject {
|
||||
|
||||
if (descriptor.name in arrayOf(
|
||||
"hep.dataforge.vis.spatial.Point3D",
|
||||
"hep.dataforge.vis.spatial.Point2D",
|
||||
Meta::class.qualifiedName
|
||||
)
|
||||
) return json {
|
||||
"\$ref" to "#/definitions/${descriptor.name}"
|
||||
}
|
||||
|
||||
|
||||
val properties: MutableMap<String, JsonObject> = mutableMapOf()
|
||||
val requiredProperties: MutableSet<String> = mutableSetOf()
|
||||
val isEnum = descriptor.kind == UnionKind.ENUM_KIND
|
||||
val isPolymorphic = descriptor.kind is PolymorphicKind
|
||||
|
||||
|
||||
if (!isEnum && !isPolymorphic) descriptor.elementDescriptors().forEachIndexed { index, child ->
|
||||
val elementName = descriptor.getElementName(index)
|
||||
|
||||
properties[elementName] = when (elementName) {
|
||||
"templates" -> json {
|
||||
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
|
||||
}
|
||||
"properties" -> json {
|
||||
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
|
||||
}
|
||||
"first", "second" -> json{
|
||||
"\$ref" to "#/definitions/children"
|
||||
}
|
||||
"styleSheet" -> json {
|
||||
"type" to "object"
|
||||
"additionalProperties" to json {
|
||||
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
|
||||
}
|
||||
}
|
||||
in arrayOf("children") -> json {
|
||||
"type" to "object"
|
||||
"additionalProperties" to json {
|
||||
"\$ref" to "#/definitions/children"
|
||||
}
|
||||
}
|
||||
else -> jsonSchema(child, context)
|
||||
}
|
||||
|
||||
if (!descriptor.isElementOptional(index)) requiredProperties.add(elementName)
|
||||
}
|
||||
|
||||
val jsonType = descriptor.jsonType
|
||||
val objectData: MutableMap<String, JsonElement> = mutableMapOf(
|
||||
"description" to JsonLiteral(descriptor.name),
|
||||
"type" to JsonLiteral(jsonType)
|
||||
)
|
||||
if (isEnum) {
|
||||
val allElementNames = (0 until descriptor.elementsCount).map(descriptor::getElementName)
|
||||
objectData += "enum" to JsonArray(allElementNames.map(::JsonLiteral))
|
||||
}
|
||||
when (jsonType) {
|
||||
"object" -> {
|
||||
objectData["properties"] = JsonObject(properties)
|
||||
val required = requiredProperties.map { JsonLiteral(it) }
|
||||
if (required.isNotEmpty()) {
|
||||
objectData["required"] = JsonArray(required)
|
||||
}
|
||||
}
|
||||
"array" -> objectData["items"] = properties.values.let {
|
||||
check(it.size == 1) { "Array descriptor has returned inconsistent number of elements: expected 1, found ${it.size}" }
|
||||
it.first()
|
||||
}
|
||||
else -> { /* no-op */
|
||||
}
|
||||
}
|
||||
return JsonObject(objectData)
|
||||
}
|
||||
|
||||
fun main() {
|
||||
val context = Visual3DPlugin.serialModule
|
||||
val definitions = json {
|
||||
"children" to json {
|
||||
"anyOf" to jsonArray {
|
||||
context.enumerate(VisualObject3D::class).forEach {
|
||||
if (it.name == "hep.dataforge.vis.spatial.VisualGroup3D") {
|
||||
+json {
|
||||
"\$ref" to "#/definitions/${it.name}"
|
||||
}
|
||||
} else {
|
||||
+jsonSchema(it, context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"hep.dataforge.vis.spatial.Point3D" to jsonSchema(Point3DSerializer.descriptor, context)
|
||||
"hep.dataforge.vis.spatial.Point2D" to jsonSchema(Point2DSerializer.descriptor, context)
|
||||
"hep.dataforge.vis.spatial.VisualGroup3D" to jsonSchema(VisualGroup3D.serializer().descriptor, context)
|
||||
|
||||
}
|
||||
|
||||
println(
|
||||
Json.indented.stringify(
|
||||
JsonObjectSerializer,
|
||||
json {
|
||||
"definitions" to definitions
|
||||
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import nl.adaptivity.xmlutil.StAXReader
|
||||
import scientifik.gdml.GDML
|
||||
import java.io.File
|
||||
|
||||
|
||||
fun main() {
|
||||
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N_coil.gdml")
|
||||
|
||||
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
|
||||
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
|
||||
val visual = xml.toVisual {
|
||||
lUnit = LUnit.CM
|
||||
}
|
||||
|
||||
//val meta = visual.toMeta()
|
||||
|
||||
val str = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
|
||||
|
||||
println(str)
|
||||
|
||||
//println(Json.indented.stringify(meta.toJson()))
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.transform.RemoveSingleChild
|
||||
import hep.dataforge.vis.spatial.transform.UnRef
|
||||
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\\cubes.gdml")
|
||||
|
||||
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
|
||||
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
|
||||
val visual = xml.toVisual {
|
||||
lUnit = LUnit.CM
|
||||
|
||||
solidConfiguration = { parent, solid ->
|
||||
if (parent.physVolumes.isNotEmpty()) {
|
||||
useStyle("opaque") {
|
||||
Material3D.OPACITY_KEY to 0.3
|
||||
VisualObject3D.LAYER_KEY to 2
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(visual as? VisualGroup3D)?.let { UnRef(it) }?.let { RemoveSingleChild(it) }
|
||||
|
||||
val string = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
|
||||
|
||||
val tmpFile = File.createTempFile("dataforge-visual", ".json")
|
||||
|
||||
tmpFile.writeText(string)
|
||||
|
||||
println(tmpFile.canonicalPath)
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import hep.dataforge.vis.spatial.Visual3DPlugin
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import hep.dataforge.vis.spatial.opacity
|
||||
import hep.dataforge.vis.spatial.transform.RemoveSingleChild
|
||||
import hep.dataforge.vis.spatial.transform.UnRef
|
||||
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 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("ecal01lay") -> GDMLTransformer.Action.REJECT
|
||||
else -> GDMLTransformer.Action.CACHE
|
||||
}
|
||||
}
|
||||
|
||||
solidConfiguration = { parent, solid ->
|
||||
if (parent.physVolumes.isNotEmpty()
|
||||
|| solid.name.startsWith("Coil")
|
||||
|| solid.name.startsWith("Yoke")
|
||||
|| solid.name.startsWith("Magnet")
|
||||
|| solid.name.startsWith("Pole")
|
||||
) {
|
||||
useStyle("opaque") {
|
||||
Material3D.OPACITY_KEY to 0.3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// (visual as? VisualGroup3D)?.let { UnRef(it) }?.let { RemoveSingleChild(it) }
|
||||
|
||||
val string = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
|
||||
|
||||
val tmpFile = File.createTempFile("dataforge-visual", ".json")
|
||||
|
||||
tmpFile.writeText(string)
|
||||
|
||||
println(tmpFile.canonicalPath)
|
||||
|
||||
// val template = visual.getTemplate("volumes.ecal01mod".toName())
|
||||
// println(template)
|
||||
// visual.flatMap { (it as? VisualGroup3D) ?: listOf(it) }.forEach {
|
||||
// if(it.parent==null) error("")
|
||||
// }
|
||||
//readLine()
|
||||
//val meta = visual.toMeta()
|
||||
// val tmpFile = File.createTempFile("dataforge-visual", "json")
|
||||
//tmpFile.writeText(meta.toString())
|
||||
//println(tmpFile.absoluteFile)
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package hep.dataforge.vis.spatial.gdml
|
||||
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import nl.adaptivity.xmlutil.StAXReader
|
||||
import scientifik.gdml.GDML
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
fun GDML.Companion.readFile(file: Path): GDML {
|
||||
val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8")
|
||||
return GDML.format.parse(GDML.serializer(), xmlReader)
|
||||
}
|
||||
|
||||
fun VisualGroup3D.gdml(file: Path, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
|
||||
val gdml = GDML.readFile(file)
|
||||
gdml(gdml, key, transformer)
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
import org.openjfx.gradle.JavaFXOptions
|
||||
import scientifik.useSerialization
|
||||
|
||||
plugins {
|
||||
id("scientifik.mpp")
|
||||
id("org.openjfx.javafxplugin")
|
||||
}
|
||||
|
||||
scientifik {
|
||||
withSerialization()
|
||||
}
|
||||
useSerialization()
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
@ -21,13 +20,18 @@ kotlin {
|
||||
}
|
||||
jvmMain {
|
||||
dependencies {
|
||||
implementation(project(":dataforge-vis-fx"))
|
||||
implementation("org.fxyz3d:fxyz3d:0.5.2")
|
||||
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(project(":wrappers"))
|
||||
// api(project(":wrappers"))
|
||||
implementation(npm("three", "0.106.2"))
|
||||
implementation(npm("@hi-level/three-csg", "1.0.6"))
|
||||
}
|
||||
|
@ -2,16 +2,21 @@
|
||||
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
|
||||
@SerialName("3d.box")
|
||||
class Box(
|
||||
val xSize: Float,
|
||||
val ySize: Float,
|
||||
@ -27,9 +32,9 @@ 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 @@ 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"
|
||||
|
||||
|
@ -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
|
||||
@ -33,13 +33,6 @@ class Composite(
|
||||
|
||||
@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(
|
||||
@ -51,13 +44,18 @@ inline fun VisualGroup3D.composite(
|
||||
val children = group.filterIsInstance<VisualObject3D>()
|
||||
if (children.size != 2) error("Composite requires exactly two children")
|
||||
return Composite(type, children[0], children[1]).also {
|
||||
if (group.properties != null) {
|
||||
it.config.update(group.config)
|
||||
it.material = group.material
|
||||
it.config.update(group.config)
|
||||
//it.material = group.material
|
||||
|
||||
if(group.position!=null) {
|
||||
it.position = group.position
|
||||
}
|
||||
if(group.rotation!=null) {
|
||||
it.rotation = group.rotation
|
||||
}
|
||||
if(group.scale!=null) {
|
||||
it.scale = group.scale
|
||||
}
|
||||
it.position = group.position
|
||||
it.rotation = group.rotation
|
||||
it.scale = group.scale
|
||||
set(name, it)
|
||||
}
|
||||
}
|
||||
|
@ -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,6 +28,49 @@ 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(
|
||||
|
@ -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,13 +18,6 @@ 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"
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) }
|
@ -1,56 +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.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.COLOR_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.OPACITY_KEY
|
||||
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 {
|
||||
|
||||
val color by string()
|
||||
var color by string(key = COLOR_KEY)
|
||||
|
||||
val opacity by float(1f)
|
||||
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()
|
||||
val COLOR_KEY = MATERIAL_KEY + "color"
|
||||
val OPACITY_KEY = MATERIAL_KEY + "opacity"
|
||||
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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fun VisualObject.color(rgb: String) {
|
||||
setProperty(COLOR_KEY, rgb)
|
||||
}
|
||||
|
||||
fun VisualObject.color(rgb: Int) = color(Colors.rgbToString(rgb))
|
||||
|
||||
fun VisualObject.color(r: UByte, g: UByte, b: UByte) = color( Colors.rgbToString(r,g,b))
|
||||
|
||||
var VisualObject.color: String?
|
||||
get() = getProperty(COLOR_KEY).string
|
||||
set(value) {
|
||||
if (value != null) {
|
||||
color(value)
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var VisualObject.material: Material3D?
|
||||
get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
|
||||
set(value) = setProperty(MATERIAL_KEY, value?.config)
|
||||
|
||||
fun VisualObject.material(builder: Material3D.() -> Unit) {
|
||||
material = Material3D.build(builder)
|
||||
}
|
||||
|
||||
var VisualObject.opacity: Double?
|
||||
get() = getProperty(OPACITY_KEY).double
|
||||
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(OPACITY_KEY, 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)
|
||||
}
|
@ -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 hep.dataforge.vis.common.number
|
||||
@ -20,6 +20,7 @@ class PolyLine(var points: List<Point3D>) : AbstractVisualObject(), VisualObject
|
||||
|
||||
//var lineType by string()
|
||||
var thickness by number(1.0, key = "material.thickness")
|
||||
|
||||
}
|
||||
|
||||
fun VisualGroup3D.polyline(vararg points: Point3D, name: String = "", action: PolyLine.() -> Unit = {}) =
|
||||
|
@ -2,18 +2,20 @@
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.NameSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.NameSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.Laminate
|
||||
import hep.dataforge.meta.MetaItem
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import hep.dataforge.vis.common.MutableVisualGroup
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
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
|
||||
@ -23,6 +25,7 @@ import kotlin.collections.set
|
||||
* A proxy [VisualObject3D] to reuse a template object
|
||||
*/
|
||||
@Serializable
|
||||
@SerialName("3d.proxy")
|
||||
class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, VisualObject3D {
|
||||
|
||||
override var position: Point3D? = null
|
||||
@ -36,22 +39,18 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
* Recursively search for defined template in the parent
|
||||
*/
|
||||
val prototype: VisualObject3D
|
||||
get() = (parent as? VisualGroup3D)?.getTemplate(templateName)
|
||||
get() = (parent as? VisualGroup3D)?.getPrototype(templateName)
|
||||
?: error("Template with name $templateName not found in $parent")
|
||||
|
||||
override fun getStyle(name: Name): Meta? = (parent as VisualGroup?)?.getStyle(name)
|
||||
|
||||
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
|
||||
(parent as VisualGroup?)?.addStyle(name, meta, apply)
|
||||
//do nothing
|
||||
}
|
||||
override val styleSheet: StyleSheet
|
||||
get() = (parent as? VisualGroup)?.styleSheet ?: StyleSheet(this)
|
||||
|
||||
override fun getProperty(name: Name, inherit: Boolean): MetaItem<*>? {
|
||||
return if (inherit) {
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
?: prototype.getProperty(name, false)
|
||||
?: parent?.getProperty(name, inherit)
|
||||
?: prototype.getProperty(name)
|
||||
?: parent?.getProperty(name)
|
||||
} else {
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
@ -59,41 +58,35 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
}
|
||||
}
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
//TODO add reference to child
|
||||
updatePosition()
|
||||
}
|
||||
|
||||
override val children: Map<NameToken, ProxyChild>
|
||||
get() = (prototype as? MutableVisualGroup)?.children?.mapValues {
|
||||
ProxyChild(it.key.asName())
|
||||
} ?: emptyMap()
|
||||
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 =
|
||||
(prototype as? VisualGroup)?.get(name)
|
||||
?: error("Prototype with name $name not found in ${this@Proxy}")
|
||||
private fun prototypeFor(name: Name): VisualObject {
|
||||
return (prototype as? VisualGroup)?.get(name)
|
||||
?: error("Prototype with name $name not found in $this")
|
||||
}
|
||||
|
||||
|
||||
override var styles: List<Name>
|
||||
get() = super.styles + prototype.styles
|
||||
set(value) {
|
||||
setProperty(VisualObject.STYLE_KEY, value.map { it.toString() })
|
||||
styleChanged()
|
||||
}
|
||||
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 by lazy {
|
||||
prototypeFor(name)
|
||||
}
|
||||
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, _) ->
|
||||
@ -102,12 +95,6 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
)
|
||||
} ?: emptyMap()
|
||||
|
||||
override fun getStyle(name: Name): Meta? = this@Proxy.getStyle(name)
|
||||
|
||||
override fun addStyle(name: Name, meta: Meta, apply: Boolean) {
|
||||
this@Proxy.addStyle(name, meta, apply)
|
||||
}
|
||||
|
||||
override var properties: Config?
|
||||
get() = propertyCache[name]
|
||||
set(value) {
|
||||
@ -129,15 +116,18 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
return if (inherit) {
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
?: prototype.getProperty(name, inherit)
|
||||
?: parent?.getProperty(name, inherit)
|
||||
?: prototype.getProperty(name)
|
||||
?: parent?.getProperty(name)
|
||||
} else {
|
||||
properties?.get(name)
|
||||
?: mergedStyles[name]
|
||||
?: prototype.getProperty(name, inherit)
|
||||
?: prototype.getProperty(name, false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -145,15 +135,36 @@ class Proxy(val templateName: Name) : AbstractVisualObject(), VisualGroup, Visua
|
||||
}
|
||||
}
|
||||
|
||||
val VisualObject.prototype: VisualObject?
|
||||
val VisualObject.prototype: VisualObject
|
||||
get() = when (this) {
|
||||
is Proxy -> prototype
|
||||
is Proxy.ProxyChild -> prototype
|
||||
else -> null
|
||||
else -> this
|
||||
}
|
||||
|
||||
/**
|
||||
* Create ref for existing prototype
|
||||
*/
|
||||
inline fun VisualGroup3D.ref(
|
||||
templateName: Name,
|
||||
name: String = "",
|
||||
action: Proxy.() -> Unit = {}
|
||||
) = Proxy(templateName).apply(action).also { set(name, it) }
|
||||
block: Proxy.() -> Unit = {}
|
||||
) = Proxy(templateName).apply(block).also { set(name, it) }
|
||||
|
||||
/**
|
||||
* Add new proxy wrapping given object and automatically adding it to the prototypes
|
||||
*/
|
||||
fun VisualGroup3D.proxy(
|
||||
templateName: Name,
|
||||
obj: VisualObject3D,
|
||||
name: String = "",
|
||||
block: Proxy.() -> Unit = {}
|
||||
): Proxy {
|
||||
val existing = getPrototype(templateName)
|
||||
if (existing == null) {
|
||||
setPrototype(templateName, obj)
|
||||
} else if (existing != obj) {
|
||||
error("Can't add different prototype on top of existing one")
|
||||
}
|
||||
return ref(templateName, name, block)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.serialization.ConfigSerializer
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.vis.common.AbstractVisualObject
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -16,7 +16,7 @@ class Sphere(
|
||||
var phi: Float = PI2,
|
||||
var thetaStart: Float = 0f,
|
||||
var theta: Float = PI.toFloat()
|
||||
) : AbstractVisualObject(), VisualObject3D {
|
||||
) : AbstractVisualObject(), VisualObject3D, Shape {
|
||||
|
||||
@Serializable(ConfigSerializer::class)
|
||||
override var properties: Config? = null
|
||||
@ -24,6 +24,19 @@ 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(
|
||||
|
@ -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(
|
||||
|
@ -1,11 +1,12 @@
|
||||
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.ConfigSerializer
|
||||
import hep.dataforge.io.MetaSerializer
|
||||
import hep.dataforge.io.NameSerializer
|
||||
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
|
||||
@ -30,14 +31,14 @@ 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 {
|
||||
contextual(Point3DSerializer)
|
||||
contextual(Point2DSerializer)
|
||||
contextual(NameSerializer)
|
||||
contextual(NameTokenSerializer)
|
||||
contextual(Meta::class, MetaSerializer)
|
||||
contextual(MetaSerializer)
|
||||
contextual(ConfigSerializer)
|
||||
|
||||
polymorphic(VisualObject::class, VisualObject3D::class) {
|
||||
@ -47,7 +48,8 @@ class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
|
||||
Tube::class with Tube.serializer()
|
||||
Box::class with Box.serializer()
|
||||
Convex::class with Convex.serializer()
|
||||
addSubclass(Extruded.serializer())
|
||||
Extruded::class with Extruded.serializer()
|
||||
addSubclass(Label3D.serializer())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,19 +8,16 @@
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.ConfigSerializer
|
||||
import hep.dataforge.io.MetaSerializer
|
||||
import hep.dataforge.io.NameSerializer
|
||||
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.meta.Meta
|
||||
import hep.dataforge.meta.MetaBuilder
|
||||
import hep.dataforge.meta.set
|
||||
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.VisualGroup
|
||||
import hep.dataforge.vis.common.StyleSheet
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
@ -31,19 +28,23 @@ 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
|
||||
*/
|
||||
var templates: VisualGroup3D? = null
|
||||
@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 val styleSheet = HashMap<Name, Meta>()
|
||||
|
||||
override var position: Point3D? = null
|
||||
override var rotation: Point3D? = null
|
||||
@ -53,6 +54,19 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||
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)
|
||||
@ -88,36 +102,42 @@ class VisualGroup3D : AbstractVisualGroup(), VisualObject3D {
|
||||
}
|
||||
}
|
||||
|
||||
fun getTemplate(name: Name): VisualObject3D? =
|
||||
templates?.get(name) as? VisualObject3D
|
||||
?: (parent as? VisualGroup3D)?.getTemplate(name)
|
||||
|
||||
override fun MetaBuilder.updateMeta() {
|
||||
set(TEMPLATES_KEY, templates?.toMeta())
|
||||
updatePosition()
|
||||
updateChildren()
|
||||
override fun attachChildren() {
|
||||
super.attachChildren()
|
||||
prototypes?.run {
|
||||
parent = this
|
||||
attachChildren()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TEMPLATES_KEY = "templates"
|
||||
const val PROTOTYPES_KEY = "templates"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A fix for serialization bug that writes all proper parents inside the tree after deserialization
|
||||
* Ger a prototype redirecting the request to the parent if prototype is not found
|
||||
*/
|
||||
fun VisualGroup.attachChildren() {
|
||||
this.children.values.forEach {
|
||||
it.parent = this
|
||||
(it as? VisualGroup)?.attachChildren()
|
||||
}
|
||||
if (this is VisualGroup3D) {
|
||||
templates?.also {
|
||||
it.parent = this
|
||||
it.attachChildren()
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -2,15 +2,15 @@
|
||||
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.io.NameSerializer
|
||||
import hep.dataforge.io.serialization.NameSerializer
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
import hep.dataforge.output.Output
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.DETAIL_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.LAYER_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.SELECTED_KEY
|
||||
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
|
||||
import kotlinx.serialization.UseSerializers
|
||||
@ -43,7 +43,7 @@ interface VisualObject3D : VisualObject {
|
||||
val LAYER_KEY = "layer".asName()
|
||||
val IGNORE_KEY = "ignore".asName()
|
||||
|
||||
val GEOMETRY_KEY = "geometey".asName()
|
||||
val GEOMETRY_KEY = "geometry".asName()
|
||||
|
||||
val x = "x".asName()
|
||||
val y = "y".asName()
|
||||
@ -80,7 +80,7 @@ var VisualObject3D.layer: Int
|
||||
setProperty(LAYER_KEY, value)
|
||||
}
|
||||
|
||||
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
|
||||
@ -132,21 +132,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 =
|
||||
@ -156,21 +156,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 =
|
||||
@ -180,19 +180,19 @@ var VisualObject3D.scaleX: Number
|
||||
get() = scale?.x ?: 1f
|
||||
set(value) {
|
||||
scale().x = value.toDouble()
|
||||
propertyChanged(VisualObject3D.xScale)
|
||||
propertyInvalidated(VisualObject3D.xScale)
|
||||
}
|
||||
|
||||
var VisualObject3D.scaleY: Number
|
||||
get() = scale?.y ?: 1f
|
||||
set(value) {
|
||||
scale().y = value.toDouble()
|
||||
propertyChanged(VisualObject3D.yScale)
|
||||
propertyInvalidated(VisualObject3D.yScale)
|
||||
}
|
||||
|
||||
var VisualObject3D.scaleZ: Number
|
||||
get() = scale?.z ?: 1f
|
||||
set(value) {
|
||||
scale().z = value.toDouble()
|
||||
propertyChanged(VisualObject3D.zScale)
|
||||
propertyInvalidated(VisualObject3D.zScale)
|
||||
}
|
@ -3,12 +3,8 @@ package hep.dataforge.vis.spatial
|
||||
import kotlin.math.PI
|
||||
|
||||
object World {
|
||||
const val CAMERA_INITIAL_DISTANCE = -500.0
|
||||
const val CAMERA_INITIAL_X_ANGLE = -50.0
|
||||
const val CAMERA_INITIAL_Y_ANGLE = 0.0
|
||||
const val CAMERA_INITIAL_Z_ANGLE = -210.0
|
||||
const val CAMERA_NEAR_CLIP = 0.1
|
||||
const val CAMERA_FAR_CLIP = 10000.0
|
||||
val ZERO = Point3D(0.0, 0.0, 0.0)
|
||||
val ONE = Point3D(1.0, 1.0, 1.0)
|
||||
}
|
||||
|
||||
const val PI2: Float = 2 * PI.toFloat()
|
@ -14,8 +14,8 @@ operator fun Point2D.component1() = x
|
||||
operator fun Point2D.component2() = y
|
||||
|
||||
fun Point2D.toMeta() = buildMeta {
|
||||
VisualObject3D.x to x
|
||||
VisualObject3D.y to y
|
||||
VisualObject3D.x put x
|
||||
VisualObject3D.y put y
|
||||
}
|
||||
|
||||
fun Meta.point2D() = Point2D(this["x"].number ?: 0, this["y"].number ?: 0)
|
||||
@ -26,23 +26,7 @@ expect class Point3D(x: Number, y: Number, z: Number) {
|
||||
var z: Double
|
||||
}
|
||||
|
||||
operator fun Point3D?.plus(other: Point3D?): Point3D? {
|
||||
return when {
|
||||
this == null && other == null -> null
|
||||
this == null -> other
|
||||
other == null -> this
|
||||
else -> Point3D(x + other.x, y + other.y, z + other.z)
|
||||
}
|
||||
}
|
||||
|
||||
operator fun Point3D?.minus(other: Point3D?): Point3D? {
|
||||
return when {
|
||||
this == null && other == null -> null
|
||||
this == null -> Point3D(-other!!.x, -other.y, -other.z)
|
||||
other == null -> this
|
||||
else -> Point3D(x - other.x, y - other.y, z - other.z)
|
||||
}
|
||||
}
|
||||
expect operator fun Point3D.plus(other: Point3D): Point3D
|
||||
|
||||
operator fun Point3D.component1() = x
|
||||
operator fun Point3D.component2() = y
|
||||
@ -50,10 +34,8 @@ operator fun Point3D.component3() = z
|
||||
|
||||
fun Meta.point3D() = Point3D(this["x"].number ?: 0, this["y"].number ?: 0, this["y"].number ?: 0)
|
||||
|
||||
val zero = Point3D(0, 0, 0)
|
||||
|
||||
fun Point3D.toMeta() = buildMeta {
|
||||
VisualObject3D.x to x
|
||||
VisualObject3D.y to y
|
||||
VisualObject3D.z to z
|
||||
VisualObject3D.x put x
|
||||
VisualObject3D.y put y
|
||||
VisualObject3D.z put z
|
||||
}
|
@ -1,45 +1,97 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,17 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
|
||||
class AxesSpec(override val config: Config) : Specific {
|
||||
var visible by boolean(!config.isEmpty())
|
||||
var size by double(AXIS_SIZE)
|
||||
var width by double(AXIS_WIDTH)
|
||||
|
||||
companion object : Specification<AxesSpec> {
|
||||
override fun wrap(config: Config): AxesSpec = AxesSpec(config)
|
||||
|
||||
const val AXIS_SIZE = 1000.0
|
||||
const val AXIS_WIDTH = 3.0
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import kotlin.math.PI
|
||||
|
||||
class CameraSpec(override val config: Config) : Specific {
|
||||
var fov by int(FIELD_OF_VIEW)
|
||||
//var aspect by double(1.0)
|
||||
var nearClip by double(NEAR_CLIP)
|
||||
var farClip by double(FAR_CLIP)
|
||||
|
||||
var distance by double(INITIAL_DISTANCE)
|
||||
var azimuth by double(INITIAL_AZIMUTH)
|
||||
var latitude by double(INITIAL_LATITUDE)
|
||||
val zenith: Double get() = PI / 2 - latitude
|
||||
|
||||
companion object : Specification<CameraSpec> {
|
||||
override fun wrap(config: Config): CameraSpec = CameraSpec(config)
|
||||
const val INITIAL_DISTANCE = 300.0
|
||||
const val INITIAL_AZIMUTH = 0.0
|
||||
const val INITIAL_LATITUDE = PI/6
|
||||
const val NEAR_CLIP = 0.1
|
||||
const val FAR_CLIP = 10000.0
|
||||
const val FIELD_OF_VIEW = 75
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
|
||||
class CanvasSpec(override val config: Config) : Specific {
|
||||
var axes by spec(AxesSpec)
|
||||
var camera by spec(CameraSpec)
|
||||
var controls by spec(ControlsSpec)
|
||||
var minSize by int(300)
|
||||
|
||||
companion object: Specification<CanvasSpec>{
|
||||
override fun wrap(config: Config): CanvasSpec = CanvasSpec(config)
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package hep.dataforge.vis.spatial.specifications
|
||||
|
||||
import hep.dataforge.meta.Config
|
||||
import hep.dataforge.meta.Specific
|
||||
import hep.dataforge.meta.Specification
|
||||
|
||||
class ControlsSpec(override val config: Config) : Specific {
|
||||
companion object : Specification<ControlsSpec> {
|
||||
override fun wrap(config: Config): ControlsSpec = ControlsSpec(config)
|
||||
}
|
||||
}
|
@ -10,11 +10,13 @@ import hep.dataforge.vis.spatial.*
|
||||
internal fun mergeChild(parent: VisualGroup, child: VisualObject): VisualObject {
|
||||
return child.apply {
|
||||
|
||||
parent.properties?.let { config.update(it) }
|
||||
config.update(parent.config)
|
||||
|
||||
//parent.properties?.let { config.update(it) }
|
||||
|
||||
if (this is VisualObject3D && parent is VisualObject3D) {
|
||||
position += parent.position
|
||||
rotation += parent.rotation
|
||||
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
|
||||
@ -49,7 +51,7 @@ object RemoveSingleChild : VisualTreeTransform<VisualGroup3D>() {
|
||||
}
|
||||
|
||||
replaceChildren()
|
||||
templates?.replaceChildren()
|
||||
prototypes?.replaceChildren()
|
||||
}
|
||||
|
||||
override fun VisualGroup3D.clone(): VisualGroup3D {
|
||||
|
@ -24,7 +24,7 @@ object UnRef : VisualTreeTransform<VisualGroup3D>() {
|
||||
}
|
||||
|
||||
private fun MutableVisualGroup.unref(name: Name) {
|
||||
(this as? VisualGroup3D)?.templates?.set(name, null)
|
||||
(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)
|
||||
|
@ -1,9 +1,8 @@
|
||||
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
|
||||
|
||||
@ -26,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)
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ class GroupTest {
|
||||
}
|
||||
box(100, 100, 100)
|
||||
material {
|
||||
"color" to Colors.lightgreen
|
||||
"opacity" to 0.3
|
||||
color(Colors.lightgreen)
|
||||
opacity = 0.3f
|
||||
}
|
||||
}
|
||||
intersect("intersect") {
|
||||
@ -46,6 +46,6 @@ class GroupTest {
|
||||
|
||||
assertEquals(3, group.count())
|
||||
assertEquals(300.0, (group["intersect"] as VisualObject3D).y.toDouble())
|
||||
assertEquals(-300.0, (group["subtract"] as VisualObject3D).y.toDouble())
|
||||
assertEquals(-300.0, (group["subtract"] as VisualObject3D).y.toDouble())
|
||||
}
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package hep.dataforge.vis.spatial
|
||||
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.meta.set
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.vis.common.useStyle
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class PropertyTest {
|
||||
@Test
|
||||
fun testInheritedProperty() {
|
||||
var box: Box? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
config["test"] = 22
|
||||
group {
|
||||
box = box(100, 100, 100)
|
||||
}
|
||||
}
|
||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testStyleProperty() {
|
||||
var box: Box? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
"test" put 22
|
||||
}
|
||||
}
|
||||
group {
|
||||
box = box(100, 100, 100).apply {
|
||||
useStyle("testStyle")
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals(22, box?.getProperty("test".asName()).int)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testColor() {
|
||||
var box: Box? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
Material3D.MATERIAL_COLOR_KEY put "#555555"
|
||||
}
|
||||
}
|
||||
group {
|
||||
box = box(100, 100, 100) {
|
||||
useStyle("testStyle")
|
||||
}
|
||||
}
|
||||
}
|
||||
assertEquals("#555555", box?.color)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testProxyStyleProperty() {
|
||||
var box: Proxy? = null
|
||||
val group = VisualGroup3D().apply {
|
||||
styleSheet {
|
||||
set("testStyle") {
|
||||
Material3D.MATERIAL_COLOR_KEY put "#555555"
|
||||
}
|
||||
}
|
||||
prototypes {
|
||||
box(100, 100, 100, name = "box") {
|
||||
styles = listOf("testStyle")
|
||||
}
|
||||
}
|
||||
group {
|
||||
box = ref("box".asName())
|
||||
}
|
||||
}
|
||||
assertEquals("#555555", box?.color)
|
||||
}
|
||||
}
|
@ -8,13 +8,15 @@ import kotlin.test.assertEquals
|
||||
class SerializationTest {
|
||||
@ImplicitReflectionSerializer
|
||||
@Test
|
||||
fun testCubeSerialization(){
|
||||
val cube = Box(100f,100f,100f).apply{
|
||||
fun testCubeSerialization() {
|
||||
val cube = Box(100f, 100f, 100f).apply {
|
||||
color(222)
|
||||
x = 100
|
||||
z = -100
|
||||
}
|
||||
val string = json.stringify(Box.serializer(),cube)
|
||||
val string = json.stringify(Box.serializer(), cube)
|
||||
println(string)
|
||||
val newCube = json.parse(Box.serializer(),string)
|
||||
assertEquals(cube.toMeta(),newCube.toMeta())
|
||||
val newCube = json.parse(Box.serializer(), string)
|
||||
assertEquals(cube.config, newCube.config)
|
||||
}
|
||||
}
|
@ -1,137 +0,0 @@
|
||||
@file:Suppress("UNCHECKED_CAST_TO_EXTERNAL_INTERFACE")
|
||||
|
||||
package hep.dataforge.vis.spatial.editor
|
||||
|
||||
import hep.dataforge.meta.string
|
||||
import hep.dataforge.names.EmptyName
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.NameToken
|
||||
import hep.dataforge.vis.common.VisualGroup
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.common.getProperty
|
||||
import hep.dataforge.vis.jsObject
|
||||
import hep.dataforge.vis.spatial.Proxy
|
||||
import hep.dataforge.vis.spatial.visible
|
||||
import info.laht.threekt.loaders.Cache.clear
|
||||
import kotlinx.html.div
|
||||
import kotlinx.html.dom.append
|
||||
import org.w3c.dom.Element
|
||||
import kotlin.dom.clear
|
||||
import kotlin.js.json
|
||||
|
||||
operator fun Name.plus(other: NameToken): Name = Name(tokens + other)
|
||||
|
||||
|
||||
private fun createInspireTree(block: Config.() -> Unit = {}): InspireTree {
|
||||
val config = (json(
|
||||
"checkbox" to json(
|
||||
"autoCheckChildren" to false
|
||||
)
|
||||
) as Config).apply(block)
|
||||
return InspireTree(config)
|
||||
}
|
||||
|
||||
private fun VisualObject.toTree(onFocus: (VisualObject?, String?) -> Unit = { _, _ -> }): InspireTree {
|
||||
|
||||
val map = HashMap<String, VisualObject>()
|
||||
|
||||
fun generateNodeConfig(item: VisualObject, fullName: Name): NodeConfig {
|
||||
val title = item.getProperty("title").string ?: fullName.last()?.toString() ?: "root"
|
||||
val className = if (item is Proxy) {
|
||||
item.prototype::class.toString()
|
||||
} else {
|
||||
item::class.toString()
|
||||
}.replace("class ", "")
|
||||
|
||||
val text: String = if (title.startsWith("@")) {
|
||||
"[$className}]"
|
||||
} else {
|
||||
"$title[$className}]"
|
||||
}
|
||||
|
||||
return json(
|
||||
"children" to if ((item as? VisualGroup)?.children?.isEmpty() != false) {
|
||||
emptyArray<NodeConfig>()
|
||||
} else {
|
||||
true
|
||||
},
|
||||
"text" to text,
|
||||
"id" to fullName.toString(),
|
||||
"itree" to json(
|
||||
"state" to json(
|
||||
"checked" to (item.visible ?: true)
|
||||
)
|
||||
)
|
||||
) as NodeConfig
|
||||
|
||||
}
|
||||
|
||||
fun TreeNode.fillChildren(group: VisualObject, groupName: Name) {
|
||||
if(group is VisualGroup) {
|
||||
group.children.forEach { (token, obj) ->
|
||||
if (!token.body.startsWith("@")) {
|
||||
val name = groupName + token
|
||||
val nodeConfig = generateNodeConfig(obj, name)
|
||||
val childNode = addChild(nodeConfig)
|
||||
map[childNode.id] = obj
|
||||
if (obj is VisualGroup) {
|
||||
childNode.fillChildren(obj, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val inspireTree = createInspireTree {
|
||||
|
||||
}
|
||||
val nodeConfig = generateNodeConfig(this, EmptyName)
|
||||
val rootNode = inspireTree.addNode(nodeConfig)
|
||||
map[rootNode.id] = this
|
||||
rootNode.fillChildren(this, EmptyName)
|
||||
|
||||
// inspireTree.on("node.selected") { node: TreeNode, isLoadEvent: Boolean ->
|
||||
// if (!isLoadEvent) {
|
||||
// map[node.id]?.selected = node.selected()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// inspireTree.on("node.deselect") { node: TreeNode ->
|
||||
// map[node.id]?.selected = node.selected()
|
||||
// }
|
||||
|
||||
inspireTree.on("node.checked") { node: TreeNode, isLoadEvent: Boolean ->
|
||||
if (!isLoadEvent) {
|
||||
map[node.id]?.visible = node.checked()
|
||||
}
|
||||
}
|
||||
|
||||
inspireTree.on("node.unchecked") { node: TreeNode ->
|
||||
if (!node.indeterminate()) {
|
||||
map[node.id]?.visible = node.checked()
|
||||
}
|
||||
}
|
||||
|
||||
inspireTree.on("node.focused") { node: TreeNode, isLoadEvent: Boolean ->
|
||||
if (!isLoadEvent) {
|
||||
onFocus(map[node.id], node.id)
|
||||
}
|
||||
}
|
||||
|
||||
inspireTree.collapseDeep()
|
||||
|
||||
return inspireTree
|
||||
}
|
||||
|
||||
fun Element.visualObjectTree(group: VisualObject, onFocus: (VisualObject?, String?) -> Unit) {
|
||||
this.clear()
|
||||
append {
|
||||
card("Visual object tree") {
|
||||
val domConfig = jsObject<DomConfig> {
|
||||
target = div()
|
||||
showCheckboxes = false
|
||||
}
|
||||
InspireTreeDOM(group.toTree(onFocus), domConfig)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.editor
|
||||
|
||||
import hep.dataforge.vis.spatial.three.ThreeOutput
|
||||
import kotlinx.html.InputType
|
||||
import kotlinx.html.dom.append
|
||||
import kotlinx.html.js.div
|
||||
import kotlinx.html.js.input
|
||||
import kotlinx.html.js.label
|
||||
import org.w3c.dom.Element
|
||||
import kotlin.dom.clear
|
||||
|
||||
fun Element.threeOutputConfig(output: ThreeOutput) {
|
||||
clear()
|
||||
append {
|
||||
card("Layers"){
|
||||
div("row") {
|
||||
(0..11).forEach { layer ->
|
||||
div("col-1") {
|
||||
label { +layer.toString() }
|
||||
input(type = InputType.checkBox).apply {
|
||||
if (layer == 0) {
|
||||
checked = true
|
||||
}
|
||||
onchange = {
|
||||
if (checked) {
|
||||
output.camera.layers.enable(layer)
|
||||
} else {
|
||||
output.camera.layers.disable(layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -1,96 +0,0 @@
|
||||
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 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 Materials {
|
||||
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
||||
val DEFAULT = MeshPhongMaterial().apply {
|
||||
color.set(DEFAULT_COLOR)
|
||||
}
|
||||
val DEFAULT_LINE_COLOR = Color(Colors.black)
|
||||
val DEFAULT_LINE = LineBasicMaterial().apply {
|
||||
color.set(DEFAULT_LINE_COLOR)
|
||||
}
|
||||
|
||||
|
||||
private val materialCache = HashMap<Meta, Material>()
|
||||
private val lineMaterialCache = HashMap<Meta, Material>()
|
||||
|
||||
fun getMaterial(meta: Meta): Material = materialCache.getOrPut(meta) {
|
||||
MeshBasicMaterial().apply {
|
||||
color = meta["color"]?.color() ?: DEFAULT_COLOR
|
||||
opacity = meta["opacity"]?.double ?: 1.0
|
||||
transparent = meta["transparent"].boolean ?: (opacity < 1.0)
|
||||
//node["specularColor"]?.let { specular = it.color() }
|
||||
//side = 2
|
||||
}
|
||||
}
|
||||
|
||||
fun getLineMaterial(meta: Meta): Material = lineMaterialCache.getOrPut(meta) {
|
||||
LineBasicMaterial().apply {
|
||||
color = meta["color"]?.color() ?: DEFAULT_LINE_COLOR
|
||||
opacity = meta["opacity"].double ?: 1.0
|
||||
transparent = meta["transparent"].boolean ?: (opacity < 1.0)
|
||||
linewidth = meta["thickness"].double ?: 1.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer Three material based on meta item
|
||||
*/
|
||||
fun Meta?.jsMaterial(): Material {
|
||||
return if (this == null) {
|
||||
Materials.DEFAULT
|
||||
} else {
|
||||
Materials.getMaterial(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun Meta?.jsLineMaterial(): Material {
|
||||
return if (this == null) {
|
||||
Materials.DEFAULT_LINE
|
||||
} else{
|
||||
Materials.getLineMaterial(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
|
||||
fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial()
|
||||
|
@ -1,6 +1,8 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.boolean
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.plus
|
||||
@ -8,10 +10,10 @@ 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.material
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.geometries.EdgesGeometry
|
||||
import info.laht.threekt.geometries.WireframeGeometry
|
||||
import info.laht.threekt.materials.MeshBasicMaterial
|
||||
import info.laht.threekt.objects.LineSegments
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import kotlin.reflect.KClass
|
||||
@ -19,56 +21,33 @@ import kotlin.reflect.KClass
|
||||
/**
|
||||
* Basic geometry-based factory
|
||||
*/
|
||||
abstract class MeshThreeFactory<T : VisualObject3D>(
|
||||
override val type: KClass<out T>
|
||||
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
|
||||
|
||||
private fun Mesh.applyEdges(obj: T) {
|
||||
children.find { it.name == "edges" }?.let { remove(it) }
|
||||
//inherited edges definition, enabled by default
|
||||
if (obj.getProperty(EDGES_ENABLED_KEY).boolean != false) {
|
||||
val material = obj.getProperty(EDGES_MATERIAL_KEY).node.jsLineMaterial()
|
||||
add(
|
||||
LineSegments(
|
||||
EdgesGeometry(geometry as BufferGeometry),
|
||||
material
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Mesh.applyWireFrame(obj: T) {
|
||||
children.find { it.name == "wireframe" }?.let { remove(it) }
|
||||
//inherited wireframe definition, disabled by default
|
||||
if (obj.getProperty(WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||
val material = obj.getProperty(WIREFRAME_MATERIAL_KEY).node.jsLineMaterial()
|
||||
add(
|
||||
LineSegments(
|
||||
WireframeGeometry(geometry as BufferGeometry),
|
||||
material
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun invoke(obj: T): Mesh {
|
||||
//TODO add caching for geometries using templates
|
||||
val geometry = buildGeometry(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()).apply {
|
||||
//val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
|
||||
|
||||
val mesh = Mesh(geometry, MeshBasicMaterial()).apply {
|
||||
matrixAutoUpdate = false
|
||||
applyEdges(obj)
|
||||
applyWireFrame(obj)
|
||||
|
||||
//set position for mesh
|
||||
updatePosition(obj)
|
||||
|
||||
//set color for mesh
|
||||
updateMaterial(obj)
|
||||
|
||||
layers.enable(obj.layer)
|
||||
children.forEach {
|
||||
it.layers.enable(obj.layer)
|
||||
@ -78,7 +57,14 @@ abstract class MeshThreeFactory<T : VisualObject3D>(
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
when {
|
||||
name.startsWith(VisualObject3D.GEOMETRY_KEY) -> mesh.geometry = buildGeometry(obj)
|
||||
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)
|
||||
@ -97,3 +83,42 @@ abstract class MeshThreeFactory<T : VisualObject3D>(
|
||||
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + Material3D.MATERIAL_KEY
|
||||
}
|
||||
}
|
||||
|
||||
fun Mesh.applyEdges(obj: VisualObject3D) {
|
||||
children.find { it.name == "edges" }?.let {
|
||||
remove(it)
|
||||
(it as LineSegments).dispose()
|
||||
}
|
||||
//inherited edges definition, enabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.EDGES_ENABLED_KEY).boolean != false) {
|
||||
|
||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node)
|
||||
add(
|
||||
LineSegments(
|
||||
EdgesGeometry(geometry as BufferGeometry),
|
||||
material
|
||||
).apply {
|
||||
name = "edges"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun Mesh.applyWireFrame(obj: VisualObject3D) {
|
||||
children.find { it.name == "wireframe" }?.let {
|
||||
remove(it)
|
||||
(it as LineSegments).dispose()
|
||||
}
|
||||
//inherited wireframe definition, disabled by default
|
||||
if (obj.getProperty(MeshThreeFactory.WIREFRAME_ENABLED_KEY).boolean == true) {
|
||||
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.WIREFRAME_MATERIAL_KEY).node)
|
||||
add(
|
||||
LineSegments(
|
||||
WireframeGeometry(geometry as BufferGeometry),
|
||||
material
|
||||
).apply {
|
||||
name = "wireframe"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
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.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 info.laht.threekt.WebGLRenderer
|
||||
import info.laht.threekt.cameras.PerspectiveCamera
|
||||
import info.laht.threekt.external.controls.OrbitControls
|
||||
import info.laht.threekt.external.controls.TrackballControls
|
||||
import info.laht.threekt.helpers.AxesHelper
|
||||
import info.laht.threekt.scenes.Scene
|
||||
import org.w3c.dom.HTMLElement
|
||||
import org.w3c.dom.Node
|
||||
import kotlin.browser.window
|
||||
import kotlin.dom.clear
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.max
|
||||
import kotlin.math.sin
|
||||
|
||||
class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> {
|
||||
|
||||
override val context: Context get() = three.context
|
||||
|
||||
var content: VisualObject3D? = null
|
||||
private set
|
||||
|
||||
val axes = AxesHelper(spec.axes.size.toInt()).apply {
|
||||
visible = spec.axes.visible
|
||||
}
|
||||
|
||||
val scene: Scene = Scene().apply {
|
||||
add(axes)
|
||||
}
|
||||
|
||||
val camera = buildCamera(spec.camera)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fun attach(element: HTMLElement) {
|
||||
element.clear()
|
||||
|
||||
camera.aspect = 1.0
|
||||
|
||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||
setClearColor(Colors.skyblue, 1)
|
||||
|
||||
}
|
||||
|
||||
addControls(renderer.domElement, spec.controls)
|
||||
|
||||
fun animate() {
|
||||
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()
|
||||
}
|
||||
|
||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||
content = obj
|
||||
val object3D = three.buildObject3D(obj)
|
||||
scene.add(object3D)
|
||||
}
|
||||
}
|
||||
|
||||
fun ThreePlugin.output(element: HTMLElement? = null, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas =
|
||||
ThreeCanvas(this, spec).apply {
|
||||
if (element != null) {
|
||||
attach(element)
|
||||
}
|
||||
}
|
@ -16,9 +16,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
|
||||
|
||||
@ -30,7 +30,7 @@ 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)
|
||||
setRotationFromEuler(obj.euler)
|
||||
@ -38,22 +38,24 @@ internal fun Object3D.updatePosition(obj: VisualObject3D) {
|
||||
updateMatrix()
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// */
|
||||
//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")
|
||||
// }
|
||||
//}
|
||||
|
||||
/**
|
||||
* Update non-position non-geometry property
|
||||
*/
|
||||
fun Object3D.updateProperty(source: VisualObject, propertyName: Name) {
|
||||
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
|
||||
//updated material
|
||||
material = source.material.jsMaterial()
|
||||
updateMaterial(source)
|
||||
} else if (
|
||||
source is VisualObject3D &&
|
||||
(propertyName.startsWith(VisualObject3D.position)
|
||||
|
@ -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>()
|
||||
|
@ -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 ThreeLabelFactory : 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
|
||||
}
|
||||
}
|
@ -1,27 +1,32 @@
|
||||
package hep.dataforge.vis.spatial.three
|
||||
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.vis.spatial.PolyLine
|
||||
import hep.dataforge.vis.spatial.layer
|
||||
import hep.dataforge.vis.spatial.material
|
||||
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<out PolyLine> get() = PolyLine::class
|
||||
override val type: KClass<PolyLine> get() = PolyLine::class
|
||||
|
||||
override fun invoke(obj: PolyLine): Object3D {
|
||||
val geometry = Geometry().apply {
|
||||
vertices = obj.points.toTypedArray()
|
||||
}
|
||||
|
||||
val material = obj.material.jsLineMaterial()
|
||||
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)
|
||||
|
||||
//layers.enable(obj.layer)
|
||||
//add listener to object properties
|
||||
obj.onPropertyChange(this) { propertyName, _, _ ->
|
||||
updateProperty(obj, propertyName)
|
||||
|
@ -0,0 +1,113 @@
|
||||
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.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import info.laht.threekt.materials.LineBasicMaterial
|
||||
import info.laht.threekt.materials.MeshBasicMaterial
|
||||
import info.laht.threekt.materials.MeshPhongMaterial
|
||||
import info.laht.threekt.math.Color
|
||||
import info.laht.threekt.objects.Mesh
|
||||
|
||||
|
||||
object ThreeMaterials {
|
||||
val DEFAULT_COLOR = Color(Colors.darkgreen)
|
||||
val DEFAULT = MeshPhongMaterial().apply {
|
||||
color.set(DEFAULT_COLOR)
|
||||
}
|
||||
val DEFAULT_LINE_COLOR = Color(Colors.black)
|
||||
val DEFAULT_LINE = LineBasicMaterial().apply {
|
||||
color.set(DEFAULT_LINE_COLOR)
|
||||
}
|
||||
|
||||
|
||||
// private val materialCache = HashMap<Meta, Material>()
|
||||
private val lineMaterialCache = HashMap<Meta?, LineBasicMaterial>()
|
||||
|
||||
|
||||
// fun buildMaterial(meta: Meta): Material =
|
||||
// MeshBasicMaterial().apply {
|
||||
// color = meta["color"]?.color() ?: DEFAULT_COLOR
|
||||
// opacity = meta["opacity"]?.double ?: 1.0
|
||||
// transparent = meta["transparent"].boolean ?: (opacity < 1.0)
|
||||
// //node["specularColor"]?.let { specular = it.color() }
|
||||
// //side = 2
|
||||
// }
|
||||
|
||||
fun getLineMaterial(meta: Meta?): LineBasicMaterial = lineMaterialCache.getOrPut(meta) {
|
||||
LineBasicMaterial().apply {
|
||||
color = meta[Material3D.COLOR_KEY]?.color() ?: DEFAULT_LINE_COLOR
|
||||
opacity = meta[Material3D.OPACITY_KEY].double ?: 1.0
|
||||
transparent = opacity < 1.0
|
||||
linewidth = meta["thickness"].double ?: 1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer color based on meta item
|
||||
*/
|
||||
fun MetaItem<*>.color(): 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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///**
|
||||
// * Infer Three material based on meta item
|
||||
// */
|
||||
//fun Meta?.jsMaterial(): Material {
|
||||
// return if (this == null) {
|
||||
// ThreeMaterials.DEFAULT
|
||||
// } else {
|
||||
// ThreeMaterials.buildMaterial(this)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//fun Meta?.jsLineMaterial(): Material {
|
||||
// return if (this == null) {
|
||||
// ThreeMaterials.DEFAULT_LINE
|
||||
// } else {
|
||||
// ThreeMaterials.buildLineMaterial(this)
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//fun Material3D?.jsMaterial(): Material = this?.config.jsMaterial()
|
||||
//fun Material3D?.jsLineMaterial(): Material = this?.config.jsLineMaterial()
|
||||
|
||||
fun Mesh.updateMaterial(obj: VisualObject) {
|
||||
val meta = obj.getProperty(Material3D.MATERIAL_KEY).node ?: EmptyMeta
|
||||
material = if(meta[Material3D.SPECULAR_COLOR]!= null){
|
||||
MeshPhongMaterial().apply {
|
||||
color = meta[Material3D.COLOR_KEY]?.color() ?: ThreeMaterials.DEFAULT_COLOR
|
||||
specular = meta[Material3D.SPECULAR_COLOR]!!.color()
|
||||
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]?.color() ?: ThreeMaterials.DEFAULT_COLOR
|
||||
opacity = meta[Material3D.OPACITY_KEY]?.double ?: 1.0
|
||||
transparent = opacity < 1.0
|
||||
wireframe = meta[Material3D.WIREFRAME_KEY].boolean ?: false
|
||||
needsUpdate = true
|
||||
}
|
||||
}
|
||||
}
|
@ -1,76 +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.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.HTMLElement
|
||||
import kotlin.browser.window
|
||||
import kotlin.dom.clear
|
||||
import kotlin.math.max
|
||||
|
||||
class ThreeOutput(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Output<VisualObject3D> {
|
||||
|
||||
override val context: Context get() = three.context
|
||||
|
||||
val axes = AxesHelper(meta["axes.size"].int ?: 50).apply { visible = false }
|
||||
|
||||
val scene: Scene = Scene().apply {
|
||||
add(AmbientLight())
|
||||
if (meta["axes.visible"].boolean == true) {
|
||||
axes.visible = true
|
||||
}
|
||||
add(axes)
|
||||
}
|
||||
|
||||
val camera = three.buildCamera(meta["camera"].node ?: EmptyMeta)
|
||||
|
||||
fun attach(element: HTMLElement) {
|
||||
element.clear()
|
||||
|
||||
camera.aspect = 1.0
|
||||
|
||||
val renderer = WebGLRenderer { antialias = true }.apply {
|
||||
setClearColor(Colors.skyblue, 1)
|
||||
|
||||
}
|
||||
|
||||
three.addControls(camera, renderer.domElement, meta["controls"].node ?: EmptyMeta)
|
||||
|
||||
fun animate() {
|
||||
window.requestAnimationFrame {
|
||||
animate()
|
||||
}
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
|
||||
element.appendChild(renderer.domElement)
|
||||
|
||||
val minSize by meta.number(0).int
|
||||
|
||||
renderer.setSize(max(minSize, element.offsetWidth), max(minSize, element.offsetWidth))
|
||||
|
||||
element.onresize = {
|
||||
renderer.setSize(element.offsetWidth, element.offsetWidth)
|
||||
camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
animate()
|
||||
}
|
||||
|
||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||
scene.add(three.buildObject3D(obj))
|
||||
}
|
||||
}
|
||||
|
||||
fun ThreePlugin.output(element: HTMLElement? = null, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit = {}) =
|
||||
ThreeOutput(this, buildMeta(meta, override)).apply {
|
||||
if (element != null) {
|
||||
attach(element)
|
||||
}
|
||||
}
|
@ -1,21 +1,11 @@
|
||||
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.names.Name
|
||||
import hep.dataforge.names.asName
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.names.startsWith
|
||||
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
|
||||
@ -34,26 +24,29 @@ class ThreePlugin : AbstractPlugin() {
|
||||
objectFactories[Sphere::class] = ThreeSphereFactory
|
||||
objectFactories[ConeSegment::class] = ThreeCylinderFactory
|
||||
objectFactories[PolyLine::class] = ThreeLineFactory
|
||||
objectFactories[Label3D::class] = ThreeLabelFactory
|
||||
}
|
||||
|
||||
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 ThreeVisualObject -> obj.toObject3D()
|
||||
is Proxy -> proxyFactory(obj)
|
||||
is VisualGroup3D -> {
|
||||
val group = ThreeGroup()
|
||||
obj.children.forEach { (name, child) ->
|
||||
obj.children.forEach { (token, child) ->
|
||||
if (child is VisualObject3D && child.ignore != true) {
|
||||
try {
|
||||
val object3D = buildObject3D(child)
|
||||
object3D.name = name.toString()
|
||||
group.add(object3D)
|
||||
group[token] = object3D
|
||||
} catch (ex: Throwable) {
|
||||
logger.error(ex) { "Failed to render $name" }
|
||||
logger.error(ex) { "Failed to render $child" }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -74,12 +67,37 @@ class ThreePlugin : AbstractPlugin() {
|
||||
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)
|
||||
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)
|
||||
@ -89,35 +107,41 @@ 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()
|
||||
}
|
||||
}
|
||||
|
||||
fun Object3D.findChild(name: Name): Object3D? {
|
||||
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() }
|
||||
|
@ -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)
|
||||
|
||||
}
|
@ -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.threeSettings(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
|
||||
clear()
|
||||
append {
|
||||
card("Settings") {
|
||||
div("row") {
|
||||
div("col-2") {
|
||||
label("checkbox-inline") {
|
||||
input(type = InputType.checkBox).apply {
|
||||
checked = canvas.axes.visible
|
||||
onChangeFunction = {
|
||||
canvas.axes.visible = checked
|
||||
}
|
||||
}
|
||||
+"Axes"
|
||||
}
|
||||
}
|
||||
div("col-1") {
|
||||
button {
|
||||
+"Export"
|
||||
onClickFunction = {
|
||||
val json = (canvas.content as? VisualGroup3D)?.let { group ->
|
||||
Visual3DPlugin.json.stringify(
|
||||
VisualGroup3D.serializer(),
|
||||
group
|
||||
)
|
||||
}
|
||||
if (json != null) {
|
||||
saveData(it, "object.json", "text/json"){
|
||||
json
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
card("Layers") {
|
||||
div("row") {
|
||||
(0..11).forEach { layer ->
|
||||
div("col-1") {
|
||||
label { +layer.toString() }
|
||||
input(type = InputType.checkBox).apply {
|
||||
if (layer == 0) {
|
||||
checked = true
|
||||
}
|
||||
onChangeFunction = {
|
||||
if (checked) {
|
||||
canvas.camera.layers.enable(layer)
|
||||
} else {
|
||||
canvas.camera.layers.disable(layer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
block()
|
||||
}
|
||||
}
|
@ -6,21 +6,16 @@ import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.node
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import info.laht.threekt.core.BufferGeometry
|
||||
import info.laht.threekt.core.DirectGeometry
|
||||
import info.laht.threekt.core.Face3
|
||||
import info.laht.threekt.core.Geometry
|
||||
import info.laht.threekt.core.Object3D
|
||||
import info.laht.threekt.external.controls.OrbitControls
|
||||
import info.laht.threekt.materials.Material
|
||||
import info.laht.threekt.math.Euler
|
||||
import info.laht.threekt.math.Vector3
|
||||
|
||||
/**
|
||||
* Utility methods for three.kt.
|
||||
* TODO move to three project
|
||||
*/
|
||||
|
||||
@Suppress("FunctionName")
|
||||
fun Group(children: Collection<Object3D>) = info.laht.threekt.objects.Group().apply {
|
||||
children.forEach { this.add(it) }
|
||||
}
|
||||
import info.laht.threekt.objects.Mesh
|
||||
import info.laht.threekt.textures.Texture
|
||||
import kotlin.math.PI
|
||||
|
||||
val VisualObject3D.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name)
|
||||
|
||||
@ -28,6 +23,8 @@ val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?:
|
||||
|
||||
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
|
||||
|
||||
internal fun Double.toRadians() = this * PI / 180
|
||||
|
||||
fun CSG.toGeometry(): Geometry {
|
||||
val geom = Geometry()
|
||||
|
||||
@ -43,7 +40,7 @@ fun CSG.toGeometry(): Geometry {
|
||||
}
|
||||
|
||||
for (j in 3..polygon.vertices.size) {
|
||||
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, zero)
|
||||
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, World.ZERO)
|
||||
fc.vertexNormals = arrayOf(
|
||||
Vector3().copy(pvs[0].normal),
|
||||
Vector3().copy(pvs[j - 2].normal),
|
||||
@ -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()
|
||||
}
|
||||
}
|
@ -1,166 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import hep.dataforge.vis.spatial.World.CAMERA_FAR_CLIP
|
||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_DISTANCE
|
||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_X_ANGLE
|
||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Y_ANGLE
|
||||
import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Z_ANGLE
|
||||
import hep.dataforge.vis.spatial.World.CAMERA_NEAR_CLIP
|
||||
import javafx.event.EventHandler
|
||||
import javafx.scene.*
|
||||
import javafx.scene.input.KeyCode
|
||||
import javafx.scene.input.KeyEvent
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.scene.input.ScrollEvent
|
||||
import javafx.scene.paint.Color
|
||||
import org.fxyz3d.utils.CameraTransformer
|
||||
import tornadofx.*
|
||||
|
||||
class Canvas3D : Fragment() {
|
||||
val world: Group = Group()
|
||||
|
||||
private val camera = PerspectiveCamera().apply {
|
||||
nearClip = CAMERA_NEAR_CLIP
|
||||
farClip = CAMERA_FAR_CLIP
|
||||
translateZ = CAMERA_INITIAL_DISTANCE
|
||||
}
|
||||
|
||||
private val cameraShift = CameraTransformer().apply {
|
||||
val cameraFlip = CameraTransformer()
|
||||
cameraFlip.children.add(camera)
|
||||
cameraFlip.setRotateZ(180.0)
|
||||
children.add(cameraFlip)
|
||||
}
|
||||
|
||||
val translationXProperty get() = cameraShift.t.xProperty()
|
||||
var translateX by translationXProperty
|
||||
val translationYProperty get() = cameraShift.t.yProperty()
|
||||
var translateY by translationYProperty
|
||||
val translationZProperty get() = cameraShift.t.zProperty()
|
||||
var translateZ by translationZProperty
|
||||
|
||||
private val cameraRotation = CameraTransformer().apply {
|
||||
children.add(cameraShift)
|
||||
ry.angle = CAMERA_INITIAL_Y_ANGLE
|
||||
rx.angle = CAMERA_INITIAL_X_ANGLE
|
||||
rz.angle = CAMERA_INITIAL_Z_ANGLE
|
||||
}
|
||||
|
||||
val rotationXProperty get() = cameraRotation.rx.angleProperty()
|
||||
var angleX by rotationXProperty
|
||||
val rotationYProperty get() = cameraRotation.ry.angleProperty()
|
||||
var angleY by rotationYProperty
|
||||
val rotationZProperty get() = cameraRotation.rz.angleProperty()
|
||||
var angleZ by rotationZProperty
|
||||
|
||||
|
||||
override val root = borderpane {
|
||||
center = SubScene(
|
||||
Group(world, cameraRotation).apply { DepthTest.ENABLE },
|
||||
1024.0,
|
||||
768.0,
|
||||
true,
|
||||
SceneAntialiasing.BALANCED
|
||||
).apply {
|
||||
fill = Color.GREY
|
||||
this.camera = this@Canvas3D.camera
|
||||
id = "canvas"
|
||||
handleKeyboard(this)
|
||||
handleMouse(this)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleKeyboard(scene: SubScene) {
|
||||
scene.onKeyPressed = EventHandler<KeyEvent> { event ->
|
||||
if (event.isControlDown) {
|
||||
when (event.code) {
|
||||
KeyCode.Z -> {
|
||||
cameraShift.t.x = 0.0
|
||||
cameraShift.t.y = 0.0
|
||||
camera.translateZ = CAMERA_INITIAL_DISTANCE
|
||||
cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE
|
||||
cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE
|
||||
}
|
||||
// KeyCode.X -> axisGroup.isVisible = !axisGroup.isVisible
|
||||
// KeyCode.S -> snapshot()
|
||||
// KeyCode.DIGIT1 -> pixelMap.filterKeys { it.getLayerNumber() == 1 }.values.forEach {
|
||||
// toggleTransparency(
|
||||
// it
|
||||
// )
|
||||
// }
|
||||
// KeyCode.DIGIT2 -> pixelMap.filterKeys { it.getLayerNumber() == 2 }.values.forEach {
|
||||
// toggleTransparency(
|
||||
// it
|
||||
// )
|
||||
// }
|
||||
// KeyCode.DIGIT3 -> pixelMap.filterKeys { it.getLayerNumber() == 3 }.values.forEach {
|
||||
// toggleTransparency(
|
||||
// it
|
||||
// )
|
||||
// }
|
||||
else -> {
|
||||
}//do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleMouse(scene: SubScene) {
|
||||
|
||||
var mousePosX: Double = 0.0
|
||||
var mousePosY: Double = 0.0
|
||||
var mouseOldX: Double = 0.0
|
||||
var mouseOldY: Double = 0.0
|
||||
var mouseDeltaX: Double = 0.0
|
||||
var mouseDeltaY: Double = 0.0
|
||||
|
||||
scene.onMousePressed = EventHandler<MouseEvent> { me ->
|
||||
mousePosX = me.sceneX
|
||||
mousePosY = me.sceneY
|
||||
mouseOldX = me.sceneX
|
||||
mouseOldY = me.sceneY
|
||||
}
|
||||
|
||||
scene.onMouseDragged = EventHandler<MouseEvent> { me ->
|
||||
mouseOldX = mousePosX
|
||||
mouseOldY = mousePosY
|
||||
mousePosX = me.sceneX
|
||||
mousePosY = me.sceneY
|
||||
mouseDeltaX = mousePosX - mouseOldX
|
||||
mouseDeltaY = mousePosY - mouseOldY
|
||||
|
||||
val modifier = when {
|
||||
me.isControlDown -> CONTROL_MULTIPLIER
|
||||
me.isShiftDown -> SHIFT_MULTIPLIER
|
||||
else -> 1.0
|
||||
}
|
||||
|
||||
if (me.isPrimaryButtonDown) {
|
||||
cameraRotation.rz.angle =
|
||||
cameraRotation.rz.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||
cameraRotation.rx.angle =
|
||||
cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
|
||||
} else if (me.isSecondaryButtonDown) {
|
||||
cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||
cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
|
||||
}
|
||||
}
|
||||
scene.onScroll = EventHandler<ScrollEvent> { event ->
|
||||
val z = camera.translateZ
|
||||
val newZ = z + MOUSE_SPEED * event.deltaY * RESIZE_SPEED
|
||||
camera.translateZ = newZ
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val AXIS_LENGTH = 2000.0
|
||||
private const val CONTROL_MULTIPLIER = 0.1
|
||||
private const val SHIFT_MULTIPLIER = 10.0
|
||||
private const val MOUSE_SPEED = 0.1
|
||||
private const val ROTATION_SPEED = 2.0
|
||||
private const val TRACK_SPEED = 6.0
|
||||
private const val RESIZE_SPEED = 50.0
|
||||
private const val LINE_WIDTH = 3.0
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import hep.dataforge.meta.*
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import javafx.beans.binding.ObjectBinding
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
* A caching binding collection for [VisualObject] properties
|
||||
*/
|
||||
class DisplayObjectFXListener(val obj: VisualObject) {
|
||||
private val binndings = HashMap<Name, ObjectBinding<MetaItem<*>?>>()
|
||||
|
||||
init {
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
binndings[name]?.invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(key: Name): ObjectBinding<MetaItem<*>?> {
|
||||
return binndings.getOrPut(key) {
|
||||
object : ObjectBinding<MetaItem<*>?>() {
|
||||
override fun computeValue(): MetaItem<*>? = obj.getProperty(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
operator fun get(key: String) = get(key.toName())
|
||||
}
|
||||
|
||||
fun ObjectBinding<MetaItem<*>?>.value() = this.objectBinding { it.value }
|
||||
fun ObjectBinding<MetaItem<*>?>.string() = this.stringBinding { it.string }
|
||||
fun ObjectBinding<MetaItem<*>?>.number() = this.objectBinding { it.number }
|
||||
fun ObjectBinding<MetaItem<*>?>.double() = this.objectBinding { it.double }
|
||||
fun ObjectBinding<MetaItem<*>?>.float() = this.objectBinding { it.number?.toFloat() }
|
||||
fun ObjectBinding<MetaItem<*>?>.int() = this.objectBinding { it.int }
|
||||
fun ObjectBinding<MetaItem<*>?>.long() = this.objectBinding { it.long }
|
||||
fun ObjectBinding<MetaItem<*>?>.node() = this.objectBinding { it.node }
|
||||
|
||||
fun <T> ObjectBinding<MetaItem<*>?>.transform(transform: (MetaItem<*>) -> T) = this.objectBinding { it?.let(transform) }
|
@ -1,50 +0,0 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.output.Output
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.Box
|
||||
import hep.dataforge.vis.spatial.VisualGroup3D
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import org.fxyz3d.shapes.primitives.CuboidMesh
|
||||
import tornadofx.*
|
||||
|
||||
/**
|
||||
* https://github.com/miho/JCSG for operations
|
||||
*
|
||||
*/
|
||||
class FX3DOutput(override val context: Context) : Output<VisualObject> {
|
||||
val canvas by lazy { Canvas3D() }
|
||||
|
||||
|
||||
private fun buildNode(obj: VisualObject): Node? {
|
||||
val listener = DisplayObjectFXListener(obj)
|
||||
val x = listener["pos.x"].float()
|
||||
val y = listener["pos.y"].float()
|
||||
val z = listener["pos.z"].float()
|
||||
val center = objectBinding(x, y, z) {
|
||||
org.fxyz3d.geometry.Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f)
|
||||
}
|
||||
return when (obj) {
|
||||
is VisualGroup3D -> Group(obj.map { buildNode(it) }).apply {
|
||||
this.translateXProperty().bind(x)
|
||||
this.translateYProperty().bind(y)
|
||||
this.translateZProperty().bind(z)
|
||||
}
|
||||
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble()).apply {
|
||||
this.centerProperty().bind(center)
|
||||
this.materialProperty().bind(listener["color"].transform { it.material() })
|
||||
}
|
||||
else -> {
|
||||
logger.error { "No renderer defined for ${obj::class}" }
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(obj: VisualObject, meta: Meta) {
|
||||
buildNode(obj)?.let { canvas.world.children.add(it) }
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import hep.dataforge.context.*
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.meta.boolean
|
||||
import hep.dataforge.provider.Type
|
||||
import hep.dataforge.vis.spatial.*
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_KEY
|
||||
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_WIREFRAME_KEY
|
||||
import hep.dataforge.vis.spatial.fx.FX3DFactory.Companion.TYPE
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.shape.CullFace
|
||||
import javafx.scene.shape.DrawMode
|
||||
import javafx.scene.shape.Shape3D
|
||||
import javafx.scene.text.Font
|
||||
import javafx.scene.text.Text
|
||||
import javafx.scene.transform.Rotate
|
||||
import org.fxyz3d.shapes.composites.PolyLine3D
|
||||
import org.fxyz3d.shapes.primitives.CuboidMesh
|
||||
import org.fxyz3d.shapes.primitives.SpheroidMesh
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.collections.component1
|
||||
import kotlin.collections.component2
|
||||
import kotlin.collections.find
|
||||
import kotlin.collections.map
|
||||
import kotlin.collections.mapNotNull
|
||||
import kotlin.collections.set
|
||||
import kotlin.math.PI
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FX3DPlugin : AbstractPlugin() {
|
||||
override val tag: PluginTag get() = Companion.tag
|
||||
|
||||
private val objectFactories = HashMap<KClass<out VisualObject3D>, FX3DFactory<*>>()
|
||||
private val compositeFactory = FXCompositeFactory(this)
|
||||
private val proxyFactory = FXProxyFactory(this)
|
||||
|
||||
init {
|
||||
//Add specialized factories here
|
||||
objectFactories[Convex::class] = FXConvexFactory
|
||||
}
|
||||
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun findObjectFactory(type: KClass<out VisualObject3D>): FX3DFactory<VisualObject3D>? {
|
||||
return (objectFactories[type] ?: context.content<FX3DFactory<*>>(TYPE).values.find { it.type == type })
|
||||
as FX3DFactory<VisualObject3D>?
|
||||
}
|
||||
|
||||
fun buildNode(obj: VisualObject3D): Node {
|
||||
val binding = VisualObjectFXBinding(obj)
|
||||
return when (obj) {
|
||||
is Proxy -> proxyFactory(obj, binding)
|
||||
is VisualGroup3D -> {
|
||||
Group(obj.children.mapNotNull { (token, obj) ->
|
||||
(obj as? VisualObject3D)?.let {
|
||||
buildNode(it).apply {
|
||||
properties["name"] = token.toString()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
is Composite -> compositeFactory(obj, binding)
|
||||
is Box -> CuboidMesh(obj.xSize.toDouble(), obj.ySize.toDouble(), obj.zSize.toDouble())
|
||||
is Sphere -> if (obj.phi == PI2 && obj.theta == PI.toFloat()) {
|
||||
//use sphere for orb
|
||||
SpheroidMesh(obj.detail ?: 16, obj.radius.toDouble(), obj.radius.toDouble())
|
||||
} else {
|
||||
FXShapeFactory(obj, binding)
|
||||
}
|
||||
is Label3D -> Text(obj.text).apply {
|
||||
font = Font.font(obj.fontFamily, obj.fontSize)
|
||||
x = -layoutBounds.width / 2
|
||||
y = layoutBounds.height / 2
|
||||
}
|
||||
is PolyLine -> PolyLine3D(
|
||||
obj.points.map { it.point },
|
||||
obj.thickness.toFloat(),
|
||||
obj.getProperty(Material3D.MATERIAL_COLOR_KEY)?.color()
|
||||
).apply {
|
||||
this.meshView.cullFace = CullFace.FRONT
|
||||
}
|
||||
else -> {
|
||||
//find specialized factory for this type if it is present
|
||||
val factory: FX3DFactory<VisualObject3D>? = findObjectFactory(obj::class)
|
||||
when {
|
||||
factory != null -> factory(obj, binding)
|
||||
obj is Shape -> FXShapeFactory(obj, binding)
|
||||
else -> error("Renderer for ${obj::class} not found")
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
translateXProperty().bind(binding[VisualObject3D.xPos].float(obj.x.toFloat()))
|
||||
translateYProperty().bind(binding[VisualObject3D.yPos].float(obj.y.toFloat()))
|
||||
translateZProperty().bind(binding[VisualObject3D.zPos].float(obj.z.toFloat()))
|
||||
scaleXProperty().bind(binding[VisualObject3D.xScale].float(obj.scaleX.toFloat()))
|
||||
scaleYProperty().bind(binding[VisualObject3D.yScale].float(obj.scaleY.toFloat()))
|
||||
scaleZProperty().bind(binding[VisualObject3D.zScale].float(obj.scaleZ.toFloat()))
|
||||
|
||||
val rotateX = Rotate(0.0, Rotate.X_AXIS).apply {
|
||||
angleProperty().bind(binding[VisualObject3D.xRotation].float(obj.rotationX.toFloat()).multiply(180.0 / PI))
|
||||
}
|
||||
|
||||
val rotateY = Rotate(0.0, Rotate.Y_AXIS).apply {
|
||||
angleProperty().bind(binding[VisualObject3D.yRotation].float(obj.rotationY.toFloat()).multiply(180.0 / PI))
|
||||
}
|
||||
|
||||
val rotateZ = Rotate(0.0, Rotate.Z_AXIS).apply {
|
||||
angleProperty().bind(binding[VisualObject3D.zRotation].float(obj.rotationZ.toFloat()).multiply(180.0 / PI))
|
||||
}
|
||||
|
||||
when (obj.rotationOrder) {
|
||||
RotationOrder.ZYX -> transforms.addAll(rotateZ, rotateY, rotateX)
|
||||
RotationOrder.XZY -> transforms.addAll(rotateX, rotateZ, rotateY)
|
||||
RotationOrder.YXZ -> transforms.addAll(rotateY, rotateX, rotateZ)
|
||||
RotationOrder.YZX -> transforms.addAll(rotateY, rotateZ, rotateX)
|
||||
RotationOrder.ZXY -> transforms.addAll(rotateZ, rotateX, rotateY)
|
||||
RotationOrder.XYZ -> transforms.addAll(rotateX, rotateY, rotateZ)
|
||||
}
|
||||
|
||||
if (this is Shape3D) {
|
||||
materialProperty().bind(binding[MATERIAL_KEY].transform {
|
||||
it.material()
|
||||
})
|
||||
|
||||
drawModeProperty().bind(binding[MATERIAL_WIREFRAME_KEY].transform {
|
||||
if (it.boolean == true) {
|
||||
DrawMode.LINE
|
||||
} else {
|
||||
DrawMode.FILL
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object : PluginFactory<FX3DPlugin> {
|
||||
override val tag = PluginTag("visual.fx3D", PluginTag.DATAFORGE_GROUP)
|
||||
override val type = FX3DPlugin::class
|
||||
override fun invoke(meta: Meta, context: Context) = FX3DPlugin()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder and updater for three.js object
|
||||
*/
|
||||
@Type(TYPE)
|
||||
interface FX3DFactory<in T : VisualObject3D> {
|
||||
|
||||
val type: KClass<in T>
|
||||
|
||||
operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node
|
||||
|
||||
companion object {
|
||||
const val TYPE = "fx3DFactory"
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import hep.dataforge.context.Context
|
||||
import hep.dataforge.context.ContextAware
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.output.Renderer
|
||||
import hep.dataforge.vis.spatial.VisualObject3D
|
||||
import hep.dataforge.vis.spatial.specifications.CanvasSpec
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.property.ObjectProperty
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.scene.*
|
||||
import javafx.scene.paint.Color
|
||||
import org.fxyz3d.scene.Axes
|
||||
import tornadofx.*
|
||||
|
||||
class FXCanvas3D(val plugin: FX3DPlugin, val spec: CanvasSpec = CanvasSpec.empty()) :
|
||||
Fragment(), Renderer<VisualObject3D>, ContextAware {
|
||||
|
||||
override val context: Context get() = plugin.context
|
||||
|
||||
val world = Group().apply {
|
||||
//transforms.add(Rotate(180.0, Rotate.Z_AXIS))
|
||||
}
|
||||
|
||||
val axes = Axes().also {
|
||||
it.setHeight(spec.axes.size)
|
||||
it.setRadius(spec.axes.width)
|
||||
it.isVisible = spec.axes.visible
|
||||
world.add(it)
|
||||
}
|
||||
|
||||
val light = AmbientLight()
|
||||
|
||||
private val camera = PerspectiveCamera().apply {
|
||||
nearClip = spec.camera.nearClip
|
||||
farClip = spec.camera.farClip
|
||||
fieldOfView = spec.camera.fov.toDouble()
|
||||
this.add(light)
|
||||
}
|
||||
|
||||
private val canvas = SubScene(
|
||||
Group(world, camera).apply { DepthTest.ENABLE },
|
||||
400.0,
|
||||
400.0,
|
||||
true,
|
||||
SceneAntialiasing.BALANCED
|
||||
).also { scene ->
|
||||
scene.fill = Color.GREY
|
||||
scene.camera = camera
|
||||
}
|
||||
|
||||
override val root = borderpane {
|
||||
center = canvas
|
||||
}
|
||||
|
||||
val controls = camera.orbitControls(canvas, spec.camera).also {
|
||||
world.add(it.centerMarker)
|
||||
}
|
||||
|
||||
val rootObjectProperty: ObjectProperty<VisualObject3D> = SimpleObjectProperty()
|
||||
var rootObject: VisualObject3D? by rootObjectProperty
|
||||
|
||||
private val rootNodeProperty = rootObjectProperty.objectBinding {
|
||||
it?.let { plugin.buildNode(it) }
|
||||
}
|
||||
|
||||
init {
|
||||
canvas.widthProperty().bind(root.widthProperty())
|
||||
canvas.heightProperty().bind(root.heightProperty())
|
||||
rootNodeProperty.addListener { _, oldValue: Node?, newValue: Node? ->
|
||||
Platform.runLater {
|
||||
if (oldValue != null) {
|
||||
world.children.remove(oldValue)
|
||||
}
|
||||
if (newValue != null) {
|
||||
world.children.add(newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun render(obj: VisualObject3D, meta: Meta) {
|
||||
rootObject = obj
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import eu.mihosoft.jcsg.CSG
|
||||
import eu.mihosoft.jcsg.Polygon
|
||||
import eu.mihosoft.vvecmath.Vector3d
|
||||
import hep.dataforge.vis.spatial.Composite
|
||||
import hep.dataforge.vis.spatial.CompositeType
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import javafx.scene.shape.MeshView
|
||||
import javafx.scene.shape.TriangleMesh
|
||||
import javafx.scene.shape.VertexFormat
|
||||
import java.util.*
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
private fun MeshView.toCSG(): CSG {
|
||||
val mesh = this.mesh as TriangleMesh
|
||||
if (mesh.vertexFormat != VertexFormat.POINT_TEXCOORD) error("Not POINT_TEXCOORD")
|
||||
val polygons: MutableList<Polygon> = ArrayList()
|
||||
val faces = mesh.faces
|
||||
val points = mesh.points
|
||||
|
||||
val vectorCache = HashMap<Int, Vector3d>()
|
||||
fun getVector(index: Int) = vectorCache.getOrPut(index) {
|
||||
Vector3d.xyz(
|
||||
points[3 * index].toDouble(),
|
||||
points[3 * index + 1].toDouble(),
|
||||
points[3 * index + 2].toDouble()
|
||||
)
|
||||
}
|
||||
|
||||
for (i in 0 until faces.size() / 6) {
|
||||
val polygon = Polygon.fromPoints(
|
||||
getVector(faces[6 * i]),
|
||||
getVector(faces[6 * i + 2]),
|
||||
getVector(faces[6 * i + 4])
|
||||
)
|
||||
polygons.add(polygon)
|
||||
}
|
||||
|
||||
return CSG.fromPolygons(polygons)
|
||||
}
|
||||
|
||||
class FXCompositeFactory(val plugin: FX3DPlugin) : FX3DFactory<Composite> {
|
||||
override val type: KClass<in Composite>
|
||||
get() = Composite::class
|
||||
|
||||
override fun invoke(obj: Composite, binding: VisualObjectFXBinding): Node {
|
||||
val first = plugin.buildNode(obj.first) as? MeshView ?: error("Can't build node")
|
||||
val second = plugin.buildNode(obj.second) as? MeshView ?: error("Can't build node")
|
||||
val firstCSG = first.toCSG()
|
||||
val secondCSG = second.toCSG()
|
||||
val resultCSG = when (obj.compositeType) {
|
||||
CompositeType.UNION -> firstCSG.union(secondCSG)
|
||||
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG)
|
||||
CompositeType.SUBTRACT -> firstCSG.difference(secondCSG)
|
||||
}
|
||||
return resultCSG.toNode()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun CSG.toNode(): Node {
|
||||
val meshes = toJavaFXMesh().asMeshViews
|
||||
return if (meshes.size == 1) {
|
||||
meshes.first()
|
||||
} else {
|
||||
Group(meshes.map { it })
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import eu.mihosoft.jcsg.PropertyStorage
|
||||
import eu.mihosoft.jcsg.ext.quickhull3d.HullUtil
|
||||
import eu.mihosoft.vvecmath.Vector3d
|
||||
import hep.dataforge.vis.spatial.Convex
|
||||
import javafx.scene.Node
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
|
||||
object FXConvexFactory : FX3DFactory<Convex> {
|
||||
override val type: KClass<in Convex> get() = Convex::class
|
||||
|
||||
override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node {
|
||||
val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage())
|
||||
return hull.toNode()
|
||||
}
|
||||
|
||||
}
|
@ -5,14 +5,16 @@ import hep.dataforge.meta.double
|
||||
import hep.dataforge.meta.get
|
||||
import hep.dataforge.meta.int
|
||||
import hep.dataforge.values.ValueType
|
||||
import hep.dataforge.vis.common.Colors
|
||||
import hep.dataforge.vis.spatial.Material3D
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.paint.Material
|
||||
import javafx.scene.paint.PhongMaterial
|
||||
|
||||
object Materials {
|
||||
object FXMaterials {
|
||||
val RED = PhongMaterial().apply {
|
||||
diffuseColor = Color.DARKRED
|
||||
specularColor = Color.RED
|
||||
specularColor = Color.WHITE
|
||||
}
|
||||
|
||||
val WHITE = PhongMaterial().apply {
|
||||
@ -22,7 +24,7 @@ object Materials {
|
||||
|
||||
val GREY = PhongMaterial().apply {
|
||||
diffuseColor = Color.DARKGREY
|
||||
specularColor = Color.GREY
|
||||
specularColor = Color.WHITE
|
||||
}
|
||||
|
||||
val BLUE = PhongMaterial(Color.BLUE)
|
||||
@ -30,24 +32,25 @@ object Materials {
|
||||
|
||||
/**
|
||||
* Infer color based on meta item
|
||||
* @param opacity default opacity
|
||||
*/
|
||||
fun MetaItem<*>.color(): Color {
|
||||
fun MetaItem<*>.color(opacity: Double = 1.0): Color {
|
||||
return when (this) {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) {
|
||||
Color.web(this.value.string)
|
||||
} else {
|
||||
is MetaItem.ValueItem -> if (this.value.type == ValueType.NUMBER) {
|
||||
val int = value.number.toInt()
|
||||
val red = int and 0x00ff0000 shr 16
|
||||
val green = int and 0x0000ff00 shr 8
|
||||
val blue = int and 0x000000ff
|
||||
Color.rgb(red, green, blue)
|
||||
Color.rgb(red, green, blue, opacity)
|
||||
} else {
|
||||
Color.web(this.value.string)
|
||||
}
|
||||
is MetaItem.NodeItem -> {
|
||||
Color.rgb(
|
||||
node["red"]?.int ?: 0,
|
||||
node["green"]?.int ?: 0,
|
||||
node["blue"]?.int ?: 0,
|
||||
node["opacity"]?.double ?: 1.0
|
||||
node[Colors.RED_KEY]?.int ?: 0,
|
||||
node[Colors.GREEN_KEY]?.int ?: 0,
|
||||
node[Colors.BLUE_KEY]?.int ?: 0,
|
||||
node[Material3D.OPACITY_KEY]?.double ?: opacity
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -58,11 +61,12 @@ fun MetaItem<*>.color(): Color {
|
||||
*/
|
||||
fun MetaItem<*>?.material(): Material {
|
||||
return when (this) {
|
||||
null -> Materials.GREY
|
||||
null -> FXMaterials.GREY
|
||||
is MetaItem.ValueItem -> PhongMaterial(color())
|
||||
is MetaItem.NodeItem -> PhongMaterial().apply {
|
||||
(node["color"]?: this@material).let { diffuseColor = it.color() }
|
||||
node["specularColor"]?.let { specularColor = it.color() }
|
||||
val opacity = node[Material3D.OPACITY_KEY].double ?: 1.0
|
||||
diffuseColor = node[Material3D.COLOR_KEY]?.color(opacity) ?: Color.DARKGREY
|
||||
specularColor = node[Material3D.SPECULAR_COLOR]?.color(opacity) ?: Color.WHITE
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import hep.dataforge.names.Name
|
||||
import hep.dataforge.names.isEmpty
|
||||
import hep.dataforge.names.toName
|
||||
import hep.dataforge.vis.common.VisualObject
|
||||
import hep.dataforge.vis.spatial.Proxy
|
||||
import javafx.scene.Group
|
||||
import javafx.scene.Node
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
class FXProxyFactory(val plugin: FX3DPlugin) : FX3DFactory<Proxy> {
|
||||
override val type: KClass<in Proxy> get() = Proxy::class
|
||||
|
||||
override fun invoke(obj: Proxy, binding: VisualObjectFXBinding): Node {
|
||||
val template = obj.prototype
|
||||
val node = plugin.buildNode(template)
|
||||
|
||||
obj.onPropertyChange(this) { name, _, _ ->
|
||||
if (name.first()?.body == Proxy.PROXY_CHILD_PROPERTY_PREFIX) {
|
||||
val childName = name.first()?.index?.toName() ?: error("Wrong syntax for proxy child property: '$name'")
|
||||
val propertyName = name.cutFirst()
|
||||
val proxyChild = obj[childName] ?: error("Proxy child with name '$childName' not found")
|
||||
val child = node.findChild(childName) ?: error("Object child with name '$childName' not found")
|
||||
child.updateProperty(proxyChild, propertyName)
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.findChild(name: Name): Node? {
|
||||
return if (name.isEmpty()) {
|
||||
this
|
||||
} else {
|
||||
(this as? Group)
|
||||
?.children
|
||||
?.find { it.properties["name"] as String == name.first()?.toString() }
|
||||
?.findChild(name.cutFirst())
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.updateProperty(obj: VisualObject, propertyName: Name) {
|
||||
// if (propertyName.startsWith(Material3D.MATERIAL_KEY)) {
|
||||
// (this as? Shape3D)?.let { it.material = obj.getProperty(Material3D.MATERIAL_KEY).material() }
|
||||
// }
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
package hep.dataforge.vis.spatial.fx
|
||||
|
||||
import hep.dataforge.meta.Meta
|
||||
import hep.dataforge.vis.spatial.GeometryBuilder
|
||||
import hep.dataforge.vis.spatial.Point3D
|
||||
import hep.dataforge.vis.spatial.Shape
|
||||
import javafx.scene.shape.Mesh
|
||||
import javafx.scene.shape.MeshView
|
||||
import javafx.scene.shape.TriangleMesh
|
||||
import javafx.scene.shape.VertexFormat
|
||||
import org.fxyz3d.geometry.Face3
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
object FXShapeFactory : FX3DFactory<Shape> {
|
||||
override val type: KClass<in Shape> get() = Shape::class
|
||||
|
||||
override fun invoke(obj: Shape, binding: VisualObjectFXBinding): MeshView {
|
||||
val mesh = FXGeometryBuilder().apply { obj.toGeometry(this) }.build()
|
||||
return MeshView(mesh)
|
||||
}
|
||||
}
|
||||
|
||||
private class FXGeometryBuilder : GeometryBuilder<Mesh> {
|
||||
val vertices = ArrayList<Point3D>()
|
||||
val faces = ArrayList<Face3>()
|
||||
private val vertexCache = HashMap<Point3D, Int>()
|
||||
|
||||
private fun append(vertex: Point3D): Int {
|
||||
val index = vertexCache[vertex] ?: -1//vertices.indexOf(vertex)
|
||||
return if (index > 0) {
|
||||
index
|
||||
} else {
|
||||
vertices.add(vertex)
|
||||
vertexCache[vertex] = vertices.size - 1
|
||||
vertices.size - 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
|
||||
//adding vertices
|
||||
val face = Face3(append(vertex1), append(vertex2), append(vertex3))
|
||||
faces.add(face)
|
||||
}
|
||||
|
||||
override fun build(): Mesh {
|
||||
val mesh = TriangleMesh(VertexFormat.POINT_TEXCOORD)
|
||||
vertices.forEach {
|
||||
//TODO optimize copy
|
||||
mesh.points.addAll(it.x.toFloat(), it.y.toFloat(), it.z.toFloat())
|
||||
}
|
||||
|
||||
mesh.texCoords.addAll(0f, 0f)
|
||||
|
||||
faces.forEach {
|
||||
mesh.faces.addAll(it.p0, 0, it.p1, 0, it.p2, 0)
|
||||
}
|
||||
return mesh
|
||||
}
|
||||
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user