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:
Peter Klimai 2020-01-02 14:33:29 +03:00
commit 6bb6a82b09
148 changed files with 5863 additions and 1864 deletions

View File

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

View File

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

View File

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

View File

@ -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) }
operator fun set(key: String, child: VisualObject?): Unit {
if (key.isBlank()) {
if(child!= null) {
addStatic(child)
}
} else {
set(key.asName(), child)
set(key.toName(), 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()
}
}
override fun MetaBuilder.updateMeta() {
updateChildren()
}
}

View File

@ -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,13 +16,23 @@ 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)
}
}
/**
@ -39,10 +48,12 @@ abstract class AbstractVisualObject : VisualObject {
private val listeners = HashSet<PropertyListener>()
override fun propertyChanged(name: Name, before: MetaItem<*>?, after: MetaItem<*>?) {
if (before != after) {
for (l in listeners) {
l.action(name, before, after)
}
}
}
override fun onPropertyChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) {
listeners.add(PropertyListener(owner, action))
@ -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)
//}

View File

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

View File

@ -0,0 +1,63 @@
@file:UseSerializers(MetaSerializer::class)
package hep.dataforge.vis.common
import hep.dataforge.io.serialization.MetaSerializer
import hep.dataforge.meta.*
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers
@Serializable
class StyleSheet() {
@Transient
internal var owner: VisualObject? = null
constructor(owner: VisualObject) : this() {
this.owner = owner
}
private val styleMap = HashMap<String, Meta>()
val items: Map<String, Meta> get() = styleMap
operator fun get(key: String): Meta? {
return styleMap[key] ?: (owner?.parent as? VisualGroup)?.styleSheet?.get(key)
}
/**
* Define a style without notifying
*/
fun define(key: String, style: Meta?) {
if (style == null) {
styleMap.remove(key)
} else {
styleMap[key] = style
}
}
operator fun set(key: String, style: Meta?) {
val oldStyle = styleMap[key]
define(key, style)
owner?.styleChanged(key, oldStyle, style)
}
operator fun set(key: String, builder: MetaBuilder.() -> Unit) {
val newStyle = get(key)?.let { buildMeta(it, builder) } ?: buildMeta(builder)
set(key, newStyle.seal())
}
}
private fun VisualObject.styleChanged(key: String, oldStyle: Meta?, newStyle: Meta?) {
if (styles.contains(key)) {
//TODO optimize set concatenation
val tokens: Collection<Name> = ((oldStyle?.items?.keys ?: emptySet()) + (newStyle?.items?.keys ?: emptySet()))
.map { it.asName() }
tokens.forEach { parent?.propertyChanged(it, oldStyle?.get(it), newStyle?.get(it)) }
}
if (this is VisualGroup) {
this.forEach { it.styleChanged(key, oldStyle, newStyle) }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,185 @@
@file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS",
"EXTERNAL_DELEGATION"
)
package hep.dataforge.vis.js.editor
import org.w3c.dom.HTMLElement
external interface Node {
var field: String
var value: String? get() = definedExternally; set(value) = definedExternally
var path: dynamic
}
external interface NodeName {
var path: Array<String>
var type: dynamic /* 'object' | 'array' */
var size: Number
}
external interface ValidationError {
var path: dynamic
var message: String
}
external interface Template {
var text: String
var title: String
var className: String? get() = definedExternally; set(value) = definedExternally
var field: String
var value: Any
}
external interface `T$6` {
var startFrom: Number
var options: Array<String>
}
external interface AutoCompleteOptions {
var confirmKeys: Array<Number>? get() = definedExternally; set(value) = definedExternally
var caseSensitive: Boolean? get() = definedExternally; set(value) = definedExternally
// var getOptions: AutoCompleteOptionsGetter? get() = definedExternally; set(value) = definedExternally
}
external interface SelectionPosition {
var row: Number
var column: Number
}
external interface SerializableNode {
var value: Any
var path: dynamic
}
external interface Color {
var rgba: Array<Number>
var hsla: Array<Number>
var rgbString: String
var rgbaString: String
var hslString: String
var hslaString: String
var hex: String
}
//external interface `T$0` {
// var field: Boolean
// var value: Boolean
//}
//
//external interface `T$1` {
// @nativeGetter
// operator fun get(key: String): String?
//
// @nativeSetter
// operator fun set(key: String, value: String)
//}
//external interface Languages {
// @nativeGetter
// operator fun get(lang: String): `T$1`?
//
// @nativeSetter
// operator fun set(lang: String, value: `T$1`)
//}
external interface JSONEditorOptions {
// var ace: AceAjax.Ace? get() = definedExternally; set(value) = definedExternally
// var ajv: Ajv? get() = definedExternally; set(value) = definedExternally
var onChange: (() -> Unit)? get() = definedExternally; set(value) = definedExternally
var onChangeJSON: ((json: Any) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onChangeText: ((jsonString: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onEditable: ((node: Node) -> dynamic)? get() = definedExternally; set(value) = definedExternally
var onError: ((error: Error) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onModeChange: ((newMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */, oldMode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onNodeName: ((nodeName: NodeName) -> String?)? get() = definedExternally; set(value) = definedExternally
var onValidate: ((json: Any) -> dynamic)? get() = definedExternally; set(value) = definedExternally
var escapeUnicode: Boolean? get() = definedExternally; set(value) = definedExternally
var sortObjectKeys: Boolean? get() = definedExternally; set(value) = definedExternally
var history: Boolean? get() = definedExternally; set(value) = definedExternally
var mode: dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
var modes: Array<dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */>? get() = definedExternally; set(value) = definedExternally
var name: String? get() = definedExternally; set(value) = definedExternally
var schema: Any? get() = definedExternally; set(value) = definedExternally
var schemaRefs: Any? get() = definedExternally; set(value) = definedExternally
var search: Boolean? get() = definedExternally; set(value) = definedExternally
var indentation: Number? get() = definedExternally; set(value) = definedExternally
var theme: String? get() = definedExternally; set(value) = definedExternally
var templates: Array<Template>? get() = definedExternally; set(value) = definedExternally
var autocomplete: AutoCompleteOptions? get() = definedExternally; set(value) = definedExternally
var mainMenuBar: Boolean? get() = definedExternally; set(value) = definedExternally
var navigationBar: Boolean? get() = definedExternally; set(value) = definedExternally
var statusBar: Boolean? get() = definedExternally; set(value) = definedExternally
var onTextSelectionChange: ((start: SelectionPosition, end: SelectionPosition, text: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onSelectionChange: ((start: SerializableNode, end: SerializableNode) -> Unit)? get() = definedExternally; set(value) = definedExternally
var onEvent: ((node: Node, event: String) -> Unit)? get() = definedExternally; set(value) = definedExternally
var colorPicker: Boolean? get() = definedExternally; set(value) = definedExternally
var onColorPicker: ((parent: HTMLElement, color: String, onChange: (color: Color) -> Unit) -> Unit)? get() = definedExternally; set(value) = definedExternally
var timestampTag: Boolean? get() = definedExternally; set(value) = definedExternally
var language: String? get() = definedExternally; set(value) = definedExternally
//var languages: Languages? get() = definedExternally; set(value) = definedExternally
var modalAnchor: HTMLElement? get() = definedExternally; set(value) = definedExternally
var enableSort: Boolean? get() = definedExternally; set(value) = definedExternally
var enableTransform: Boolean? get() = definedExternally; set(value) = definedExternally
var maxVisibleChilds: Number? get() = definedExternally; set(value) = definedExternally
}
external interface JsonPath {
var path: dynamic
}
external interface EditorSelection {
var start: SerializableNode
var end: SerializableNode
}
external interface TextSelection {
var start: SelectionPosition
var end: SelectionPosition
var text: String
}
@JsModule("jsoneditor")
@JsNonModule
external open class JSONEditor(
container: HTMLElement,
options: JSONEditorOptions? = definedExternally /* null */,
json: dynamic = definedExternally /* null */
) {
open fun collapseAll()
open fun destroy()
open fun expandAll()
open fun focus()
open fun get(): Any
open fun getMode(): dynamic /* 'tree' | 'view' | 'form' | 'code' | 'text' */
open fun getName(): String?
open fun getNodesByRange(start: JsonPath, end: JsonPath): Array<SerializableNode>
open fun getSelection(): EditorSelection
open fun getText(): String
open fun getTextSelection(): TextSelection
open fun refresh()
open fun set(json: Any)
open fun setMode(mode: String /* 'tree' */)
open fun setMode(mode: String /* 'view' */)
open fun setMode(mode: String /* 'form' */)
open fun setMode(mode: String /* 'code' */)
open fun setMode(mode: String /* 'text' */)
open fun setName(name: String? = definedExternally /* null */)
open fun setSchema(schema: Any?, schemaRefs: Any? = definedExternally /* null */)
open fun setSelection(start: JsonPath, end: JsonPath)
open fun setText(jsonString: String)
open fun setTextSelection(start: SelectionPosition, end: SelectionPosition)
open fun update(json: Any)
open fun updateText(jsonString: String)
companion object {
var VALID_OPTIONS: Array<String>
// var ace: AceAjax.Ace
// var Ajv: Ajv
var VanillaPicker: Any
}
}

View File

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

View File

@ -0,0 +1,23 @@
/* Remove default bullets */
ul, .objTree-subtree {
list-style-type: none;
}
/* Style the caret/arrow */
.objTree-caret {
cursor: pointer;
user-select: none; /* Prevent text selection */
}
/* Create the caret/arrow with a unicode, and style it */
.objTree-caret::before {
content: "\25B6";
color: black;
display: inline-block;
margin-right: 6px;
}
/* Rotate the caret/arrow icon when clicked on (using JavaScript) */
.objTree-caret-down::before {
transform: rotate(90deg);
}

View File

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

View File

@ -1,6 +1,8 @@
package hep.dataforge.vis.fx.values
package hep.dataforge.vis.fx.editor
import hep.dataforge.meta.Meta
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Null
import hep.dataforge.values.Value
import hep.dataforge.values.asValue
@ -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()
}
}

View File

@ -3,11 +3,13 @@
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package hep.dataforge.vis.fx.values
package hep.dataforge.vis.fx.editor
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.value
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.values.Value
import hep.dataforge.values.parseValue
import javafx.collections.FXCollections
@ -50,9 +52,10 @@ class ComboBoxValueChooser(val values: Collection<Value>? = null) : ValueChooser
}
companion object : ValueChooser.Factory {
override val name: String = "combo"
override val name: Name = "combo".asName()
override fun invoke(meta: Meta): ValueChooser = ComboBoxValueChooser(meta["values"].value?.list)
override fun invoke(meta: Meta): ValueChooser =
ComboBoxValueChooser(meta["values"].value?.list)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,74 @@
package hep.dataforge.vis.fx.editor
import hep.dataforge.descriptors.NodeDescriptor
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.meta.update
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.findStyle
import javafx.beans.binding.Binding
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.Node
import javafx.scene.Parent
import javafx.scene.layout.VBox
import tornadofx.*
class VisualObjectEditorFragment(val selector: (VisualObject) -> Meta) : Fragment() {
val itemProperty = SimpleObjectProperty<VisualObject>()
var item: VisualObject? by itemProperty
val descriptorProperty = SimpleObjectProperty<NodeDescriptor>()
constructor(
item: VisualObject?,
descriptor: NodeDescriptor?,
selector: (VisualObject) -> Config = { it.config }
) : this(selector) {
this.item = item
this.descriptorProperty.set(descriptor)
}
private var currentConfig: Config? = null
private val configProperty: Binding<Config?> = itemProperty.objectBinding { visualObject ->
if (visualObject == null) return@objectBinding null
val meta = selector(visualObject)
val config = Config().apply {
update(meta)
onChange(this@VisualObjectEditorFragment) { key, _, after ->
visualObject.setProperty(key, after)
}
}
//remember old config reference to cleanup listeners
currentConfig?.removeListener(this)
currentConfig = config
config
}
private val configEditorProperty: Binding<Node?> = configProperty.objectBinding(descriptorProperty) {
it?.let {
ConfigEditor(it, descriptorProperty.get()).root
}
}
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding() {
VBox().apply {
item?.styles?.forEach { styleName ->
val styleMeta = item?.findStyle(styleName)
if (styleMeta != null) {
titledpane(styleName, node = MetaViewer(styleMeta).root)
}
}
}
}
override val root: Parent = vbox {
titledpane("Properties", collapsible = false) {
contentProperty().bind(configEditorProperty)
}
titledpane("Styles", collapsible = false) {
visibleWhen(itemProperty.booleanBinding { it?.styles?.isNotEmpty() ?: false })
contentProperty().bind(styleBoxProperty)
}
}
}

View File

@ -0,0 +1,55 @@
package hep.dataforge.vis.fx.editor
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import javafx.beans.property.SimpleObjectProperty
import javafx.scene.control.SelectionMode
import javafx.scene.control.TreeItem
import tornadofx.*
private fun toTreeItem(visualObject: VisualObject, title: String): TreeItem<Pair<String, VisualObject>> {
return object : TreeItem<Pair<String, VisualObject>>(title to visualObject) {
init {
if (visualObject is VisualGroup) {
//lazy populate the tree
expandedProperty().onChange { expanded ->
if (expanded && children.isEmpty()) {
children.setAll(visualObject.children.map {
toTreeItem(it.value, it.key.toString())
})
}
}
}
}
override fun isLeaf(): Boolean {
return !(visualObject is VisualGroup && visualObject.children.isNotEmpty())
}
}
}
class VisualObjectTreeFragment : Fragment() {
val itemProperty = SimpleObjectProperty<VisualObject>()
var item: VisualObject? by itemProperty
val selectedProperty = SimpleObjectProperty<VisualObject>()
override val root = vbox {
titledpane("Object tree", collapsible = false) {
treeview<Pair<String, VisualObject>> {
cellFormat {
text = item.first
}
itemProperty.onChange { rootObject ->
if (rootObject != null) {
root = toTreeItem(rootObject, "world")
}
}
selectionModel.selectionMode = SelectionMode.SINGLE
val selectedValue = selectionModel.selectedItemProperty().objectBinding { it?.value?.second }
selectedProperty.bind(selectedValue)
}
}
}
}

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

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

View File

@ -1,23 +0,0 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import org.openjfx.gradle.JavaFXOptions
plugins {
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"
}
}

View File

@ -74,5 +74,5 @@ class JSRootDemoApp : ApplicationBase() {
}
}
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" to presenter.dispose())
override fun dispose() = emptyMap<String, Any>()//mapOf("lines" put presenter.dispose())
}

View File

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

View File

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

View File

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

View File

@ -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
}
}
newRotation?.let {
val point = Point3D(it.x(), it.y(), it.z())
if (rotation != null || point != ZERO) {
rotation = point
}
rotation?.let {
this@withPosition.rotationX = rotation.x()
this@withPosition.rotationY = rotation.y()
this@withPosition.rotationZ = rotation.z()
//this@withPosition.rotationOrder = RotationOrder.ZXY
}
scale?.let {
this@withPosition.scaleX = scale.x.toFloat()
this@withPosition.scaleY = scale.y.toFloat()
this@withPosition.scaleZ = scale.z.toFloat()
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

View File

@ -1,13 +0,0 @@
.loader {
border: 16px solid #f3f3f3; /* Light grey */
border-top: 16px solid #3498db; /* Blue */
border-radius: 50%;
width: 120px;
height: 120px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

View File

@ -0,0 +1,169 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.meta.Meta
import hep.dataforge.vis.spatial.*
import kotlinx.serialization.*
import kotlinx.serialization.json.*
import kotlinx.serialization.modules.SerialModule
import kotlinx.serialization.modules.SerialModuleCollector
import kotlin.reflect.KClass
internal val SerialDescriptor.jsonType
get() = when (this.kind) {
StructureKind.LIST -> "array"
PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG,
PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> "number"
PrimitiveKind.STRING, PrimitiveKind.CHAR, UnionKind.ENUM_KIND -> "string"
PrimitiveKind.BOOLEAN -> "boolean"
else -> "object"
}
private fun SerialModule.enumerate(type: KClass<*>): Sequence<SerialDescriptor> {
val list = ArrayList<SerialDescriptor>()
fun send(descriptor: SerialDescriptor) = list.add(descriptor)
val enumerator = object : SerialModuleCollector {
override fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>) {
if (kClass == type) {
send(serializer.descriptor)
}
}
override fun <Base : Any, Sub : Base> polymorphic(
baseClass: KClass<Base>,
actualClass: KClass<Sub>,
actualSerializer: KSerializer<Sub>
) {
if (baseClass == type) {
send(actualSerializer.descriptor)
}
}
}
dumpTo(enumerator)
return list.asSequence()
}
/**
* Creates an [JsonObject] which contains Json Schema of given [descriptor].
*
* Schema can contain following fields:
* `description`, `type` for all descriptors;
* `properties` and `required` for objects;
* `enum` for enums;
* `items` for arrays.
*
* User can modify this schema to add additional validation keywords
* (as per [https://json-schema.org/latest/json-schema-validation.html])
* if they want.
*/
private fun jsonSchema(descriptor: SerialDescriptor, context: SerialModule): JsonObject {
if (descriptor.name in arrayOf(
"hep.dataforge.vis.spatial.Point3D",
"hep.dataforge.vis.spatial.Point2D",
Meta::class.qualifiedName
)
) return json {
"\$ref" to "#/definitions/${descriptor.name}"
}
val properties: MutableMap<String, JsonObject> = mutableMapOf()
val requiredProperties: MutableSet<String> = mutableSetOf()
val isEnum = descriptor.kind == UnionKind.ENUM_KIND
val isPolymorphic = descriptor.kind is PolymorphicKind
if (!isEnum && !isPolymorphic) descriptor.elementDescriptors().forEachIndexed { index, child ->
val elementName = descriptor.getElementName(index)
properties[elementName] = when (elementName) {
"templates" -> json {
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
}
"properties" -> json {
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
}
"first", "second" -> json{
"\$ref" to "#/definitions/children"
}
"styleSheet" -> json {
"type" to "object"
"additionalProperties" to json {
"\$ref" to "#/definitions/${Meta::class.qualifiedName}"
}
}
in arrayOf("children") -> json {
"type" to "object"
"additionalProperties" to json {
"\$ref" to "#/definitions/children"
}
}
else -> jsonSchema(child, context)
}
if (!descriptor.isElementOptional(index)) requiredProperties.add(elementName)
}
val jsonType = descriptor.jsonType
val objectData: MutableMap<String, JsonElement> = mutableMapOf(
"description" to JsonLiteral(descriptor.name),
"type" to JsonLiteral(jsonType)
)
if (isEnum) {
val allElementNames = (0 until descriptor.elementsCount).map(descriptor::getElementName)
objectData += "enum" to JsonArray(allElementNames.map(::JsonLiteral))
}
when (jsonType) {
"object" -> {
objectData["properties"] = JsonObject(properties)
val required = requiredProperties.map { JsonLiteral(it) }
if (required.isNotEmpty()) {
objectData["required"] = JsonArray(required)
}
}
"array" -> objectData["items"] = properties.values.let {
check(it.size == 1) { "Array descriptor has returned inconsistent number of elements: expected 1, found ${it.size}" }
it.first()
}
else -> { /* no-op */
}
}
return JsonObject(objectData)
}
fun main() {
val context = Visual3DPlugin.serialModule
val definitions = json {
"children" to json {
"anyOf" to jsonArray {
context.enumerate(VisualObject3D::class).forEach {
if (it.name == "hep.dataforge.vis.spatial.VisualGroup3D") {
+json {
"\$ref" to "#/definitions/${it.name}"
}
} else {
+jsonSchema(it, context)
}
}
}
}
"hep.dataforge.vis.spatial.Point3D" to jsonSchema(Point3DSerializer.descriptor, context)
"hep.dataforge.vis.spatial.Point2D" to jsonSchema(Point2DSerializer.descriptor, context)
"hep.dataforge.vis.spatial.VisualGroup3D" to jsonSchema(VisualGroup3D.serializer().descriptor, context)
}
println(
Json.indented.stringify(
JsonObjectSerializer,
json {
"definitions" to definitions
"\$ref" to "#/definitions/hep.dataforge.vis.spatial.VisualGroup3D"
}
)
)
}

View File

@ -1,26 +0,0 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualGroup3D
import nl.adaptivity.xmlutil.StAXReader
import scientifik.gdml.GDML
import java.io.File
fun main() {
val file = File("D:\\Work\\Projects\\gdml.kt\\gdml-source\\BM@N_coil.gdml")
val xmlReader = StAXReader(file.inputStream(), "UTF-8")
val xml = GDML.format.parse(GDML.serializer(), xmlReader)
val visual = xml.toVisual {
lUnit = LUnit.CM
}
//val meta = visual.toMeta()
val str = Visual3DPlugin.json.stringify(VisualGroup3D.serializer(), visual)
println(str)
//println(Json.indented.stringify(meta.toJson()))
}

View File

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

View File

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

View File

@ -0,0 +1,18 @@
package hep.dataforge.vis.spatial.gdml
import hep.dataforge.vis.spatial.VisualGroup3D
import nl.adaptivity.xmlutil.StAXReader
import scientifik.gdml.GDML
import java.nio.file.Files
import java.nio.file.Path
fun GDML.Companion.readFile(file: Path): GDML {
val xmlReader = StAXReader(Files.newInputStream(file), "UTF-8")
return GDML.format.parse(GDML.serializer(), xmlReader)
}
fun VisualGroup3D.gdml(file: Path, key: String = "", transformer: GDMLTransformer.() -> Unit = {}) {
val gdml = GDML.readFile(file)
gdml(gdml, key, transformer)
}

View File

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

View File

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

View File

@ -1,9 +1,9 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.update
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
@ -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.material = group.material
if(group.position!=null) {
it.position = group.position
}
if(group.rotation!=null) {
it.rotation = group.rotation
}
if(group.scale!=null) {
it.scale = group.scale
}
set(name, it)
}
}

View File

@ -2,11 +2,13 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import kotlin.math.cos
import kotlin.math.sin
/**
* A cylinder or cut cone segment
@ -18,7 +20,7 @@ class ConeSegment(
var upperRadius: Float,
var startAngle: Float = 0f,
var angle: Float = PI2
) : AbstractVisualObject(), VisualObject3D {
) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
@ -26,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(

View File

@ -2,9 +2,8 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.meta.MetaBuilder
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@ -19,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"
}

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point2DSerializer::class, Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable

View File

@ -0,0 +1,30 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
@Serializable
class Label3D(var text: String, var fontSize: Double, var fontFamily: String) : AbstractVisualObject(),
VisualObject3D {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
override var position: Point3D? = null
override var rotation: Point3D? = null
override var scale: Point3D? = null
}
fun VisualGroup3D.label(
text: String,
fontSize: Number = 20,
fontFamily: String = "Arial",
name: String = "",
action: Label3D.() -> Unit = {}
) =
Label3D(text, fontSize.toDouble(), fontFamily).apply(action).also { set(name, it) }

View File

@ -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
val descriptor = NodeDescriptor {
value(VisualObject3D.VISIBLE_KEY) {
type(ValueType.BOOLEAN)
default(true)
}
node(MATERIAL_KEY) {
value(COLOR_KEY) {
type(ValueType.STRING, ValueType.NUMBER)
default("#ffffff")
}
value(OPACITY_KEY) {
type(ValueType.NUMBER)
default(1.0)
}
value(WIREFRAME_KEY) {
type(ValueType.BOOLEAN)
default(false)
}
}
}
}
}
fun VisualObject.color(rgb: String) {
setProperty(COLOR_KEY, rgb)
fun VisualObject3D.color(rgb: String) {
setProperty(MATERIAL_COLOR_KEY, rgb)
}
fun VisualObject.color(rgb: Int) = color(Colors.rgbToString(rgb))
fun VisualObject3D.color(rgb: Int) {
setProperty(MATERIAL_COLOR_KEY, rgb)
}
fun VisualObject.color(r: UByte, g: UByte, b: UByte) = color( Colors.rgbToString(r,g,b))
fun VisualObject3D.color(r: UByte, g: UByte, b: UByte) = setProperty(
MATERIAL_COLOR_KEY,
Colors.rgbToMeta(r, g, b)
)
var VisualObject.color: String?
get() = getProperty(COLOR_KEY).string
/**
* 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) {
if (value != null) {
color(value)
}
setProperty(MATERIAL_COLOR_KEY, value)
}
var VisualObject.material: Material3D?
get() = getProperty(MATERIAL_KEY).node?.let { Material3D.wrap(it) }
set(value) = setProperty(MATERIAL_KEY, value?.config)
//var VisualObject3D.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)
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 VisualObject.opacity: Double?
get() = getProperty(OPACITY_KEY).double
var VisualObject3D.opacity: Double?
get() = getProperty(MATERIAL_OPACITY_KEY).double
set(value) {
setProperty(OPACITY_KEY, value)
setProperty(MATERIAL_OPACITY_KEY, value)
}

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import 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 = {}) =

View File

@ -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 {
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}")
override var styles: List<Name>
get() = super.styles + prototype.styles
set(value) {
setProperty(VisualObject.STYLE_KEY, value.map { it.toString() })
styleChanged()
private fun prototypeFor(name: Name): VisualObject {
return (prototype as? VisualGroup)?.get(name)
?: error("Prototype with name $name not found in $this")
}
override fun allProperties(): Laminate = Laminate(properties, mergedStyles, prototype.allProperties())
//override fun findAllStyles(): Laminate = Laminate((styles + prototype.styles).mapNotNull { findStyle(it) })
@Serializable
inner class ProxyChild(val name: Name) : AbstractVisualObject(), VisualGroup {
val prototype: VisualObject 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)
}

View File

@ -2,7 +2,7 @@
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
@ -16,7 +16,7 @@ class Sphere(
var phi: Float = PI2,
var thetaStart: Float = 0f,
var theta: Float = PI.toFloat()
) : AbstractVisualObject(), VisualObject3D {
) : AbstractVisualObject(), VisualObject3D, Shape {
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
@ -24,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(

View File

@ -1,7 +1,7 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial
import hep.dataforge.io.ConfigSerializer
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import kotlinx.serialization.Serializable
@ -123,6 +123,7 @@ class Tube(
}
}
}
}
inline fun VisualGroup3D.tube(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,17 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.*
class AxesSpec(override val config: Config) : Specific {
var visible by boolean(!config.isEmpty())
var size by double(AXIS_SIZE)
var width by double(AXIS_WIDTH)
companion object : Specification<AxesSpec> {
override fun wrap(config: Config): AxesSpec = AxesSpec(config)
const val AXIS_SIZE = 1000.0
const val AXIS_WIDTH = 3.0
}
}

View File

@ -0,0 +1,26 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.*
import kotlin.math.PI
class CameraSpec(override val config: Config) : Specific {
var fov by int(FIELD_OF_VIEW)
//var aspect by double(1.0)
var nearClip by double(NEAR_CLIP)
var farClip by double(FAR_CLIP)
var distance by double(INITIAL_DISTANCE)
var azimuth by double(INITIAL_AZIMUTH)
var latitude by double(INITIAL_LATITUDE)
val zenith: Double get() = PI / 2 - latitude
companion object : Specification<CameraSpec> {
override fun wrap(config: Config): CameraSpec = CameraSpec(config)
const val INITIAL_DISTANCE = 300.0
const val INITIAL_AZIMUTH = 0.0
const val INITIAL_LATITUDE = PI/6
const val NEAR_CLIP = 0.1
const val FAR_CLIP = 10000.0
const val FIELD_OF_VIEW = 75
}
}

View File

@ -0,0 +1,15 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.*
class CanvasSpec(override val config: Config) : Specific {
var axes by spec(AxesSpec)
var camera by spec(CameraSpec)
var controls by spec(ControlsSpec)
var minSize by int(300)
companion object: Specification<CanvasSpec>{
override fun wrap(config: Config): CanvasSpec = CanvasSpec(config)
}
}

View File

@ -0,0 +1,11 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.Config
import hep.dataforge.meta.Specific
import hep.dataforge.meta.Specification
class ControlsSpec(override val config: Config) : Specific {
companion object : Specification<ControlsSpec> {
override fun wrap(config: Config): ControlsSpec = ControlsSpec(config)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,79 @@
package hep.dataforge.vis.spatial
import hep.dataforge.meta.int
import hep.dataforge.meta.set
import hep.dataforge.names.asName
import hep.dataforge.vis.common.useStyle
import kotlin.test.Test
import kotlin.test.assertEquals
class PropertyTest {
@Test
fun testInheritedProperty() {
var box: Box? = null
val group = VisualGroup3D().apply {
config["test"] = 22
group {
box = box(100, 100, 100)
}
}
assertEquals(22, box?.getProperty("test".asName()).int)
}
@Test
fun testStyleProperty() {
var box: Box? = null
val group = VisualGroup3D().apply {
styleSheet {
set("testStyle") {
"test" put 22
}
}
group {
box = box(100, 100, 100).apply {
useStyle("testStyle")
}
}
}
assertEquals(22, box?.getProperty("test".asName()).int)
}
@Test
fun testColor() {
var box: Box? = null
val group = VisualGroup3D().apply {
styleSheet {
set("testStyle") {
Material3D.MATERIAL_COLOR_KEY put "#555555"
}
}
group {
box = box(100, 100, 100) {
useStyle("testStyle")
}
}
}
assertEquals("#555555", box?.color)
}
@Test
fun testProxyStyleProperty() {
var box: Proxy? = null
val group = VisualGroup3D().apply {
styleSheet {
set("testStyle") {
Material3D.MATERIAL_COLOR_KEY put "#555555"
}
}
prototypes {
box(100, 100, 100, name = "box") {
styles = listOf("testStyle")
}
}
group {
box = ref("box".asName())
}
}
assertEquals("#555555", box?.color)
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,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()

View File

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

View File

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

View File

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

View File

@ -10,6 +10,9 @@ import info.laht.threekt.core.Face3
import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Vector3
/**
* An implementation of geometry builder for Three.js [BufferGeometry]
*/
class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
private val vertices = ArrayList<Point3D>()

View File

@ -0,0 +1,56 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Label3D
import hep.dataforge.vis.spatial.color
import info.laht.threekt.DoubleSide
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.PlaneBufferGeometry
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.CanvasTextBaseline
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.MIDDLE
import kotlin.browser.document
import kotlin.reflect.KClass
/**
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
*/
object 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
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,34 @@
@file:UseSerializers(Point3DSerializer::class)
package hep.dataforge.vis.spatial.three
import hep.dataforge.io.serialization.ConfigSerializer
import hep.dataforge.meta.Config
import hep.dataforge.vis.common.AbstractVisualObject
import hep.dataforge.vis.spatial.Point3D
import hep.dataforge.vis.spatial.Point3DSerializer
import hep.dataforge.vis.spatial.VisualObject3D
import info.laht.threekt.core.Object3D
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
/**
* A custom visual object that has its own Three.js renderer
*/
interface ThreeVisualObject : VisualObject3D {
fun toObject3D(): Object3D
}
@Serializable
class CustomThreeVisualObject(val threeFactory: ThreeFactory<VisualObject3D>) : AbstractVisualObject(),
ThreeVisualObject {
override var position: Point3D? = null
override var rotation: Point3D? = null
override var scale: Point3D? = null
@Serializable(ConfigSerializer::class)
override var properties: Config? = null
override fun toObject3D(): Object3D = threeFactory(this)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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