v0.2.0-dev-22 #47

Merged
altavir merged 158 commits from dev into master 2021-07-17 11:04:22 +03:00
40 changed files with 474 additions and 302 deletions
Showing only changes of commit 5b8d298ac6 - Show all commits

View File

@ -0,0 +1,24 @@
plugins {
id("ru.mipt.npm.mpp")
}
val kvisionVersion: String = "3.16.2"
kscience{
application()
}
kotlin {
sourceSets {
commonMain {
dependencies {
implementation(project(":visionforge-solid"))
}
}
jsMain {
dependencies {
}
}
}
}

View File

@ -0,0 +1,55 @@
package ru.mipt.npm.sat
import hep.dataforge.vision.solid.*
import kotlin.math.PI
internal fun visionOfSatellite(
layers: Int = 10,
layerHeight: Number = 10,
xSegments: Int = 3,
ySegments: Int = xSegments,
xSegmentSize: Number = 30,
ySegmentSize: Number = xSegmentSize,
fiberDiameter: Number = 1.0,
): SolidGroup = SolidGroup {
opacity = 0.3
val totalXSize = xSegments * xSegmentSize.toDouble()
val totalYSize = ySegments * ySegmentSize.toDouble()
for (layer in 1..layers) {
group("layer[$layer]") {
for (i in 1..xSegments) {
for (j in 1..ySegments) {
box(xSegmentSize, ySegmentSize, layerHeight, name = "segment[$i,$j]") {
z = (layer - 0.5) * layerHeight.toDouble()
x = (i - 0.5) * xSegmentSize.toDouble()
y = (j - 0.5) * ySegmentSize.toDouble()
}
}
}
group("fibers") {
for (i in 1..xSegments) {
cylinder(fiberDiameter, totalYSize) {
rotationX = PI / 2
z = (layer - 1.0) * layerHeight.toDouble() + fiberDiameter.toDouble()
x = (i - 0.5) * xSegmentSize.toDouble()
y = totalYSize/2
color("red")
}
}
for (j in 1..ySegments) {
cylinder(fiberDiameter, totalXSize) {
rotationY = PI / 2
z = (layer) * layerHeight.toDouble() - fiberDiameter.toDouble()
y = (j - 0.5) * xSegmentSize.toDouble()
x = totalXSize/2
color("blue")
}
}
}
}
}
}

View File

@ -0,0 +1,34 @@
package ru.mipt.npm.sat
import hep.dataforge.context.Global
import hep.dataforge.js.Application
import hep.dataforge.js.startApplication
import hep.dataforge.vision.solid.three.ThreePlugin
import hep.dataforge.vision.solid.three.render
import kotlinx.browser.document
import hep.dataforge.meta.invoke
import org.w3c.dom.HTMLElement
private class SatDemoApp : Application {
override fun start(state: Map<String, Any>) {
val element = document.getElementById("canvas") as? HTMLElement
?: error("Element with id 'canvas' not found on page")
val three = Global.plugins.fetch(ThreePlugin)
val sat = visionOfSatellite(
ySegments = 5,
)
three.render(element, sat){
minSize = 500
axes {
size = 500.0
visible = true
}
}
}
}
fun main() {
startApplication(::SatDemoApp)
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Three js demo for particle physics</title>
<script type="text/javascript" src="sat-demo.js"></script>
</head>
<body class="application">
<div id="canvas"></div>
</body>
</html>

View File

@ -6,6 +6,7 @@ plugins {
}
kscience {
useCoroutines()
val fxVersion: String by rootProject.extra
useFx(FXModule.CONTROLS, version = fxVersion, configuration = DependencyConfiguration.IMPLEMENTATION)
application()

View File

@ -2,7 +2,14 @@ package hep.dataforge.vision.solid.demo
import hep.dataforge.js.Application
import hep.dataforge.js.startApplication
import kotlin.browser.document
import hep.dataforge.vision.solid.x
import hep.dataforge.vision.solid.y
import kotlinx.browser.document
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.random.Random
private class ThreeDemoApp : Application {
@ -13,27 +20,25 @@ private class ThreeDemoApp : Application {
ThreeDemoGrid(element).run {
showcase()
showcaseCSG()
// demo("dynamicBox", "Dancing boxes") {
// val boxes = (-10..10).flatMap { i ->
// (-10..10).map { j ->
// varBox(10, 10, 0, name = "cell_${i}_${j}") {
// x = i * 10
// y = j * 10
// value = 128
// setProperty(EDGES_ENABLED_KEY, false)
// setProperty(WIREFRAME_ENABLED_KEY, false)
// }
// }
// }
// GlobalScope.launch {
// while (isActive) {
// delay(500)
// boxes.forEach { box ->
// box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255)
// }
// }
// }
// }
demo("dynamicBox", "Dancing boxes") {
val boxes = (-10..10).flatMap { i ->
(-10..10).map { j ->
varBox(10, 10, 0, name = "cell_${i}_${j}") {
x = i * 10
y = j * 10
value = 128
}
}
}
GlobalScope.launch {
while (isActive) {
delay(500)
boxes.forEach { box ->
box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(1..255)
}
}
}
}
}

View File

@ -11,6 +11,8 @@ import hep.dataforge.vision.Vision
import hep.dataforge.vision.solid.three.ThreeCanvas
import hep.dataforge.vision.solid.three.ThreePlugin
import hep.dataforge.vision.solid.three.output
import kotlinx.browser.document
import kotlinx.dom.clear
import kotlinx.html.dom.append
import kotlinx.html.dom.create
import kotlinx.html.h2
@ -19,8 +21,6 @@ import kotlinx.html.id
import kotlinx.html.js.div
import kotlinx.html.span
import org.w3c.dom.Element
import kotlin.browser.document
import kotlin.dom.clear
import kotlin.reflect.KClass
class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) : OutputManager {

View File

@ -4,6 +4,7 @@ package hep.dataforge.vision.solid.demo
import hep.dataforge.meta.int
import hep.dataforge.meta.number
import hep.dataforge.meta.set
import hep.dataforge.meta.setItem
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
@ -12,7 +13,6 @@ import hep.dataforge.vision.getProperty
import hep.dataforge.vision.set
import hep.dataforge.vision.solid.*
import hep.dataforge.vision.solid.Solid.Companion.GEOMETRY_KEY
import hep.dataforge.vision.solid.demo.VariableBoxThreeFactory.Z_SIZE_KEY
import hep.dataforge.vision.solid.three.*
import hep.dataforge.vision.solid.three.ThreeMaterials.getMaterial
import info.laht.threekt.core.BufferGeometry
@ -21,86 +21,89 @@ import info.laht.threekt.geometries.BoxBufferGeometry
import info.laht.threekt.objects.Mesh
import kotlinx.serialization.UseSerializers
import kotlin.math.max
import kotlin.reflect.KClass
internal var Solid.variableZSize: Number
get() = getProperty(Z_SIZE_KEY, false).number ?: 0f
set(value) {
setItem(Z_SIZE_KEY, value.asValue())
}
internal var Solid.value: Int
get() = getProperty("value", false).int ?: 0
set(value) {
setItem("value", value.asValue())
val size = value.toFloat() / 255f * 20f
scaleZ = size
z = -size / 2
val b = max(0, 255 - value)
val r = max(0, value - 255)
val g = 255 - b - r
color(r.toUByte(), g.toUByte(), b.toUByte())
}
fun SolidGroup.varBox(
internal fun SolidGroup.varBox(
xSize: Number,
ySize: Number,
zSize: Number,
name: String = "",
action: Solid.() -> Unit = {}
) = CustomThreeVision(VariableBoxThreeFactory).apply {
action: VariableBox.() -> Unit = {},
): VariableBox = VariableBox(xSize, ySize, zSize).apply(action).also { set(name, it) }
internal class VariableBox(xSize: Number, ySize: Number, zSize: Number) : ThreeVision() {
init {
scaleX = xSize
scaleY = ySize
scaleZ = zSize
}.apply(action).also { set(name, it) }
config[MeshThreeFactory.EDGES_ENABLED_KEY] = false
config[MeshThreeFactory.WIREFRAME_ENABLED_KEY] = false
}
private object VariableBoxThreeFactory : ThreeFactory<Solid> {
val X_SIZE_KEY = GEOMETRY_KEY + "xSize"
val Y_SIZE_KEY = GEOMETRY_KEY + "ySize"
val Z_SIZE_KEY = GEOMETRY_KEY + "zSize"
override val type: KClass<in Solid> get() = Solid::class
override fun invoke(obj: Solid): Object3D {
val xSize = obj.getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
val ySize = obj.getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
val zSize = obj.getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
override fun render(): Object3D {
val xSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
val ySize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
val zSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
val geometry = BoxBufferGeometry(1, 1, 1)
//JS sometimes tries to pass Geometry as BufferGeometry
@Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected")
val mesh = Mesh(geometry, getMaterial(obj,true)).apply {
applyEdges(obj)
applyWireFrame(obj)
val mesh = Mesh(geometry, getMaterial(this@VariableBox, true)).apply {
applyEdges(this@VariableBox)
applyWireFrame(this@VariableBox)
//set position for mesh
updatePosition(obj)
updatePosition(this@VariableBox)
layers.enable(obj.layer)
layers.enable(this@VariableBox.layer)
children.forEach {
it.layers.enable(obj.layer)
it.layers.enable(this@VariableBox.layer)
}
}
mesh.scale.set(xSize, ySize, zSize)
//add listener to object properties
obj.onPropertyChange(this) { name ->
onPropertyChange(mesh) { name ->
when {
// name.startsWith(GEOMETRY_KEY) -> {
// val newXSize = obj.getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
// val newYSize = obj.getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
// val newZSize = obj.getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
// mesh.scale.set(newXSize, newYSize, newZSize)
// mesh.updateMatrix()
// }
name.startsWith(MeshThreeFactory.WIREFRAME_KEY) -> mesh.applyWireFrame(obj)
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(obj)
else -> mesh.updateProperty(obj, name)
name.startsWith(GEOMETRY_KEY) -> {
val newXSize = getProperty(X_SIZE_KEY, false).number?.toDouble() ?: 1.0
val newYSize = getProperty(Y_SIZE_KEY, false).number?.toDouble() ?: 1.0
val newZSize = getProperty(Z_SIZE_KEY, false).number?.toDouble() ?: 1.0
mesh.scale.set(newXSize, newYSize, newZSize)
mesh.updateMatrix()
}
name.startsWith(MeshThreeFactory.WIREFRAME_KEY) -> mesh.applyWireFrame(this@VariableBox)
name.startsWith(MeshThreeFactory.EDGES_KEY) -> mesh.applyEdges(this@VariableBox)
else -> mesh.updateProperty(this@VariableBox, name)
}
}
return mesh
}
var variableZSize: Number
get() = getProperty(Z_SIZE_KEY, false).number ?: 0f
set(value) {
setItem(Z_SIZE_KEY, value.asValue())
}
var value: Int
get() = getProperty("value", false).int ?: 0
set(value) {
setItem("value", value.asValue())
val size = value.toFloat() / 255f * 20f
scaleZ = size
z = size / 2
val b = max(0, 128 - value)
val r = max(0, value - 128)
val g = 255 - b - r
color(r.toUByte(), g.toUByte(), b.toUByte())
}
companion object{
private val X_SIZE_KEY = GEOMETRY_KEY + "xSize"
private val Y_SIZE_KEY = GEOMETRY_KEY + "ySize"
private val Z_SIZE_KEY = GEOMETRY_KEY + "zSize"
}
}

View File

@ -43,5 +43,6 @@ include(
":demo:spatial-showcase",
":demo:gdml",
":demo:muon-monitor",
":demo:sat-demo",
":playground"
)

View File

@ -1,6 +1,5 @@
package hep.dataforge.vision.bootstrap
import hep.dataforge.meta.DFExperimental
import hep.dataforge.vision.solid.SolidGroup
import hep.dataforge.vision.solid.SolidManager
import hep.dataforge.vision.solid.three.ThreeCanvas
@ -15,8 +14,7 @@ import org.w3c.dom.HTMLInputElement
import org.w3c.dom.events.Event
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import react.RBuilder
import react.ReactElement
import react.*
import react.dom.button
import react.dom.div
import react.dom.input
@ -31,17 +29,33 @@ private fun saveData(event: Event, fileName: String, mimeType: String = "text/pl
fileSaver.saveAs(blob, fileName)
}
@OptIn(DFExperimental::class)
public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement = accordion("controls") {
public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement {
return child(CanvasControls){
attrs{
this.canvas = canvas
}
}
}
public external interface CanvasControlsProps : RProps {
public var canvas: ThreeCanvas
}
public val CanvasControls: FunctionalComponent<CanvasControlsProps> = functionalComponent ("CanvasControls") { props ->
val visionManager = useMemo(
{ props.canvas.context.plugins.fetch(SolidManager).visionManager },
arrayOf(props.canvas)
)
accordion("controls") {
entry("Settings") {
div("row") {
div("col-2") {
label("checkbox-inline") {
input(type = InputType.checkBox) {
attrs {
defaultChecked = canvas.axes.visible
defaultChecked = props.canvas.axes.visible
onChangeFunction = {
canvas.axes.visible = (it.target as HTMLInputElement).checked
props.canvas.axes.visible = (it.target as HTMLInputElement).checked
}
}
}
@ -53,11 +67,8 @@ public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement = accordio
+"Export"
attrs {
onClickFunction = {
val json = (canvas.content as? SolidGroup)?.let { group ->
SolidManager.jsonForSolids.encodeToString(
SolidGroup.serializer(),
group
)
val json = (props.canvas.content as? SolidGroup)?.let { group ->
visionManager.encodeToString(group)
}
if (json != null) {
saveData(it, "object.json", "text/json") {
@ -82,9 +93,10 @@ public fun RBuilder.canvasControls(canvas: ThreeCanvas): ReactElement = accordio
}
onChangeFunction = {
if ((it.target as HTMLInputElement).checked) {
canvas.camera.layers.enable(layer)
props.canvas.camera.layers.enable(layer)
} else {
canvas.camera.layers.disable(layer)
props.canvas.camera.layers.disable(layer)
}
}
}
}
@ -118,10 +130,8 @@ public fun Element.displayCanvasControls(canvas: ThreeCanvas, block: TagConsumer
+"Export"
onClickFunction = {
val json = (canvas.content as? SolidGroup)?.let { group ->
SolidManager.jsonForSolids.encodeToString(
SolidGroup.serializer(),
group
)
val visionManager = canvas.context.plugins.fetch(SolidManager).visionManager
visionManager.encodeToString(group)
}
if (json != null) {
saveData(it, "object.json", "text/json") {

View File

@ -1,5 +1,8 @@
package hep.dataforge.vision
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.node
import hep.dataforge.names.*
import kotlinx.serialization.Transient
@ -7,7 +10,7 @@ import kotlinx.serialization.Transient
/**
* Abstract implementation of mutable group of [Vision]
*/
abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
public abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
//protected abstract val _children: MutableMap<NameToken, T>
@ -22,30 +25,31 @@ abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
/**
* Update or create stylesheet
*/
fun styleSheet(block: StyleSheet.() -> Unit) {
val res = styleSheet ?: StyleSheet(this).also { styleSheet = it }
res.block()
public fun styleSheet(block: StyleSheet.() -> Unit) {
if (styleSheet == null) {
styleSheet = StyleSheet(this@AbstractVisionGroup)
}
styleSheet!!.block()
}
override fun propertyChanged(name: Name) {
super.propertyChanged(name)
for(obj in this) {
for (obj in this) {
obj.propertyChanged(name)
}
}
// TODO Consider renaming to `StructureChangeListener` (singular)
private data class StructureChangeListeners(val owner: Any?, val callback: (Name, Vision?) -> Unit)
private data class StructureChangeListener(val owner: Any?, val callback: (NameToken, Vision?) -> Unit)
@Transient
private val structureChangeListeners = HashSet<StructureChangeListeners>()
private val structureChangeListeners = HashSet<StructureChangeListener>()
/**
* Add listener for children change
*/
override fun onChildrenChange(owner: Any?, action: (Name, Vision?) -> Unit) {
override fun onChildrenChange(owner: Any?, action: (NameToken, Vision?) -> Unit) {
structureChangeListeners.add(
StructureChangeListeners(
StructureChangeListener(
owner,
action
)
@ -62,7 +66,7 @@ abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
/**
* Propagate children change event upwards
*/
protected fun childrenChanged(name: Name, child: Vision?) {
protected fun childrenChanged(name: NameToken, child: Vision?) {
structureChangeListeners.forEach { it.callback(name, child) }
}
@ -77,10 +81,11 @@ abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
protected abstract fun setChild(token: NameToken, child: Vision)
/**
* Add a static child. Statics could not be found by name, removed or replaced
* Add a static child. Statics could not be found by name, removed or replaced. Changing statics also do not trigger events.
*/
protected open fun addStatic(child: Vision) =
set(NameToken("@static(${child.hashCode()})").asName(), child)
protected open fun addStatic(child: Vision): Unit {
setChild(NameToken("@static", index = child.hashCode().toString()), child)
}
protected abstract fun createGroup(): AbstractVisionGroup
@ -124,6 +129,7 @@ abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
when {
name.isEmpty() -> {
if (child != null) {
attach(child)
addStatic(child)
}
}
@ -135,6 +141,7 @@ abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
attach(child)
setChild(token, child)
}
childrenChanged(token, child)
}
else -> {
//TODO add safety check
@ -142,7 +149,13 @@ abstract class AbstractVisionGroup : AbstractVision(), MutableVisionGroup {
parent[name.tokens.last().asName()] = child
}
}
childrenChanged(name, child)
}
override fun update(meta: Meta) {
val styleMeta = meta.get("styleSheet").node
if (styleMeta != null) {
this.styleSheet?.update(styleMeta)
}
super.update(meta)
}
}

View File

@ -235,12 +235,12 @@ public object Colors {
}
}
/**
* Convert three bytes representing color to Meta
*/
fun rgbToMeta(r: UByte, g: UByte, b: UByte): Meta = Meta {
RED_KEY put r.toInt()
GREEN_KEY put g.toInt()
BLUE_KEY put b.toInt()
}
// /**
// * Convert three bytes representing color to Meta
// */
// fun rgbToMeta(r: UByte, g: UByte, b: UByte): Meta = Meta {
// RED_KEY put r.toInt()
// GREEN_KEY put g.toInt()
// BLUE_KEY put b.toInt()
// }
}

View File

@ -8,13 +8,10 @@ import kotlinx.serialization.Serializable
@Serializable
@SerialName("group")
class SimpleVisionGroup : AbstractVisionGroup() {
public class SimpleVisionGroup : AbstractVisionGroup() {
override var styleSheet: StyleSheet? = null
//FIXME to be lifted to AbstractVisualGroup after https://github.com/Kotlin/kotlinx.serialization/issues/378 is fixed
override var properties: Config? = null
@SerialName("children")
private val _children = HashMap<NameToken, Vision>()
override val children: Map<NameToken, Vision> get() = _children

View File

@ -16,11 +16,11 @@ import kotlinx.serialization.encoding.Encoder
* A container for styles
*/
@Serializable
public class StyleSheet private constructor(private val styleMap: MutableMap<String, Meta> = LinkedHashMap()) {
public class StyleSheet private constructor(private val styleMap: MutableMap<String, Meta>) {
@Transient
internal var owner: Vision? = null
public constructor(owner: Vision) : this() {
public constructor(owner: Vision) : this(LinkedHashMap()) {
this.owner = owner
}
@ -91,6 +91,10 @@ public class StyleSheet private constructor(private val styleMap: MutableMap<Str
}
}
public fun StyleSheet.update(styleMeta: Meta){
TODO()
}
/**
* Add style name to the list of styles to be resolved later. The style with given name does not necessary exist at the moment.

View File

@ -69,7 +69,7 @@ public interface Vision : Configurable {
}
/**
* Update this vision using external meta.
* Update this vision using external meta. Children are not updated.
*/
public fun update(meta: Meta)

View File

@ -0,0 +1,50 @@
package hep.dataforge.vision
import hep.dataforge.meta.Config
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.set
import hep.dataforge.names.NameToken
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
//public class VisionChange(public val properties: Meta, public val childrenChange: Map<NameToken, VisionChange>)
public class VisionChangeCollector(
public val manager: VisionManager,
public val scope: CoroutineScope,
public val vision: Vision,
public val lock: Mutex = Mutex()
) {
private val collector: Config = Config()
private val childrenCollectors = HashMap<NameToken, VisionChangeCollector>()
init {
vision.onPropertyChange(this) { propertyName ->
collector[propertyName] = vision.properties?.get(propertyName)
}
if (vision is VisionGroup) {
vision.children.forEach { (token, child) ->
childrenCollectors[token] = VisionChangeCollector(manager, scope, child, lock)
}
}
if (vision is MutableVisionGroup) {
TODO("Tread vision structure change")
// vision.onChildrenChange(this) { childName, child ->
// if(child == null){
// childrenCollectors[childName] = null
// } else {
// childrenCollectors[childName] = manager.encodeToMeta(child)
// }
// }
}
}
}
//
//fun collectUpdates(manager: VisionManager, scope: CoroutineScope, vision: Vision): Flow<Meta> {
//
//
// vision.
//}

View File

@ -87,7 +87,7 @@ public interface MutableVisionGroup : VisionGroup, VisionContainerBuilder<Vision
* @param owner the handler to properly remove listeners
* @param action First argument of the action is the name of changed child. Second argument is the new value of the object.
*/
public fun onChildrenChange(owner: Any?, action: (Name, Vision?) -> Unit)
public fun onChildrenChange(owner: Any?, action: (NameToken, Vision?) -> Unit)
/**
* Remove children change listener

View File

@ -3,6 +3,10 @@ package hep.dataforge.vision
import hep.dataforge.context.*
import hep.dataforge.meta.*
import hep.dataforge.meta.descriptors.NodeDescriptor
import hep.dataforge.names.asName
import hep.dataforge.values.Null
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.modules.SerializersModule
@ -10,7 +14,6 @@ import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.subclass
import kotlin.reflect.KClass
@DFExperimental
public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
override val tag: PluginTag get() = Companion.tag
@ -50,35 +53,29 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
?: error("Expected node, but value found. Check your serializer!")
public fun updateVision(vision: Vision, meta: Meta) {
vision.update(meta)
if (vision is MutableVisionGroup) {
val children by meta.node()
children?.items?.forEach { (token, item) ->
when {
item.value == Null -> vision[token] = null //Null means removal
item.node != null -> {
val node = item.node!!
val type by node.string()
if (type != null) {
//If the type is present considering it as new node, not an update
vision[token.asName()] = decodeFromMeta(node)
} else {
val existing = vision.children[token]
if (existing != null) {
updateVision(existing, node)
}
}
}
}
}
}
}
// public fun <T : Vision> VisionForm<T>.buildVision(meta: Meta, descriptor: NodeDescriptor? = null): T {
// val json = meta.toJson(descriptor)
// return jsonFormat.decodeFromJsonElement(serializer, json)
// }
//
// @OptIn(ExperimentalSerializationApi::class)
// public fun buildVision(meta: Meta): Vision {
// val type = meta["type"].string ?: Vision.serializer().descriptor.serialName
// val form = forms.values.find { it.name.toString() == type } ?: error("Could not resolve a form for type $type")
// return form.buildVision(meta)
// }
//
// public inline fun <reified T : Vision> buildSpecificVision(meta: Meta): T {
// val factory = resolveVisionForm(T::class)
// return factory.buildVision(meta)
// }
//
// public fun <T : Vision> writeVisionToMeta(vision: T): Meta {
// val form = resolveVisionForm(vision::class) ?: error("Could not resolve a form for $vision")
// val json = jsonFormat.encodeToJsonElement(form.serializer, vision)
// return json.toMetaItem().node!!
// }
//
// public fun updateVision(vision: Vision, meta: Meta) {
// resolveVisionForm(vision::class).patch(vision, meta)
// }
public companion object : PluginFactory<VisionManager> {
override val tag: PluginTag = PluginTag(name = "vision", group = PluginTag.DATAFORGE_GROUP)

View File

@ -57,7 +57,7 @@ public class SolidGroup : AbstractVisionGroup(), Solid, PrototypeHolder {
override var scale: Point3D? = null
@SerialName("children")
private val _children = HashMap<NameToken, Vision>()
private val _children = LinkedHashMap<NameToken, Vision>()
override val children: Map<NameToken, Vision> get() = _children
override fun attachChildren() {
@ -122,7 +122,7 @@ internal class Prototypes(
override fun removeChild(token: NameToken) {
children.remove(token)
childrenChanged(token.asName(), null)
childrenChanged(token, null)
}
override fun setChild(token: NameToken, child: Vision) {

View File

@ -16,43 +16,7 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.modules.*
import kotlin.reflect.KClass
//@OptIn(ExperimentalSerializationApi::class)
//@DFExperimental
//private fun SerializersModule.extractFactories(): List<SolidForm<*>> {
// val list = ArrayList<SolidForm<*>>()
//
// val collector = object : SerializersModuleCollector {
// override fun <T : Any> contextual(kClass: KClass<T>, serializer: KSerializer<T>) {
// //Do nothing
// }
//
// override fun <Base : Any, Sub : Base> polymorphic(
// baseClass: KClass<Base>,
// actualClass: KClass<Sub>,
// actualSerializer: KSerializer<Sub>,
// ) {
// if (baseClass == Vision::class) {
// @Suppress("UNCHECKED_CAST") val factory: SolidForm<Solid> = SolidForm(
// actualClass as KClass<Solid>,
// actualSerializer as KSerializer<Solid>
// )
// list.add(factory)
// }
// }
//
// override fun <Base : Any> polymorphicDefault(
// baseClass: KClass<Base>,
// defaultSerializerProvider: (className: String?) -> DeserializationStrategy<out Base>?,
// ) {
// TODO("Not yet implemented")
// }
//
// }
// dumpTo(collector)
// return list
//}
@DFExperimental
public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
public val visionManager: VisionManager by require(VisionManager)
@ -96,18 +60,16 @@ public class SolidManager(meta: Meta) : AbstractPlugin(meta) {
}
}
public val jsonForSolids = Json{
internal val jsonForSolids: Json = Json{
prettyPrint = true
useArrayPolymorphism = false
encodeDefaults = false
ignoreUnknownKeys = true
serializersModule = SolidManager.serializersModuleForSolids
serializersModule = serializersModuleForSolids
}
@OptIn(DFExperimental::class)
public fun encodeToString(solid: Solid): String = jsonForSolids.encodeToString(Vision.serializer(), solid)
@OptIn(DFExperimental::class)
public fun decodeFromString(str: String): Vision = jsonForSolids.decodeFromString(Vision.serializer(), str).also {
if(it is VisionGroup){
it.attachChildren()

View File

@ -91,7 +91,7 @@ public fun Solid.color(rgb: Int) {
public fun Solid.color(r: UByte, g: UByte, b: UByte): Unit = setItem(
MATERIAL_COLOR_KEY,
Colors.rgbToMeta(r, g, b)
Colors.rgbToString(r, g, b).asValue()
)
/**

View File

@ -1,15 +1,20 @@
package hep.dataforge.vision.solid.specifications
import hep.dataforge.meta.Scheme
import hep.dataforge.meta.SchemeSpec
import hep.dataforge.meta.int
import hep.dataforge.meta.spec
import hep.dataforge.meta.*
public class Canvas3DOptions : Scheme() {
public var axes: Axes by spec(Axes, Axes.empty())
public var camera: Camera by spec(Camera, Camera.empty())
public var controls: Controls by spec(Controls, Controls.empty())
public var minSize: Int by int(300)
public var minWith: Number by number { minSize }
public var minHeight: Number by number { minSize }
public var maxSize: Int by int(Int.MAX_VALUE)
public var maxWith: Number by number { maxSize }
public var maxHeight: Number by number { maxSize }
public companion object : SchemeSpec<Canvas3DOptions>(::Canvas3DOptions)
}

View File

@ -4,6 +4,6 @@ import hep.dataforge.meta.Scheme
import hep.dataforge.meta.SchemeSpec
class Controls : Scheme() {
companion object : SchemeSpec<Controls>(::Controls)
public class Controls : Scheme() {
public companion object : SchemeSpec<Controls>(::Controls)
}

View File

@ -35,7 +35,7 @@ internal fun mergeChild(parent: VisionGroup, child: Vision): Vision {
}
@DFExperimental
object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
override fun SolidGroup.transformInPlace() {
fun MutableVisionGroup.replaceChildren() {

View File

@ -9,7 +9,7 @@ import hep.dataforge.vision.solid.Proxy
import hep.dataforge.vision.solid.SolidGroup
@DFExperimental
object UnRef : VisualTreeTransform<SolidGroup>() {
internal object UnRef : VisualTreeTransform<SolidGroup>() {
private fun VisionGroup.countRefs(): Map<Name, Int> {
return children.values.fold(HashMap()) { reducer, obj ->
if (obj is VisionGroup) {

View File

@ -5,11 +5,11 @@ import hep.dataforge.vision.Vision
/**
* A root class for [Vision] tree optimization
*/
abstract class VisualTreeTransform<T : Vision> {
public abstract class VisualTreeTransform<T : Vision> {
protected abstract fun T.transformInPlace()
protected abstract fun T.clone(): T
operator fun invoke(source: T, inPlace: Boolean = true): T {
public operator fun invoke(source: T, inPlace: Boolean = true): T {
val newSource = if (inPlace) {
source
} else {
@ -21,7 +21,7 @@ abstract class VisualTreeTransform<T : Vision> {
}
}
fun <T : Vision> T.transform(vararg transform: VisualTreeTransform<T>): T {
public fun <T : Vision> T.transform(vararg transform: VisualTreeTransform<T>): T {
var res = this
transform.forEach {
res = it(res)
@ -29,6 +29,6 @@ fun <T : Vision> T.transform(vararg transform: VisualTreeTransform<T>): T {
return res
}
fun <T : Vision> T.transformInPlace(vararg transform: VisualTreeTransform<in T>) {
public fun <T : Vision> T.transformInPlace(vararg transform: VisualTreeTransform<in T>) {
transform.forEach { it(this) }
}

View File

@ -2,6 +2,7 @@ package hep.dataforge.vision.solid.three
import hep.dataforge.meta.boolean
import hep.dataforge.meta.node
import hep.dataforge.names.Name
import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.names.startsWith
@ -19,13 +20,13 @@ import kotlin.reflect.KClass
/**
* Basic geometry-based factory
*/
abstract class MeshThreeFactory<in T : Solid>(
public abstract class MeshThreeFactory<in T : Solid>(
override val type: KClass<in T>
) : ThreeFactory<T> {
/**
* Build a geometry for an object
*/
abstract fun buildGeometry(obj: T): BufferGeometry
public abstract fun buildGeometry(obj: T): BufferGeometry
override fun invoke(obj: T): Mesh {
val geometry = buildGeometry(obj)
@ -60,14 +61,14 @@ abstract class MeshThreeFactory<in T : Solid>(
return mesh
}
companion object {
val EDGES_KEY = "edges".asName()
val WIREFRAME_KEY = "wireframe".asName()
val ENABLED_KEY = "enabled".asName()
val EDGES_ENABLED_KEY = EDGES_KEY + ENABLED_KEY
val EDGES_MATERIAL_KEY = EDGES_KEY + SolidMaterial.MATERIAL_KEY
val WIREFRAME_ENABLED_KEY = WIREFRAME_KEY + ENABLED_KEY
val WIREFRAME_MATERIAL_KEY = WIREFRAME_KEY + SolidMaterial.MATERIAL_KEY
public companion object {
public val EDGES_KEY: Name = "edges".asName()
public val WIREFRAME_KEY: Name = "wireframe".asName()
public val ENABLED_KEY: Name = "enabled".asName()
public val EDGES_ENABLED_KEY: Name = EDGES_KEY + ENABLED_KEY
public val EDGES_MATERIAL_KEY: Name = EDGES_KEY + SolidMaterial.MATERIAL_KEY
public val WIREFRAME_ENABLED_KEY: Name = WIREFRAME_KEY + ENABLED_KEY
public val WIREFRAME_MATERIAL_KEY: Name = WIREFRAME_KEY + SolidMaterial.MATERIAL_KEY
}
}

View File

@ -4,8 +4,8 @@ import hep.dataforge.vision.solid.Box
import hep.dataforge.vision.solid.detail
import info.laht.threekt.geometries.BoxBufferGeometry
object ThreeBoxFactory : MeshThreeFactory<Box>(Box::class) {
override fun buildGeometry(obj: Box) =
public object ThreeBoxFactory : MeshThreeFactory<Box>(Box::class) {
override fun buildGeometry(obj: Box): BoxBufferGeometry =
obj.detail?.let { detail ->
BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize, detail, detail, detail)
} ?: BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize)

View File

@ -35,7 +35,6 @@ import org.w3c.dom.HTMLElement
import org.w3c.dom.Node
import org.w3c.dom.events.MouseEvent
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
/**
@ -112,7 +111,10 @@ public class ThreeCanvas(
element.appendChild(renderer.domElement)
renderer.setSize(max(options.minSize, element.clientWidth), max(options.minSize, element.clientWidth))
renderer.setSize(
element.clientWidth.coerceIn(options.minWith.toInt()..options.maxWith.toInt()),
element.clientHeight.coerceIn(options.minHeight.toInt()..options.maxHeight.toInt())
)
window.onresize = {
renderer.setSize(element.clientWidth, element.clientWidth)
@ -238,7 +240,7 @@ public class ThreeCanvas(
}
public companion object {
public const val DO_NOT_HIGHLIGHT_TAG = "doNotHighlight"
public const val DO_NOT_HIGHLIGHT_TAG: String = "doNotHighlight"
private const val HIGHLIGHT_NAME = "@highlight"
private const val SELECT_NAME = "@select"
}
@ -251,7 +253,8 @@ public fun ThreePlugin.output(
): ThreeCanvas = ThreeCanvas(element, this, spec, onClick)
public fun ThreePlugin.render(
element: HTMLElement, obj: Solid,
spec: Canvas3DOptions = Canvas3DOptions.empty(),
onClick: ((Name?) -> Unit)? = null,
): Unit = output(element, spec, onClick).render(obj)
element: HTMLElement,
obj: Solid,
onSelect: ((Name?) -> Unit)? = null,
options: Canvas3DOptions.() -> Unit = {},
): Unit = output(element, Canvas3DOptions(options), onSelect).render(obj)

View File

@ -9,17 +9,17 @@ 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 kotlinx.browser.document
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.CanvasTextBaseline
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.MIDDLE
import kotlin.browser.document
import kotlin.reflect.KClass
/**
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
*/
object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun invoke(obj: SolidLabel): Object3D {

View File

@ -8,7 +8,7 @@ import info.laht.threekt.objects.Mesh
/**
* This should be inner, because it uses object builder
*/
class ThreeCompositeFactory(val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) {
public class ThreeCompositeFactory(public val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) {
override fun buildGeometry(obj: Composite): BufferGeometry {
val first = three.buildObject3D(obj.first) as? Mesh ?: error("First part of composite is not a mesh")

View File

@ -4,7 +4,7 @@ import hep.dataforge.vision.solid.Convex
import info.laht.threekt.external.geometries.ConvexBufferGeometry
import info.laht.threekt.math.Vector3
object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
public object ThreeConvexFactory : MeshThreeFactory<Convex>(Convex::class) {
override fun buildGeometry(obj: Convex): ConvexBufferGeometry {
@Suppress("USELESS_CAST") val vectors = obj.points.toTypedArray() as Array<Vector3>
return ConvexBufferGeometry(vectors)

View File

@ -7,7 +7,7 @@ import info.laht.threekt.geometries.CylinderBufferGeometry
import kotlin.math.PI
import kotlin.math.pow
object ThreeCylinderFactory : MeshThreeFactory<ConeSegment>(ConeSegment::class) {
public object ThreeCylinderFactory : MeshThreeFactory<ConeSegment>(ConeSegment::class) {
override fun buildGeometry(obj: ConeSegment): BufferGeometry {
val cylinder = obj.detail?.let {
val segments = it.toDouble().pow(0.5).toInt()

View File

@ -18,21 +18,21 @@ import kotlin.reflect.KClass
* Builder and updater for three.js object
*/
@Type(TYPE)
interface ThreeFactory<in T : Vision> {
public interface ThreeFactory<in T : Vision> {
val type: KClass<in T>
public val type: KClass<in T>
operator fun invoke(obj: T): Object3D
public operator fun invoke(obj: T): Object3D
companion object {
const val TYPE = "threeFactory"
public companion object {
public const val TYPE: String = "threeFactory"
}
}
/**
* Update position, rotation and visibility
*/
fun Object3D.updatePosition(obj: Vision) {
public fun Object3D.updatePosition(obj: Vision) {
visible = obj.visible ?: true
if(obj is Solid) {
position.set(obj.x, obj.y, obj.z)
@ -45,7 +45,7 @@ fun Object3D.updatePosition(obj: Vision) {
/**
* Update non-position non-geometry property
*/
fun Object3D.updateProperty(source: Vision, propertyName: Name) {
public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
if (this is Mesh && propertyName.startsWith(MATERIAL_KEY)) {
this.material = getMaterial(source, false)
} else if (
@ -63,7 +63,7 @@ fun Object3D.updateProperty(source: Vision, propertyName: Name) {
/**
* Generic factory for elements which provide inside geometry builder
*/
object ThreeShapeFactory : MeshThreeFactory<GeometrySolid>(GeometrySolid::class) {
public object ThreeShapeFactory : MeshThreeFactory<GeometrySolid>(GeometrySolid::class) {
override fun buildGeometry(obj: GeometrySolid): BufferGeometry {
return obj.run {
ThreeGeometryBuilder().apply { toGeometry(this) }.build()

View File

@ -12,7 +12,7 @@ import kotlin.reflect.KClass
/**
*
*/
object ThreeLabelFactory : ThreeFactory<SolidLabel> {
public object ThreeLabelFactory : ThreeFactory<SolidLabel> {
override val type: KClass<in SolidLabel> get() = SolidLabel::class
override fun invoke(obj: SolidLabel): Object3D {

View File

@ -10,7 +10,7 @@ import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments
import kotlin.reflect.KClass
object ThreeLineFactory : ThreeFactory<PolyLine> {
public object ThreeLineFactory : ThreeFactory<PolyLine> {
override val type: KClass<PolyLine> get() = PolyLine::class
override fun invoke(obj: PolyLine): Object3D {

View File

@ -11,7 +11,7 @@ import kotlin.collections.set
import kotlin.reflect.KClass
import info.laht.threekt.objects.Group as ThreeGroup
class ThreePlugin : AbstractPlugin() {
public class ThreePlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag
private val objectFactories = HashMap<KClass<out Solid>, ThreeFactory<*>>()
@ -35,9 +35,9 @@ class ThreePlugin : AbstractPlugin() {
as ThreeFactory<Solid>?
}
fun buildObject3D(obj: Solid): Object3D {
public fun buildObject3D(obj: Solid): Object3D {
return when (obj) {
is ThreeVision -> obj.toObject3D()
is ThreeVision -> obj.render()
is Proxy -> proxyFactory(obj)
is SolidGroup -> {
val group = ThreeGroup()
@ -69,17 +69,17 @@ class ThreePlugin : AbstractPlugin() {
}
}
obj.onChildrenChange(this) { name, child ->
if (name.isEmpty()) {
logger.error { "Children change with empty name on $group" }
return@onChildrenChange
}
obj.onChildrenChange(this) { nameToken, child ->
// if (name.isEmpty()) {
// logger.error { "Children change with empty name on $group" }
// return@onChildrenChange
// }
// val parentName = name.cutLast()
// val childName = name.last()!!
//removing old object
findChild(name)?.let { oldChild ->
findChild(nameToken.asName())?.let { oldChild ->
oldChild.parent?.remove(oldChild)
}
@ -87,7 +87,7 @@ class ThreePlugin : AbstractPlugin() {
if (child != null && child is Solid) {
try {
val object3D = buildObject3D(child)
set(name, object3D)
set(nameToken, object3D)
} catch (ex: Throwable) {
logger.error(ex) { "Failed to render $child" }
}
@ -108,10 +108,10 @@ class ThreePlugin : AbstractPlugin() {
}
}
companion object : PluginFactory<ThreePlugin> {
override val tag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
override val type = ThreePlugin::class
override fun invoke(meta: Meta, context: Context) = ThreePlugin()
public companion object : PluginFactory<ThreePlugin> {
override val tag: PluginTag = PluginTag("visual.three", PluginTag.DATAFORGE_GROUP)
override val type: KClass<ThreePlugin> = ThreePlugin::class
override fun invoke(meta: Meta, context: Context): ThreePlugin = ThreePlugin()
}
}

View File

@ -11,7 +11,7 @@ import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh
import kotlin.reflect.KClass
class ThreeProxyFactory(val three: ThreePlugin) : ThreeFactory<Proxy> {
public class ThreeProxyFactory(public val three: ThreePlugin) : ThreeFactory<Proxy> {
private val cache = HashMap<Solid, Object3D>()
override val type: KClass<Proxy> = Proxy::class

View File

@ -5,7 +5,7 @@ import hep.dataforge.vision.solid.detail
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.geometries.SphereBufferGeometry
object ThreeSphereFactory : MeshThreeFactory<Sphere>(Sphere::class) {
public object ThreeSphereFactory : MeshThreeFactory<Sphere>(Sphere::class) {
override fun buildGeometry(obj: Sphere): BufferGeometry {
return obj.detail?.let {detail ->
SphereBufferGeometry(

View File

@ -8,11 +8,6 @@ import kotlinx.serialization.Serializable
/**
* A custom visual object that has its own Three.js renderer
*/
public interface ThreeVision : Solid {
public fun toObject3D(): Object3D
}
@Serializable
public class CustomThreeVision(public val threeFactory: ThreeFactory<Solid>) : BasicSolid(), ThreeVision {
override fun toObject3D(): Object3D = threeFactory(this)
public abstract class ThreeVision : BasicSolid() {
public abstract fun render(): Object3D
}