diff --git a/CHANGELOG.md b/CHANGELOG.md index d638f0c8..8ea6fc9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,11 +19,6 @@ - VisionGroup builder accepts `null` as name for statics instead of `""` - gdml sphere is rendered as a SphereLayer instead of Sphere (#35) - Tube is replaced by more general ConeSurface -- position, rotation and size moved to properties -- prototypes moved to children -- Immutable Solid instances -- Property listeners are not triggered if there are no changes. -- Feedback websocket connection in the client. ### Deprecated diff --git a/README.md b/README.md index c9312320..621f474f 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ * [visionforge-gdml](#visionforge-gdml) * [Visualization for External Systems](#visualization-for-external-systems) * [Demonstrations](#demonstrations) - * [Simple Example - Spatial Showcase](#simple-example---spatial-showcase) + * [Simple Example - Solid Showcase](#simple-example---solid-showcase) * [Full-Stack Application Example - Muon Monitor](#full-stack-application-example---muon-monitor-visualization) * [GDML Example](#gdml-example) @@ -118,16 +118,16 @@ The `demo` module contains several example projects (demonstrations) of using th They are briefly described in this section, for more details please consult the corresponding per-project README file. -### Simple Example - Spatial Showcase +### Simple Example - Solid Showcase Contains a simple demonstration with a grid including a few shapes that you can rotate, move camera, and so on. Some shapes will also periodically change their color and visibility. -[More details](demo/spatial-showcase/README.md) +[More details](demo/solid-showcase/README.md) **Example view:** -![](docs/images/spatial-showcase.png) +![](docs/images/solid-showcase.png) ### Full-Stack Application Example - Muon Monitor Visualization @@ -156,4 +156,4 @@ Visualization example for geometry defined as GDML file. ## Thanks and references The original three.js bindings were made by [Lars Ivar Hatledal](https://github.com/markaren), but the project is discontinued right now. -All other libraries are explicitly shown as dependencies. We would like to express specific thanks to JetBrains Kotlin-JS team for consulting us during the work. \ No newline at end of file +All other libraries are explicitly shown as dependencies. We would like to express specific thanks to JetBrains Kotlin-JS team for consulting us during the work. diff --git a/build.gradle.kts b/build.gradle.kts index 4ab2a7d5..8236106e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,7 +20,7 @@ allprojects { } group = "space.kscience" - version = "0.2.0-dev-22" + version = "0.2.0-dev-21" } subprojects { diff --git a/demo/gdml/README.md b/demo/gdml/README.md index 6ad546f1..5c2a5abe 100644 --- a/demo/gdml/README.md +++ b/demo/gdml/README.md @@ -1,12 +1,10 @@ - ### GDML Example Visualization example for geometry defined as GDML file. ##### Building project -To build the app, run `demo/gdml/Tasks/distribution/jsBrowserDistribution` Gradle task, then open -`demo/gdml/build/distribuions/gdml-js-0.1.3-dev/index.html` file in your browser, and +To build the app, run `demo/gdml/Tasks/kotlin browser/jsBrowserDistribution` Gradle task, then drag-and-drop GDML file to the window to see visualization. For an example file, you can use `demo/gdml/src/jsMain/resources/cubes.gdml`. diff --git a/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisionTest.kt b/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisualTest.kt similarity index 97% rename from demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisionTest.kt rename to demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisualTest.kt index 17bdc014..c12c7215 100644 --- a/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisionTest.kt +++ b/demo/gdml/src/commonTest/kotlin/space/kscience/visionforge/gdml/GDMLVisualTest.kt @@ -10,7 +10,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull -class GDMLVisionTest { +class GDMLVisualTest { // @Test // fun testCubesStyles(){ diff --git a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt index 4f6560b6..1d619dfb 100644 --- a/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt +++ b/demo/gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/demo/GDMLAppComponent.kt @@ -10,7 +10,6 @@ import space.kscience.dataforge.context.fetch import space.kscience.dataforge.names.Name import space.kscience.gdml.Gdml import space.kscience.gdml.decodeFromString -import space.kscience.visionforge.gdml.markLayers import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.tab @@ -33,11 +32,7 @@ val GDMLApp = functionalComponent("GDMLApp") { props -> val parsedVision = when { name.endsWith(".gdml") || name.endsWith(".xml") -> { val gdml = Gdml.decodeFromString(data) - gdml.toVision().apply { - root(visionManager) - console.info("Marking layers for file $name") - markLayers() - } + gdml.toVision() } name.endsWith(".json") -> visionManager.decodeFromString(data) else -> { diff --git a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt index ec28649c..b43ce062 100644 --- a/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt +++ b/demo/js-playground/src/main/kotlin/JsPlaygroundApp.kt @@ -1,5 +1,8 @@ import kotlinx.browser.document -import kotlinx.css.* +import kotlinx.css.height +import kotlinx.css.vh +import kotlinx.css.vw +import kotlinx.css.width import react.child import react.dom.render import space.kscience.dataforge.context.Context @@ -29,8 +32,6 @@ private class JsPlaygroundApp : Application { render(element) { styledDiv { css{ - padding(0.pt) - margin(0.pt) height = 100.vh width = 100.vw } diff --git a/demo/js-playground/src/main/resources/index.html b/demo/js-playground/src/main/resources/index.html index 83a9016e..7a777ef1 100644 --- a/demo/js-playground/src/main/resources/index.html +++ b/demo/js-playground/src/main/resources/index.html @@ -2,11 +2,10 @@ - js-playground - +
\ No newline at end of file diff --git a/demo/muon-monitor/README.md b/demo/muon-monitor/README.md index 87b3cf43..df95c968 100644 --- a/demo/muon-monitor/README.md +++ b/demo/muon-monitor/README.md @@ -26,9 +26,8 @@ with it. ##### Building project To run full-stack Muon Monitor Visualization application (both JVM server and Web browser front-end), -run `demo/muon-monitor/application/run` task. +run `demo/muon-monitor/Tasks/application/run` task. ##### Example view: ![](../../docs/images/muon-monitor.png) - diff --git a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt index 17d5ac86..e87a2abc 100644 --- a/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt +++ b/demo/muon-monitor/src/commonMain/kotlin/ru/mipt/npm/muon/monitor/Monitor.kt @@ -11,7 +11,7 @@ import space.kscience.visionforge.solid.plus class SC1( val name: String, val center: Point3D, - val xSize: Float = PIXEL_XY_SIZE, val ySize: Float = PIXEL_XY_SIZE, val zSize: Float = PIXEL_Z_SIZE + val xSize: Double = PIXEL_XY_SIZE, val ySize: Double = PIXEL_XY_SIZE, val zSize: Double = PIXEL_Z_SIZE ) class SC16( @@ -121,12 +121,12 @@ internal expect fun readMonitorConfig(): String object Monitor { const val GEOMETRY_TOLERANCE = 0.01 - const val PIXEL_XY_SIZE = 122.0f - const val PIXEL_XY_SPACING = 123.2f - const val PIXEL_Z_SIZE = 30.0f - const val CENTRAL_LAYER_Z = 0.0f - const val UPPER_LAYER_Z = -166.0f - const val LOWER_LAYER_Z = 180.0f + const val PIXEL_XY_SIZE = 122.0 + const val PIXEL_XY_SPACING = 123.2 + const val PIXEL_Z_SIZE = 30.0 + const val CENTRAL_LAYER_Z = 0.0 + const val UPPER_LAYER_Z = -166.0 + const val LOWER_LAYER_Z = 180.0 /** * Build map for the whole monitor diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt index 579bca15..71a96647 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/Pixel.kt @@ -17,19 +17,19 @@ import kotlin.random.Random internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) { // val layer: Layer = findLayer(center.z); private val upLayer = - findLayer(sc.center.z + sc.zSize / 2f)//Layer("${name}_up", center.z + zSize / 2.0); + findLayer(sc.center.z + sc.zSize / 2.0)//Layer("${name}_up", center.z + zSize / 2.0); private val bottomLayer = - findLayer(sc.center.z - sc.zSize / 2f)//Layer("${name}_bottom", center.z - zSize / 2.0); + findLayer(sc.center.z - sc.zSize / 2.0)//Layer("${name}_bottom", center.z - zSize / 2.0); private val centralLayer = findLayer(sc.center.z) - private val center = Vector3D(sc.center.x.toDouble(), sc.center.y.toDouble(), sc.center.z.toDouble()) + private val center = Vector3D(sc.center.x, sc.center.y, sc.center.z) private val sideLayers: Array = arrayOf( - Plane(center.add(Vector3D(PIXEL_XY_SIZE / 2.0, 0.0, 0.0)), Vector3D(1.0, 0.0, 0.0), GEOMETRY_TOLERANCE), - Plane(center.add(Vector3D(-PIXEL_XY_SIZE / 2.0, 0.0, 0.0)), Vector3D(-1.0, 0.0, 0.0), GEOMETRY_TOLERANCE), - Plane(center.add(Vector3D(0.0, PIXEL_XY_SIZE / 2.0, 0.0)), Vector3D(0.0, 1.0, 0.0), GEOMETRY_TOLERANCE), - Plane(center.add(Vector3D(0.0, -PIXEL_XY_SIZE / 2.0, 0.0)), Vector3D(0.0, -1.0, 0.0), GEOMETRY_TOLERANCE) - ) + Plane(center.add(Vector3D(PIXEL_XY_SIZE / 2, 0.0, 0.0)), Vector3D(1.0, 0.0, 0.0), GEOMETRY_TOLERANCE), + Plane(center.add(Vector3D(-PIXEL_XY_SIZE / 2, 0.0, 0.0)), Vector3D(-1.0, 0.0, 0.0), GEOMETRY_TOLERANCE), + Plane(center.add(Vector3D(0.0, PIXEL_XY_SIZE / 2, 0.0)), Vector3D(0.0, 1.0, 0.0), GEOMETRY_TOLERANCE), + Plane(center.add(Vector3D(0.0, -PIXEL_XY_SIZE / 2, 0.0)), Vector3D(0.0, -1.0, 0.0), GEOMETRY_TOLERANCE) + ); //TODO add efficiency private fun containsPoint(x: Double, y: Double, z: Double, tolerance: Double = GEOMETRY_TOLERANCE): Boolean { @@ -63,8 +63,8 @@ internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) { * The layer number from up to bottom */ fun getLayerNumber(): Int { - return when (this.center.z.toFloat()) { - UPPER_LAYER_Z -> 1 + return when (this.center.z) { + UPPER_LAYER_Z -> 1; CENTRAL_LAYER_Z -> 2; LOWER_LAYER_Z -> 3; else -> throw RuntimeException("Unknown layer"); diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt index c2578783..de704441 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/line.kt @@ -45,7 +45,7 @@ fun makeTrack(start: Vector3D, direction: Vector3D): Line { fun makeTrack(x: Double, y: Double, theta: Double, phi: Double): Line { //TODO check angle definitions return makeTrack( - Vector3D(x, y, CENTRAL_LAYER_Z.toDouble()), + Vector3D(x, y, CENTRAL_LAYER_Z), Vector3D(phi, theta) ) } diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt index d2ec7235..a4e83c8b 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/monitor.kt @@ -13,12 +13,12 @@ import ru.mipt.npm.muon.monitor.readResource internal const val MINIMAL_TRACK_LENGTH = 10.0 -private val layerCache = HashMap() +private val layerCache = HashMap() -fun findLayer(z: Float): Plane = layerCache.getOrPut(z) { +fun findLayer(z: Double): Plane = layerCache.getOrPut(z) { Plane( - Vector3D(0.0, 0.0, z.toDouble()), Vector3D(0.0, 0.0, 1.0), - Monitor.GEOMETRY_TOLERANCE.toDouble() + Vector3D(0.0, 0.0, z), Vector3D(0.0, 0.0, 1.0), + Monitor.GEOMETRY_TOLERANCE ) } diff --git a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/simulation.kt b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/simulation.kt index f8d45fd4..6019a94a 100644 --- a/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/simulation.kt +++ b/demo/muon-monitor/src/jvmMain/kotlin/ru/mipt/npm/muon/monitor/sim/simulation.kt @@ -28,8 +28,8 @@ interface TrackGenerator { */ class UniformTrackGenerator( override val rnd: RandomGenerator, - val maxX: Float = 4 * PIXEL_XY_SIZE, - val maxY: Float = 4 * PIXEL_XY_SIZE + val maxX: Double = 4 * PIXEL_XY_SIZE, + val maxY: Double = 4 * PIXEL_XY_SIZE ) : TrackGenerator { override fun generate(): Line { @@ -44,8 +44,8 @@ class UniformTrackGenerator( class FixedAngleGenerator( override val rnd: RandomGenerator, val phi: Double, val theta: Double, - val maxX: Float = 4 * PIXEL_XY_SIZE, - val maxY: Float = 4 * PIXEL_XY_SIZE + val maxX: Double = 4 * PIXEL_XY_SIZE, + val maxY: Double = 4 * PIXEL_XY_SIZE ) : TrackGenerator { override fun generate(): Line { val x = (1 - rnd.nextDouble() * 2.0) * maxX @@ -60,8 +60,8 @@ class FixedAngleGenerator( class Cos2TrackGenerator( override val rnd: RandomGenerator, val power: Double = 2.0, - val maxX: Float = 4 * PIXEL_XY_SIZE, - val maxY: Float = 4 * PIXEL_XY_SIZE + val maxX: Double = 4 * PIXEL_XY_SIZE, + val maxY: Double = 4 * PIXEL_XY_SIZE ) : TrackGenerator { override fun generate(): Line { diff --git a/demo/solid-showcase/README.md b/demo/solid-showcase/README.md index 20866220..f8771e13 100644 --- a/demo/solid-showcase/README.md +++ b/demo/solid-showcase/README.md @@ -5,8 +5,8 @@ Some shapes will also periodically change their color and visibility. ##### Building project -To see the JS demo: run `demo/spatial-showcase/Tasks/distribution/jsBrowserDistribution` Gradle task, then open -`build/distribuions/spatial-showcase-js-0.1.3-dev/index.html` file in your browser. +To see the JS demo: run `demo/solid-showcase/Tasks/kotlin browser/jsBrowserRun` Gradle task, then open +`build/distribuions/solid-showcase-js-0.1.3-dev/index.html` file in your browser. To see Java FX demo, run `demo/spatial-showcase/Tasks/application/run` Gradle task, or `main()` from `FXDemoApp.kt`. diff --git a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt index 2385fef1..bfa90827 100644 --- a/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt +++ b/demo/solid-showcase/src/commonMain/kotlin/space/kscience/visionforge/solid/demo/demo.kt @@ -27,7 +27,7 @@ fun VisionLayout.demo(name: String, title: String = name, block: SolidGro } val canvasOptions = Canvas3DOptions { - size { + size{ minSize = 400 } axes { @@ -57,8 +57,9 @@ fun VisionLayout.showcase() { rotationX = PI / 4 color("blue") } - sphereLayer(50, 40, theta = PI / 2) { - rotationX = -PI * 3 / 4 + sphereLayer(50,40){ + theta = (PI/2).toFloat() + rotationX = - PI * 3 / 4 z = 110 color(Colors.pink) } diff --git a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt index 22cac2c6..e3961775 100644 --- a/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt +++ b/demo/solid-showcase/src/jsMain/kotlin/space/kscience/visionforge/solid/demo/VariableBox.kt @@ -1,7 +1,7 @@ package space.kscience.visionforge.solid.demo import info.laht.threekt.core.Object3D -import info.laht.threekt.geometries.BoxGeometry +import info.laht.threekt.geometries.BoxBufferGeometry import info.laht.threekt.objects.Mesh import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.number @@ -23,10 +23,10 @@ internal fun SolidGroup.varBox( action: VariableBox.() -> Unit = {}, ): VariableBox = VariableBox(xSize, ySize).apply(action).also { set(name, it) } -internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision() { +internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeVision() { override fun render(three: ThreePlugin): Object3D { - val geometry = BoxGeometry(xSize, ySize, 1) + val geometry = BoxBufferGeometry(xSize, ySize, 1) val material = ThreeMaterials.DEFAULT.clone() diff --git a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt index 3ce92f0f..1ed557ea 100644 --- a/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt +++ b/demo/solid-showcase/src/jvmMain/kotlin/space/kscience/visionforge/solid/demo/FXDemoApp.kt @@ -14,7 +14,6 @@ class FXDemoApp : App(FXDemoGrid::class) { stage.height = 600.0 view.showcase() - view.showcaseCSG() } } diff --git a/docs/images/all-solids.png b/docs/images/all-solids.png new file mode 100644 index 00000000..9be7b40e Binary files /dev/null and b/docs/images/all-solids.png differ diff --git a/docs/images/big-rotated-box.png b/docs/images/big-rotated-box.png new file mode 100644 index 00000000..7b7cdc14 Binary files /dev/null and b/docs/images/big-rotated-box.png differ diff --git a/docs/images/box.png b/docs/images/box.png new file mode 100644 index 00000000..6824b2d3 Binary files /dev/null and b/docs/images/box.png differ diff --git a/docs/images/classic-hexagon.png b/docs/images/classic-hexagon.png new file mode 100644 index 00000000..f0053ae4 Binary files /dev/null and b/docs/images/classic-hexagon.png differ diff --git a/docs/images/cone-1.png b/docs/images/cone-1.png new file mode 100644 index 00000000..47272f24 Binary files /dev/null and b/docs/images/cone-1.png differ diff --git a/docs/images/cone-2.png b/docs/images/cone-2.png new file mode 100644 index 00000000..1f552182 Binary files /dev/null and b/docs/images/cone-2.png differ diff --git a/docs/images/cone-segment-1.png b/docs/images/cone-segment-1.png new file mode 100644 index 00000000..b5900555 Binary files /dev/null and b/docs/images/cone-segment-1.png differ diff --git a/docs/images/cone-segment-2.png b/docs/images/cone-segment-2.png new file mode 100644 index 00000000..033c1c73 Binary files /dev/null and b/docs/images/cone-segment-2.png differ diff --git a/docs/images/cone-surface-1.png b/docs/images/cone-surface-1.png new file mode 100644 index 00000000..dad20dda Binary files /dev/null and b/docs/images/cone-surface-1.png differ diff --git a/docs/images/cone-surface-2.png b/docs/images/cone-surface-2.png new file mode 100644 index 00000000..7ec66315 Binary files /dev/null and b/docs/images/cone-surface-2.png differ diff --git a/docs/images/cone-surface-fragment-2.png b/docs/images/cone-surface-fragment-2.png new file mode 100644 index 00000000..2d8eaa99 Binary files /dev/null and b/docs/images/cone-surface-fragment-2.png differ diff --git a/docs/images/cone-surface-fragment.png b/docs/images/cone-surface-fragment.png new file mode 100644 index 00000000..2d82835f Binary files /dev/null and b/docs/images/cone-surface-fragment.png differ diff --git a/docs/images/custom-hexagon.png b/docs/images/custom-hexagon.png new file mode 100644 index 00000000..5245fa9f Binary files /dev/null and b/docs/images/custom-hexagon.png differ diff --git a/docs/images/cylinder-1.png b/docs/images/cylinder-1.png new file mode 100644 index 00000000..2e5869bc Binary files /dev/null and b/docs/images/cylinder-1.png differ diff --git a/docs/images/cylinder-2.png b/docs/images/cylinder-2.png new file mode 100644 index 00000000..88c406bf Binary files /dev/null and b/docs/images/cylinder-2.png differ diff --git a/docs/images/cylinder.png b/docs/images/cylinder.png new file mode 100644 index 00000000..fef58bf5 Binary files /dev/null and b/docs/images/cylinder.png differ diff --git a/docs/images/frustum-cone-segment.png b/docs/images/frustum-cone-segment.png new file mode 100644 index 00000000..96e4170f Binary files /dev/null and b/docs/images/frustum-cone-segment.png differ diff --git a/docs/images/frustum-cone.png b/docs/images/frustum-cone.png new file mode 100644 index 00000000..e5f6786c Binary files /dev/null and b/docs/images/frustum-cone.png differ diff --git a/docs/images/high-box.png b/docs/images/high-box.png new file mode 100644 index 00000000..a3da5384 Binary files /dev/null and b/docs/images/high-box.png differ diff --git a/docs/images/scheme.png b/docs/images/scheme.png new file mode 100644 index 00000000..baa6a877 Binary files /dev/null and b/docs/images/scheme.png differ diff --git a/docs/images/small-box.png b/docs/images/small-box.png new file mode 100644 index 00000000..4ebc0cb3 Binary files /dev/null and b/docs/images/small-box.png differ diff --git a/docs/images/sphere.png b/docs/images/sphere.png new file mode 100644 index 00000000..63caacae Binary files /dev/null and b/docs/images/sphere.png differ diff --git a/docs/images/tube-fragment.png b/docs/images/tube-fragment.png new file mode 100644 index 00000000..e2a20ebe Binary files /dev/null and b/docs/images/tube-fragment.png differ diff --git a/docs/images/tube.png b/docs/images/tube.png new file mode 100644 index 00000000..3664be34 Binary files /dev/null and b/docs/images/tube.png differ diff --git a/docs/images/two-boxes-1.png b/docs/images/two-boxes-1.png new file mode 100644 index 00000000..58544e58 Binary files /dev/null and b/docs/images/two-boxes-1.png differ diff --git a/docs/images/two-boxes-2.png b/docs/images/two-boxes-2.png new file mode 100644 index 00000000..8acaaea5 Binary files /dev/null and b/docs/images/two-boxes-2.png differ diff --git a/docs/images/wide-box.png b/docs/images/wide-box.png new file mode 100644 index 00000000..762a76ac Binary files /dev/null and b/docs/images/wide-box.png differ diff --git a/docs/tutorial.md b/docs/tutorial.md new file mode 100644 index 00000000..15b24b3c --- /dev/null +++ b/docs/tutorial.md @@ -0,0 +1,333 @@ +# Tutorial + +#### The main goal of this tutorial is to show all capabilities of ... (this part will be supplemented) + +The simple visualization can be made with function `main`. (this part will be supplemented as well) +```kotlin +import kotlinx.html.div +import space.kscience.dataforge.context.Context +import space.kscience.visionforge.html.ResourceLocation +import space.kscience.visionforge.solid.* +import java.nio.file.Paths + +fun main(){ + val context = Context{ + plugin(Solids) + } + + context.makeVisionFile ( + Paths.get("customFile.html"), + resourceLocation = ResourceLocation.EMBED + ){ + div { + vision { + solid { + } + } + } + } +} +``` +## Solids properties +**We will analyze which basic properties solids have using `box` solid.** + +*Basic properties:* +1. `opacity` - It is set in `float`. It takes on values from 0 to 1, which represent percents of solid opacity. It's initial value is 1. +2. `color` - It can be specified as `Int`, `String`, or as three `Ubytes`, which represent color in `rgb`. Elementally, the solid will have `green` color. +3. `rotation` - it's the point, around which the solid will be rotated. Initially, the value is `Point3D(0, 0, 0)`. Changing `x` coordinate of the point, you make pivot around `x axis`. The same for other coordinates: changing `y` - pivot around `y axis`, changing `z` - pivot around `z axis`. +4. position, which is given by values `x`, `y`, `z`. Initial values are `x = 0`, `y = 0`, `z = 0`. The coordinate system is Cartesian. It's elemental position is this - vertical `y` axis and horizontal `Oxz` plane. + +Let's see how properties are set in solids. +The `small box` will have elemental values of properties. If you will not set properties, it will have the same `position`, `color`, `rotation`, and `opacity` values. + +***You can see that `box` take four values. Later, we will discuss what they do in more detail. Now, it does not really matter.*** +```kotlin +box(10, 10, 10, name = "small box"){ + x = 0 + y = 0 + z = 0 + opacity = 1 //100% opacity + color("red") //as string + rotation = Point3D(0, 0, 0) +} +``` +![](../docs/images/small-box.png) + +The `big box` will have properties with custom values. +```kotlin +box(40, 40, 40, name = "big box"){ + x = 20 + y = 10 + z = 60 + opacity = 0.5 //50% opacity + color(0u, 179u, 179u) //color in rgb + rotation = Point3D(60, 80, 0) +} +``` +![](../docs/images/big-rotated-box.png) +If we compare these boxes, we will see all differences. + +Here is the function `main` with both boxes. +```kotlin +fun main(){ + val context = Context{ + plugin(Solids) + } + + context.makeVisionFile ( + Paths.get("customFile.html"), + resourceLocation = ResourceLocation.EMBED + ){ + div { + vision { + solid { + box(10, 10, 10, name = "small box"){ + x = 0 + y = 0 + z = 0 + opacity = 1 //100% opacity + color("red") //as string + rotation = Point3D(0, 0, 0) + } + box(40, 40, 40, name = "big box"){ + x = 20 + y = 10 + z = 60 + opacity = 0.5 //50% opacity + color(0u, 179u, 179u) //rgb + rotation = Point3D(60, 80, 0) + } + } + } + } + } +} +``` +![](../docs/images/two-boxes-1.png) +![](../docs/images/two-boxes-2.png) + +***There is plenty of other properties, especially of those, which you can create by yourself. Here we mention just small part.*** + +## Basic Solids +Now, let's see which solids can be visualized: +### 1) PolyLine +### 2) Box + +First thing which has to be mentioned is that `box` takes four values: `box(x, y, z, name)` +* `x` - x-axis length of the `box` +* `y` - y-axis length of the `box` +* `z` - z-axis length of the `box` + +These values have `Float` type. *`x`, `y`, and `z` are necessary values, which cannot be ignored. You have to set them.* + +* `name` - `box`'es identifier with `String` type. *It's an optional value, but without it you won't be able to control solid.* + +Let's create just usual `box` with equal ribs. + +```kotlin + box(50, 50, 50, name = "box") { + color("pink") + } +``` + ![](../docs/images/box.png) + +Now, let's make `box` with bigger `y` value. + ```kotlin + box(10, 25, 10, name = "high box") { + color("black") + } + ``` +As you can see, only rib of `y-axis` differs from other ribs. + + ![](../docs/images/high-box.png) + +For final trial, let's create `box` with bigger `x` value. + + ```kotlin + box(65, 40, 40, name = "wide box") { + x = 0 + y = 0 + z = 0 + color("black") + } + ``` +Predictably, only `x-axis` rib bigger than other ribs. + + ![](../docs/images/wide-box.png) + +### 3) Sphere + +It takes in two values: `radius`, and `name`. +Actually, `name` is general value for all solids, so do not wonder, since all solids need their own identifier. + +As for `radius`, it has `Float` type, and, as you can guess, it sets radius of the sphere, which will be created. + ```kotlin + sphere(50, name = "sphere") { + x = 0 + y = 0 + z = 0 + opacity = 0.9 + color("blue") + } + ``` + ![](../docs/images/sphere.png) + +### 4) Hexagon + +It is solid which has six edges. It is set by eight values: `node1`,..., `node8`. They all have `Point3D` type, so they are just points, vertices. + +*Six edges are these:* +1) Edge with vertices `node1`, `node4`, `node3`, `node2` +2) Edge with vertices `node1`, `node2`, `node6`, `node5` +3) Edge with vertices `node2`, `node3`, `node7`, `node6` +4) Edge with vertices `node4`, `node8`, `node7`, `node3` +5) Edge with vertices `node1`, `node5`, `node8`, `node4` +6) Edge with vertices `node8`, `node5`, `node6`, `node7` + +![](../docs/images/scheme.png) + +As hexagon takes in specific points, we understand that this solid cannot be moved, it fixed in space, and it can't make pivots. + +Let's make classic parallelepiped. +```kotlin + hexagon( + Point3D(25, 30, 25), + Point3D(35, 30, 25), + Point3D(35, 30, 15), + Point3D(25, 30, 15), + Point3D(30, 18, 20), + Point3D(40, 18, 20), + Point3D(40, 18, 10), + Point3D(30, 18, 10), + name = "classic hexagon"){ + color("green") + } +``` + ![](../docs/images/classic-hexagon.png) + +Now, let's make a custom hexagon. + + ```kotlin + hexagon( + Point3D(5, 30, 5), + Point3D(24, 30, 8), + Point3D(20, 30, -10), + Point3D(5, 30, -7), + Point3D(8, 16, 0), + Point3D(12, 16, 0), + Point3D(10, 16, -5), + Point3D(6.5, 12, -3), + name = "custom_hexagon"){ + color("brown") + } + ``` + ![](../docs/images/custom-hexagon.png) +### 3) Cone +It takes in six values: `bottomRadius`, `height`, `upperRadius`, `startAngle`, `angle`, and `name`. + +Obviously, `bottomRadius` is responsible for radius of a bottom base, and `height` sets height of a cone along the `z-axis`. + +As it takes such values as `upperRadius`, `startAngle`, `angle`, `cone` can build not only usual cones, but also cone segments. Initially, `upperRadius` will have `0.0` value, `startAngle` - `0f`, `angle` - `PI2`, so if you don't set them, you'll get just a simple cone. + +Setting `upperRadius`, you make a frustum cone, since it sets a radius of the upper base of a cone. Set `startAngle`, and `angle` let to cut off segments by planes perpendicular to the base. `startAngle` - an angle, starting with which segment will be left, `angle` - an angle of cone, which will be set from `startAngle`. + +Let's build a classic cone: +```kotlin + cone(60, 80, name = "cone") { + color("beige") + } + ``` + ![](../docs/images/cone-1.png) + ![](../docs/images/cone-2.png) + +First of all, we have to try to build a frustum cone: +```kotlin +cone(60, 80, name = "cone") { + color(0u, 40u, 0u) +} +``` +![](../docs/images/frustum-cone.png) + +Now, we need to make a try to build a cone segment: + +```kotlin +cone(60, 80, angle = PI, name = "cone") { + color(0u, 0u, 200u) +} +``` +![](../docs/images/cone-segment-1.png) +![](../docs/images/cone-segment-2.png) + +Finally, the segment of frustum cone is left for a try: +```kotlin +cone(60, 100, 20, PI*3/4, angle = PI/3, name = "cone") { + color(190u, 0u, 0u) +} +``` +![](../docs/images/frustum-cone-segment.png) + +### 4) Cone Surface +This solid is set by seven values:`bottomOuterRadius`, `bottomInnerRadius`, `height`, `topOuterRadius`, `topInnerRadius`, `startAngle`, and `angle`. + +In addition to `height`, `startAngle`, and `angle`, which work as they work in `cone`, there are some new values. +`bottomOuterRadius`, and `bottomInnerRadius` set properties of the bottom circle, `topOuterRadius`, `topInnerRadius` - of the upper circle. They have no initial value, so that means they have to be set. + +Generally, `cone`, and `coneSurface` buildings work in the same way, it's possible to make `coneSurface`'s fragments as in `cone` + +Let's build usual cone surface with almost all properties set: +```kotlin + coneSurface(60, 50, 30, 10, 100, name = "cone surface") { + color("red") + rotation = Point3D(2, 50, -9) + } + ``` +![](../docs/images/cone-surface-1.png) +![](../docs/images/cone-surface-2.png) + +Now, let's create a cone surface and set all it's properties: + +```kotlin +coneSurface(30, 25, 10, 10, 8,0f, pi*3/4, name = "cone surface") { + color("fuchsia") + rotation = Point3D(2, 50, -9) +} +``` +![](../docs/images/cone-surface-fragment.png) +![](../docs/images/cone-surface-fragment-2.png) + +### 5) Cylinder + +This solid is set by `radius`, and `height`. As you can see by accepting values, there's no option of building fragments of cylinders. + +Here's a demonstration of a cylinder: + +```kotlin +cylinder(40, 100, "cylinder"){ + rotation = Point3D(40, 0, 0) + color("indigo") +} +``` +![](../docs/images/cylinder-1.png) +![](../docs/images/cylinder-2.png) +### 6) Tube + +`tube` takes in `radius`, `height`, `innerRadius`, `startAngle`, `angle`, and `name`. *All values are familiar from `cone`, and `coneSurface` solids.* + +Here is an example of classic tube: +```kotlin +tube(50, 40, 20, name = "usual tube"){ + opacity = 0.4 +} +``` +![](../docs/images/tube.png) + +This is an example of tube fragment: + +```kotlin +tube(50, 40, 20, 0f, PI, name = "fragmented tube"){ + color("white") +} +``` +![](../docs/images/tube-fragment.png) +### 7) Extruded + diff --git a/jupyter/visionforge-gdml-jupyter/build.gradle.kts b/jupyter/visionforge-gdml-jupyter/build.gradle.kts index 896b44a4..e2d1db03 100644 --- a/jupyter/visionforge-gdml-jupyter/build.gradle.kts +++ b/jupyter/visionforge-gdml-jupyter/build.gradle.kts @@ -4,9 +4,9 @@ plugins { description = "Jupyter api artifact for GDML rendering" -kotlin { +kotlin{ explicitApi = null - js { + js{ useCommonJs() browser { webpackTask { @@ -25,17 +25,19 @@ kotlin { tasks.getByName("jvmProcessResources") { dependsOn(jsBrowserDistribution) - from(jsBrowserDistribution) + afterEvaluate { + from(jsBrowserDistribution) + } } } - sourceSets { + sourceSets{ commonMain { dependencies { api(project(":visionforge-solid")) } } - jvmMain { + jvmMain{ dependencies { implementation(project(":visionforge-gdml")) } @@ -50,10 +52,10 @@ kotlin { } } -kscience { +kscience{ useJupyter() } -readme { +readme{ maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 6311841e..bc62b31e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -16,6 +16,8 @@ pluginManagement { } } +//enableFeaturePreview("GRADLE_METADATA") + rootProject.name = "visionforge" diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt index 78b75124..c9b68219 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionBase.kt @@ -28,10 +28,11 @@ internal data class PropertyListener( */ @Serializable @SerialName("vision") -public open class VisionBase( - override @Transient var parent: VisionGroup? = null, +public open class VisionBase : Vision { protected var properties: Config? = null -) : Vision { + + @Transient + override var parent: VisionGroup? = null @Synchronized protected fun getOrCreateProperties(): Config { @@ -74,12 +75,9 @@ public open class VisionBase( } override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { - val oldItem = properties?.getItem(name) - if(oldItem!= item) { - getOrCreateProperties().setItem(name, item) - if (notify) { - invalidateProperty(name) - } + getOrCreateProperties().setItem(name, item) + if (notify) { + invalidateProperty(name) } } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt index 06ccb7bc..85b147ab 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionChange.kt @@ -65,14 +65,14 @@ private fun Vision.isolate(manager: VisionManager): Vision { } /** - * @param delete flag showing that this vision child should be removed + * @param void flag showing that this vision child should be removed * @param vision a new value for vision content * @param properties updated properties - * @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [delete] flag to true. + * @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [void] flag to true. */ @Serializable public data class VisionChange( - public val delete: Boolean = false, + public val void: Boolean = false, public val vision: Vision? = null, @Serializable(MetaSerializer::class) public val properties: Meta? = null, public val children: Map? = null, diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt index 6b35d971..4f16982b 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroup.kt @@ -58,7 +58,6 @@ public operator fun VisionGroup.iterator(): Iterator = children.values.i public val VisionGroup.isEmpty: Boolean get() = this.children.isEmpty() public interface VisionContainerBuilder { - //TODO add documentation public operator fun set(name: Name?, child: V?) } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt index 933b55cd..44800673 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionGroupBase.kt @@ -17,7 +17,7 @@ import space.kscience.dataforge.names.* @Serializable @SerialName("vision.group") public open class VisionGroupBase( - @SerialName("children") protected val childrenInternal: MutableMap = LinkedHashMap(), + @SerialName("children") internal val childrenInternal: MutableMap = LinkedHashMap(), ) : VisionBase(), MutableVisionGroup { /** @@ -134,7 +134,7 @@ public open class VisionGroupBase( override fun update(change: VisionChange) { change.children?.forEach { (name, change) -> when { - change.delete -> set(name, null) + change.void -> set(name, null) change.vision != null -> set(name, change.vision) else -> get(name)?.update(change) } diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt index 4f01e839..b3e9faf4 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionManager.kt @@ -39,8 +39,6 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) { public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string) public fun encodeToString(vision: Vision): String = jsonFormat.encodeToString(visionSerializer, vision) - public fun encodeToString(change: VisionChange): String = - jsonFormat.encodeToString(VisionChange.serializer(), change) public fun decodeFromJson(json: JsonElement): Vision = jsonFormat.decodeFromJsonElement(visionSerializer, json) diff --git a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt index 112295c9..68567ee3 100644 --- a/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt +++ b/visionforge-core/src/commonMain/kotlin/space/kscience/visionforge/VisionPropertyContainer.kt @@ -1,15 +1,12 @@ package space.kscience.visionforge -import space.kscience.dataforge.meta.Config import space.kscience.dataforge.meta.MetaItem -import space.kscience.dataforge.meta.get -import space.kscience.dataforge.meta.set import space.kscience.dataforge.names.Name /** * Property containers are used to create a symmetric behaviors for vision properties and style builders */ -public interface VisionPropertyContainer { +public interface VisionPropertyContainer { public fun getProperty( name: Name, inherit: Boolean = false, @@ -18,18 +15,4 @@ public interface VisionPropertyContainer { ): MetaItem? public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true) -} - -public open class SimpleVisionPropertyContainer(protected val config: Config): VisionPropertyContainer{ - override fun getProperty( - name: Name, - inherit: Boolean, - includeStyles: Boolean, - includeDefaults: Boolean - ): MetaItem? = config[name] - - override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { - config[name] = item - } - } \ No newline at end of file diff --git a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt index 017c615d..98803115 100644 --- a/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt +++ b/visionforge-core/src/jsMain/kotlin/space/kscience/visionforge/VisionClient.kt @@ -2,9 +2,6 @@ package space.kscience.visionforge import kotlinx.browser.document import kotlinx.browser.window -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import org.w3c.dom.* import org.w3c.dom.url.URL import space.kscience.dataforge.context.* @@ -16,14 +13,14 @@ import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_CONNEC import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_ENDPOINT_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_FETCH_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE +import kotlin.collections.set import kotlin.reflect.KClass -import kotlin.time.Duration public class VisionClient : AbstractPlugin() { override val tag: PluginTag get() = Companion.tag private val visionManager: VisionManager by require(VisionManager) - //private val visionMap = HashMap() + private val visionMap = HashMap() /** * Up-going tree traversal in search for endpoint attribute @@ -56,73 +53,11 @@ public class VisionClient : AbstractPlugin() { private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null - private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) { + private fun renderVision(element: Element, vision: Vision?, outputMeta: Meta) { if (vision != null) { + visionMap[element] = vision val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision") renderer.render(element, vision, outputMeta) - - element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr -> - val wsUrl = if (attr.value.isBlank() || attr.value == "auto") { - val endpoint = resolveEndpoint(element) - logger.info { "Vision server is resolved to $endpoint" } - URL(endpoint).apply { - pathname += "/ws" - } - } else { - URL(attr.value) - }.apply { - protocol = "ws" - searchParams.append("name", name) - } - - logger.info { "Updating vision data from $wsUrl" } - - //Individual websocket for this element - WebSocket(wsUrl.toString()).apply { - onmessage = { messageEvent -> - val stringData: String? = messageEvent.data as? String - if (stringData != null) { - val change: VisionChange = visionManager.jsonFormat.decodeFromString( - VisionChange.serializer(), - stringData - ) - - if (change.vision != null) { - renderer.render(element, vision, outputMeta) - } - - logger.debug { "Got update $change for output with name $name" } - vision.update(change) - } else { - console.error("WebSocket message data is not a string") - } - } - - - //Backward change propagation - var feedbackJob: Job? = null - - onopen = { - feedbackJob = vision.flowChanges( - visionManager, - Duration.Companion.milliseconds(300) - ).onEach { change -> - send(visionManager.encodeToString(change)) - }.launchIn(visionManager.context) - - console.info("WebSocket update channel established for output '$name'") - } - - onclose = { - feedbackJob?.cancel() - console.info("WebSocket update channel closed for output '$name'") - } - onerror = { - feedbackJob?.cancel() - console.error("WebSocket update channel error for output '$name'") - } - } - } } } @@ -144,39 +79,85 @@ public class VisionClient : AbstractPlugin() { visionManager.decodeFromString(it) } - when { - embeddedVision != null -> { - logger.info { "Found embedded vision for output with name $name" } - renderVision(name, element, embeddedVision, outputMeta) - } - element.attributes[OUTPUT_FETCH_ATTRIBUTE] != null -> { - val attr = element.attributes[OUTPUT_FETCH_ATTRIBUTE]!! + if (embeddedVision != null) { + logger.info { "Found embedded vision for output with name $name" } + renderVision(element, embeddedVision, outputMeta) + } - val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") { - val endpoint = resolveEndpoint(element) - logger.info { "Vision server is resolved to $endpoint" } - URL(endpoint).apply { - pathname += "/vision" + element.attributes[OUTPUT_FETCH_ATTRIBUTE]?.let { attr -> + + val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") { + val endpoint = resolveEndpoint(element) + logger.info { "Vision server is resolved to $endpoint" } + URL(endpoint).apply { + pathname += "/vision" + } + } else { + URL(attr.value) + }.apply { + searchParams.append("name", name) + } + + logger.info { "Fetching vision data from $fetchUrl" } + window.fetch(fetchUrl).then { response -> + if (response.ok) { + response.text().then { text -> + val vision = visionManager.decodeFromString(text) + renderVision(element, vision, outputMeta) } } else { - URL(attr.value) - }.apply { - searchParams.append("name", name) + logger.error { "Failed to fetch initial vision state from $fetchUrl" } } - logger.info { "Fetching vision data from $fetchUrl" } - window.fetch(fetchUrl).then { response -> - if (response.ok) { - response.text().then { text -> - val vision = visionManager.decodeFromString(text) - renderVision(name, element, vision, outputMeta) + } + } + + element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr -> + val wsUrl = if (attr.value.isBlank() || attr.value == "auto") { + val endpoint = resolveEndpoint(element) + logger.info { "Vision server is resolved to $endpoint" } + URL(endpoint).apply { + pathname += "/ws" + } + } else { + URL(attr.value) + }.apply { + protocol = "ws" + searchParams.append("name", name) + } + + logger.info { "Updating vision data from $wsUrl" } + + WebSocket(wsUrl.toString()).apply { + onmessage = { messageEvent -> + val stringData: String? = messageEvent.data as? String + if (stringData != null) { + val change = visionManager.jsonFormat.decodeFromString( + VisionChange.serializer(), + stringData + ) + + if (change.vision != null) { + renderVision(element, change.vision, outputMeta) } + + logger.debug { "Got update $change for output with name $name" } + visionMap[element]?.update(change) + ?: console.info("Target vision for element $element with name $name not found") } else { - logger.error { "Failed to fetch initial vision state from $fetchUrl" } + console.error("WebSocket message data is not a string") } } + onopen = { + console.info("WebSocket update channel established for output '$name'") + } + onclose = { + console.info("WebSocket update channel closed for output '$name'") + } + onerror = { + console.error("WebSocket update channel error for output '$name'") + } } - else -> error("No embedded vision data / fetch url for $name") } } diff --git a/visionforge-fx/build.gradle.kts b/visionforge-fx/build.gradle.kts index 09adb66a..391517d7 100644 --- a/visionforge-fx/build.gradle.kts +++ b/visionforge-fx/build.gradle.kts @@ -22,7 +22,7 @@ dependencies { exclude(group = "org.openjfx") } - api("org.fxyz3d:fxyz3d:0.5.4") { + api("org.fxyz3d:fxyz3d:0.5.2") { exclude(module = "slf4j-simple") } api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}") diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt index f843e02c..6bd56fcf 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FX3DPlugin.kt @@ -65,8 +65,6 @@ class FX3DPlugin : AbstractPlugin() { } is SolidLabel -> Text(obj.text).apply { font = Font.font(obj.fontFamily, obj.fontSize) - transforms.add(Rotate(180.0, Rotate.Y_AXIS)) - transforms.add(Rotate(180.0, Rotate.Z_AXIS)) x = -layoutBounds.width / 2 y = layoutBounds.height / 2 } @@ -131,10 +129,10 @@ class FX3DPlugin : AbstractPlugin() { } } - public companion object : PluginFactory { - override val tag: PluginTag = PluginTag("vision.fx3D", PluginTag.DATAFORGE_GROUP) - override val type: KClass = FX3DPlugin::class - override fun invoke(meta: Meta, context: Context): FX3DPlugin = FX3DPlugin() + companion object : PluginFactory { + override val tag = PluginTag("vision.fx3D", PluginTag.DATAFORGE_GROUP) + override val type = FX3DPlugin::class + override fun invoke(meta: Meta, context: Context) = FX3DPlugin() } } @@ -142,14 +140,14 @@ class FX3DPlugin : AbstractPlugin() { * Builder and updater for three.js object */ @Type(TYPE) -public interface FX3DFactory { +interface FX3DFactory { - public val type: KClass + val type: KClass - public operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node + operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node - public companion object { - public const val TYPE = "fx3DFactory" + companion object { + const val TYPE = "fx3DFactory" } } diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCanvas3D.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCanvas3D.kt index a05c844a..171bc5a3 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCanvas3D.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCanvas3D.kt @@ -4,7 +4,6 @@ import javafx.application.Platform import javafx.beans.property.ObjectProperty import javafx.beans.property.SimpleObjectProperty import javafx.scene.* -import javafx.scene.layout.BorderPane import javafx.scene.paint.Color import org.fxyz3d.scene.Axes import space.kscience.dataforge.context.Context @@ -12,32 +11,31 @@ import space.kscience.dataforge.context.ContextAware import space.kscience.visionforge.solid.specifications.Canvas3DOptions import tornadofx.* -public class FXCanvas3D( - public val fx3d: FX3DPlugin, - public val options: Canvas3DOptions = Canvas3DOptions.empty(), +class FXCanvas3D( + val plugin: FX3DPlugin, + val spec: Canvas3DOptions = Canvas3DOptions.empty(), ) : Fragment(), ContextAware { - override val context: Context get() = fx3d.context + override val context: Context get() = plugin.context - public val world: Group = Group().apply { + val world = Group().apply { //transforms.add(Rotate(180.0, Rotate.Z_AXIS)) } - public val axes: Axes = Axes().also { - it.setHeight(options.axes.size) - it.setRadius(options.axes.width) - it.isVisible = options.axes.visible + val axes = Axes().also { + it.setHeight(spec.axes.size) + it.setRadius(spec.axes.width) + it.isVisible = spec.axes.visible world.add(it) } - public val light: AmbientLight = AmbientLight() + val light = AmbientLight() private val camera = PerspectiveCamera().apply { - nearClip = options.camera.nearClip - farClip = options.camera.farClip - fieldOfView = options.camera.fov.toDouble() - - add(light) + nearClip = spec.camera.nearClip + farClip = spec.camera.farClip + fieldOfView = spec.camera.fov.toDouble() + this.add(light) } private val canvas = SubScene( @@ -51,19 +49,19 @@ public class FXCanvas3D( scene.camera = camera } - override val root: BorderPane = borderpane { + override val root = borderpane { center = canvas } - public val controls: OrbitControls = camera.orbitControls(canvas, options.camera).also { + val controls = camera.orbitControls(canvas, spec.camera).also { world.add(it.centerMarker) } - public val rootObjectProperty: ObjectProperty = SimpleObjectProperty() - public var rootObject: Solid? by rootObjectProperty + val rootObjectProperty: ObjectProperty = SimpleObjectProperty() + var rootObject: Solid? by rootObjectProperty private val rootNodeProperty = rootObjectProperty.objectBinding { - it?.let { fx3d.buildNode(it) } + it?.let { plugin.buildNode(it) } } init { @@ -81,7 +79,7 @@ public class FXCanvas3D( } } - public fun render(vision: Solid) { + fun render(vision: Solid) { rootObject = vision } } \ No newline at end of file diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt index 1cdcf914..5aa7619e 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXCompositeFactory.kt @@ -38,7 +38,7 @@ private fun MeshView.toCSG(): CSG { return CSG.fromPolygons(polygons) } -public class FXCompositeFactory(public val plugin: FX3DPlugin) : FX3DFactory { +class FXCompositeFactory(val plugin: FX3DPlugin) : FX3DFactory { override val type: KClass get() = Composite::class @@ -48,7 +48,7 @@ public class FXCompositeFactory(public val plugin: FX3DPlugin) : FX3DFactory firstCSG.union(secondCSG) + CompositeType.UNION -> firstCSG.union(secondCSG) CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) CompositeType.SUBTRACT -> firstCSG.difference(secondCSG) } diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXConvexFactory.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXConvexFactory.kt index 7bc44207..f2bbba54 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXConvexFactory.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXConvexFactory.kt @@ -7,14 +7,11 @@ import javafx.scene.Node import kotlin.reflect.KClass -public object FXConvexFactory : FX3DFactory { +object FXConvexFactory : FX3DFactory { override val type: KClass get() = Convex::class override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node { - val hull = HullUtil.hull( - obj.points.map { Vector3d.xyz(it.x.toDouble(), it.y.toDouble(), it.z.toDouble()) }, - PropertyStorage() - ) + val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage()) return hull.toNode() } diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXShapeFactory.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXShapeFactory.kt index 116075ce..ad88aac0 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXShapeFactory.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/FXShapeFactory.kt @@ -8,7 +8,7 @@ import org.fxyz3d.geometry.Face3 import space.kscience.dataforge.meta.Meta import kotlin.reflect.KClass -public object FXShapeFactory : FX3DFactory { +object FXShapeFactory : FX3DFactory { override val type: KClass get() = GeometrySolid::class override fun invoke(obj: GeometrySolid, binding: VisualObjectFXBinding): MeshView { diff --git a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/OrbitControls.kt b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/OrbitControls.kt index 2fe573e0..47c3c8e8 100644 --- a/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/OrbitControls.kt +++ b/visionforge-fx/src/main/kotlin/space/kscience/visionforge/solid/OrbitControls.kt @@ -1,110 +1,127 @@ package space.kscience.visionforge.solid -import javafx.beans.property.SimpleBooleanProperty +import javafx.beans.InvalidationListener import javafx.beans.property.SimpleDoubleProperty import javafx.event.EventHandler +import javafx.geometry.Point3D import javafx.scene.Camera import javafx.scene.Node import javafx.scene.SubScene +import javafx.scene.input.MouseEvent +import javafx.scene.input.ScrollEvent import javafx.scene.shape.Sphere import javafx.scene.transform.Rotate -import javafx.scene.transform.Rotate.X_AXIS -import javafx.scene.transform.Rotate.Y_AXIS import javafx.scene.transform.Translate -import space.kscience.dataforge.meta.useProperty import tornadofx.* -import kotlin.math.PI -import kotlin.math.cos -import kotlin.math.max -import kotlin.math.sin +import kotlin.math.* import space.kscience.visionforge.solid.specifications.Camera as CameraSpec -public class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec: CameraSpec) { - /** - * Azimuth angle in radians - */ - public val azimuthProperty: SimpleDoubleProperty = SimpleDoubleProperty().apply { - spec.useProperty(CameraSpec::azimuth){ - set(spec.azimuth) - } - } - public var azimuth: Double by azimuthProperty +class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec: CameraSpec) { - /** - * Zenith angle in radians - */ - public val zenithProperty: SimpleDoubleProperty = SimpleDoubleProperty().apply { - spec.useProperty(CameraSpec::latitude){ - set(PI / 2 - spec.latitude) - } - } + val distanceProperty = SimpleDoubleProperty(spec.distance) + var distance by distanceProperty - public var zenith: Double by zenithProperty + val azimuthProperty = SimpleDoubleProperty(spec.azimuth) + var azimuth by azimuthProperty + val zenithProperty = SimpleDoubleProperty(PI / 2 - spec.latitude) + var zenith by zenithProperty - private val baseTranslate = Translate(0.0, 0.0, 0.0) + val latitudeProperty = zenithProperty.unaryMinus().plus(PI / 2) + val latitude by latitudeProperty - public var x: Double by baseTranslate.xProperty() - public var y: Double by baseTranslate.yProperty() - public var z: Double by baseTranslate.zProperty() + val baseXProperty = SimpleDoubleProperty(0.0) + var x by baseXProperty + val baseYProperty = SimpleDoubleProperty(0.0) + var y by baseYProperty + val baseZProperty = SimpleDoubleProperty(0.0) + var z by baseZProperty - private val distanceProperty = SimpleDoubleProperty().apply { - spec.useProperty(CameraSpec::distance) { - set(it) - } - } + private val baseTranslate = Translate() - private val distanceTranslation = Translate().apply { - zProperty().bind(-distanceProperty) - } +// val basePositionProperty: ObjectBinding = +// nonNullObjectBinding(baseXProperty, baseYProperty, baseZProperty) { +// Point3D(x, y, z) +// } +// +// val basePosition by basePositionProperty - public var distance: Double by distanceProperty - - private val centering = Translate().apply { - xProperty().bind(-canvas.widthProperty() / 2) - yProperty().bind(-canvas.heightProperty() / 2) - } - - private val yUpRotation = Rotate(180.0, X_AXIS) - - private val azimuthRotation = Rotate().apply { - axis = Y_AXIS - angleProperty().bind(azimuthProperty * (180.0 / PI)) - } - - private val zenithRotation = Rotate().apply { - axisProperty().bind(objectBinding(azimuthProperty) { - azimuthRotation.inverseTransform(X_AXIS) - }) - angleProperty().bind(-zenithProperty * (180.0 / PI)) - } - - private val inProgressProperty = SimpleBooleanProperty(false) - - - public val centerMarker: Node by lazy { + val centerMarker by lazy { Sphere(10.0).also { it.transforms.setAll(baseTranslate) - it.visibleProperty().bind(inProgressProperty) } } + //private val center = Translate() + + private val rx = Rotate(0.0, Rotate.X_AXIS) + + private val ry = Rotate(0.0, Rotate.Y_AXIS) + + private val translate = Translate() + + //private val rz = Rotate(180.0, Rotate.Z_AXIS) + + init { - camera.transforms.setAll( - baseTranslate, - yUpRotation, - azimuthRotation, - zenithRotation, - distanceTranslation, - centering, - ) + camera.transforms.setAll(ry, rx, translate) + update() + val listener = InvalidationListener { + update() + } + distanceProperty.addListener(listener) + azimuthProperty.addListener(listener) + zenithProperty.addListener(listener) + baseXProperty.addListener(listener) + baseYProperty.addListener(listener) + baseZProperty.addListener(listener) canvas.apply { +// center.xProperty().bind(widthProperty().divide(2)) +// center.zProperty().bind(heightProperty().divide(2)) handleMouse() } +// coordinateContainer?.vbox { +// label(distanceProperty.asString()) +// label(azimuthProperty.asString()) +// label(zenithProperty.asString()) +// } } + private fun update() { + val spherePosition = Point3D( + sin(zenith) * sin(azimuth), + cos(zenith), + sin(zenith) * cos(azimuth) + ).times(distance) + val basePosition = Point3D(x, y, z) + baseTranslate.x = x + baseTranslate.y = y + baseTranslate.z = z + //Create direction vector + val cameraPosition = basePosition + spherePosition + val camDirection: Point3D = (-spherePosition).normalize() + + val xRotation = Math.toDegrees(asin(-camDirection.y)) + val yRotation = Math.toDegrees(atan2(camDirection.x, camDirection.z)) + + rx.pivotX = cameraPosition.x + rx.pivotY = cameraPosition.y + rx.pivotZ = cameraPosition.z + rx.angle = xRotation + + ry.pivotX = cameraPosition.x + ry.pivotY = cameraPosition.y + ry.pivotZ = cameraPosition.z + ry.angle = yRotation + + translate.x = cameraPosition.x + translate.y = cameraPosition.y + translate.z = cameraPosition.z + } + + private fun Node.handleMouse() { var mousePosX = 0.0 @@ -114,21 +131,20 @@ public class OrbitControls internal constructor(camera: Camera, canvas: SubScene var mouseDeltaX: Double var mouseDeltaY: Double - onMousePressed = EventHandler { me -> + onMousePressed = EventHandler { me -> mousePosX = me.sceneX mousePosY = me.sceneY mouseOldX = me.sceneX mouseOldY = me.sceneY - inProgressProperty.set(true) } - onMouseDragged = EventHandler { me -> + onMouseDragged = EventHandler { me -> mouseOldX = mousePosX mouseOldY = mousePosY mousePosX = me.sceneX mousePosY = me.sceneY - mouseDeltaX = mouseOldX - mousePosX - mouseDeltaY = mouseOldY - mousePosY + mouseDeltaX = mousePosX - mouseOldX + mouseDeltaY = mousePosY - mouseOldY val modifier = when { me.isControlDown -> CONTROL_MULTIPLIER @@ -137,24 +153,19 @@ public class OrbitControls internal constructor(camera: Camera, canvas: SubScene } if (me.isPrimaryButtonDown) { - azimuth = (azimuth - mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED) - zenith = (zenith - mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED).coerceIn(-PI/2, PI/2) + azimuth = (azimuth - mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED).coerceIn(0.0, 2 * PI) + zenith = (zenith - mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED).coerceIn(0.0,PI) } else if (me.isSecondaryButtonDown) { - x += MOUSE_SPEED * modifier * TRACK_SPEED * (mouseDeltaX * cos(azimuth) - mouseDeltaY * sin(azimuth)) - z += MOUSE_SPEED * modifier * TRACK_SPEED * ( mouseDeltaX * sin(azimuth) + mouseDeltaY * cos(azimuth)) + x += MOUSE_SPEED * modifier * TRACK_SPEED * (mouseDeltaX * cos(azimuth) + mouseDeltaY * sin(azimuth)) + z += MOUSE_SPEED * modifier * TRACK_SPEED * (-mouseDeltaX * sin(azimuth) + mouseDeltaY * cos(azimuth)) } } - - onMouseReleased = EventHandler { - inProgressProperty.set(false) - } - - onScroll = EventHandler { event -> + onScroll = EventHandler { event -> distance = max(1.0, distance - MOUSE_SPEED * event.deltaY * RESIZE_SPEED) } } - public companion object { + companion object { private const val CONTROL_MULTIPLIER = 0.1 private const val SHIFT_MULTIPLIER = 10.0 private const val MOUSE_SPEED = 0.1 @@ -164,5 +175,5 @@ public class OrbitControls internal constructor(camera: Camera, canvas: SubScene } } -public fun Camera.orbitControls(canvas: SubScene, spec: CameraSpec): OrbitControls = - OrbitControls(this, canvas, spec) +fun Camera.orbitControls(canvas: SubScene, spec: CameraSpec) = + OrbitControls(this, canvas, spec) \ No newline at end of file diff --git a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlOptimizer.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlOptimizer.kt new file mode 100644 index 00000000..a35e845c --- /dev/null +++ b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlOptimizer.kt @@ -0,0 +1,83 @@ +package space.kscience.visionforge.gdml + +import space.kscience.dataforge.meta.itemSequence +import space.kscience.dataforge.misc.DFExperimental +import space.kscience.visionforge.Vision +import space.kscience.visionforge.meta +import space.kscience.visionforge.solid.* + +public expect class Counter() { + public fun get(): Int + public fun incrementAndGet(): Int +} + +private fun Point3D?.safePlus(other: Point3D?): Point3D? = if (this == null && other == null) { + null +} else { + (this ?: Point3D(0, 0, 0)) + (other ?: Point3D(0, 0, 0)) +} + +@DFExperimental +internal fun Vision.updateFrom(other: Vision): Vision { + if (this is Solid && other is Solid) { + position = position.safePlus(other.position) + rotation = rotation.safePlus(other.rotation) + if (this.scale != null || other.scale != null) { + scaleX = scaleX.toDouble() * other.scaleX.toDouble() + scaleY = scaleY.toDouble() * other.scaleY.toDouble() + scaleZ = scaleZ.toDouble() * other.scaleZ.toDouble() + } + other.meta.itemSequence().forEach { (name, item) -> + if (getProperty(name) == null) { + setProperty(name, item) + } + } + } + return this +} +// +//@DFExperimental +//private class GdmlOptimizer() : VisionVisitor { +// val logger = KotlinLogging.logger("SingleChildReducer") +// +// private val depthCount = HashMap() +// +// override suspend fun visit(name: Name, vision: Vision) { +// val depth = name.length +// depthCount.getOrPut(depth) { Counter() }.incrementAndGet() +// } +// +// override fun skip(name: Name, vision: Vision): Boolean = vision is Proxy.ProxyChild +// +// override suspend fun visitChildren(name: Name, group: VisionGroup) { +// if (name == "volumes".toName()) return +// if (group !is MutableVisionGroup) return +// +// val newChildren = group.children.entries.associate { (visionToken, vision) -> +// //Reduce single child groups +// if (vision is VisionGroup && vision !is Proxy && vision.children.size == 1) { +// val (token, child) = vision.children.entries.first() +// child.parent = null +// if (token != visionToken) { +// child.config["solidName"] = token.toString() +// } +// visionToken to child.updateFrom(vision) +// } else { +// visionToken to vision +// } +// } +// if (newChildren != group.children) { +// group.removeAll() +// newChildren.forEach { (token, child) -> +// group[token] = child +// } +// } +// } +//} +// +//@DFExperimental +//suspend fun SolidGroup.optimizeGdml(): Job = coroutineScope { +// prototypes?.let { +// VisionVisitor.visitTree(GdmlOptimizer(), this, it) +// } ?: CompletableDeferred(Unit) +//} \ No newline at end of file diff --git a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformer.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformerEnv.kt similarity index 90% rename from visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformer.kt rename to visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformerEnv.kt index 98b4509e..ef41a214 100644 --- a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformer.kt +++ b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/GdmlTransformerEnv.kt @@ -41,7 +41,7 @@ public class GdmlTransformer { internal val styleCache = HashMap() - public fun Solid.registerAndUseStyle(name: String, builder: MetaBuilder.() -> Unit) { + public fun Solid.useStyle(name: String, builder: MetaBuilder.() -> Unit) { styleCache.getOrPut(name.toName()) { Meta(builder) } @@ -49,7 +49,7 @@ public class GdmlTransformer { } public fun Solid.transparent() { - registerAndUseStyle("transparent") { + useStyle("transparent") { SolidMaterial.MATERIAL_OPACITY_KEY put 0.3 "edges.enabled" put true } @@ -75,7 +75,7 @@ public class GdmlTransformer { if (parent.physVolumes.isNotEmpty()) transparent() - registerAndUseStyle(styleName) { + useStyle(styleName) { val vfMaterial = SolidMaterial().apply { configurePaint(material, solid) } @@ -125,11 +125,7 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { fun Solid.configureSolid(root: Gdml, parent: GdmlVolume, solid: GdmlSolid) { val material = parent.materialref.resolve(root) ?: GdmlElement(parent.materialref.ref) - with(settings) { - with(this@configureSolid) { - configureSolid(parent, solid, material) - } - } + settings.run { configureSolid(parent, solid, material) } } private fun proxySolid(root: Gdml, group: SolidGroup, solid: GdmlSolid, name: String): SolidReferenceGroup { @@ -163,26 +159,25 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { newScale: GdmlScale? = null, ): T = apply { newPos?.let { - val gdmlX = it.x(settings.lUnit) - if (gdmlX != 0f) x = gdmlX - val gdmlY = it.y(settings.lUnit) - if (gdmlY != 0f) y = gdmlY - val gdmlZ = it.z(settings.lUnit) - if (gdmlZ != 0f) z = gdmlZ + val point = Point3D(it.x(settings.lUnit), it.y(settings.lUnit), it.z(settings.lUnit)) + if (point != Point3D.ZERO) { + position = point + } } newRotation?.let { - val gdmlX = it.x(settings.aUnit) - if (gdmlX != 0f) rotationX = gdmlX - val gdmlY = it.y(settings.aUnit) - if (gdmlY != 0f) rotationY = gdmlY - val gdmlZ = it.z(settings.aUnit) - if (gdmlZ != 0f) rotationZ = gdmlZ + val point = Point3D(it.x(settings.aUnit), it.y(settings.aUnit), it.z(settings.aUnit)) + if (point != Point3D.ZERO) { + rotation = point + } + //this@withPosition.rotationOrder = RotationOrder.ZXY } newScale?.let { - if (it.x != 1f) scaleX = it.x - if (it.y != 1f) scaleY = it.y - if (it.z != 1f) scaleZ = it.z + val point = Point3D(it.x, it.y, it.z) + if (point != Point3D.ONE) { + scale = point + } } + //TODO convert units if needed } fun T.withPosition(root: Gdml, physVolume: GdmlPhysVolume): T = withPosition( @@ -232,10 +227,11 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { bottomRadius = solid.rmax1 * lScale, height = solid.z * lScale, upperRadius = solid.rmax2 * lScale, - startAngle = solid.startphi * aScale, - angle = solid.deltaphi * aScale, name = name - ) + ) { + startAngle = solid.startphi * aScale + angle = solid.deltaphi * aScale + } } else { coneSurface( bottomOuterRadius = solid.rmax1 * lScale, @@ -243,10 +239,11 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { height = solid.z * lScale, topOuterRadius = solid.rmax2 * lScale, topInnerRadius = solid.rmin2 * lScale, - startAngle = solid.startphi * aScale, - angle = solid.deltaphi * aScale, name = name - ) + ) { + startAngle = solid.startphi * aScale + angle = solid.deltaphi * aScale + } } is GdmlXtru -> extrude(name) { shape { @@ -274,15 +271,12 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { scaleZ = solid.scale.z.toFloat() } } - is GdmlSphere -> sphereLayer( - outerRadius = solid.rmax * lScale, - innerRadius = solid.rmin * lScale, - phi = solid.deltaphi * aScale, - theta = solid.deltatheta * aScale, - phiStart = solid.startphi * aScale, - thetaStart = solid.starttheta * aScale, - name = name, - ) + is GdmlSphere -> sphereLayer(solid.rmax * lScale, solid.rmin * lScale, name) { + phi = solid.deltaphi * aScale + theta = solid.deltatheta * aScale + phiStart = solid.startphi * aScale + thetaStart = solid.starttheta * aScale + } is GdmlOrb -> sphere(solid.r * lScale, name = name) is GdmlPolyhedra -> extrude(name) { //getting the radius of first @@ -303,7 +297,7 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) { val first: GdmlSolid = solid.first.resolve(root) ?: error("") val second: GdmlSolid = solid.second.resolve(root) ?: error("") val type: CompositeType = when (solid) { - is GdmlUnion -> CompositeType.SUM // dumb sum for better performance + is GdmlUnion -> CompositeType.UNION is GdmlSubtraction -> CompositeType.SUBTRACT is GdmlIntersection -> CompositeType.INTERSECT } diff --git a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/markLayers.kt b/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/markLayers.kt deleted file mode 100644 index befc69bb..00000000 --- a/visionforge-gdml/src/commonMain/kotlin/space/kscience/visionforge/gdml/markLayers.kt +++ /dev/null @@ -1,89 +0,0 @@ -package space.kscience.visionforge.gdml - -import space.kscience.dataforge.context.info -import space.kscience.dataforge.context.logger -import space.kscience.dataforge.names.Name -import space.kscience.dataforge.names.NameToken -import space.kscience.dataforge.names.length -import space.kscience.dataforge.names.plus -import space.kscience.visionforge.VisionGroup -import space.kscience.visionforge.solid.Solid -import space.kscience.visionforge.solid.SolidGroup -import space.kscience.visionforge.solid.SolidReferenceGroup -import space.kscience.visionforge.solid.layer - - -private class VisionCounterTree( - val name: Name, - val vision: Solid, - val prototypes: HashMap -) { - - // self count for prototypes - var selfCount = 1 - - val children: Map by lazy { - (vision as? VisionGroup)?.children?.mapValues { (key, vision) -> - if (vision is SolidReferenceGroup) { - prototypes.getOrPut(vision.refName) { - VisionCounterTree(vision.refName, vision.prototype, prototypes) - }.apply { - selfCount += 1 - } - } else { - VisionCounterTree(name + key, vision as Solid, prototypes) - } - } ?: emptyMap() - } - - val childrenCount: Int by lazy { - children.values.sumOf { it.childrenCount + 1 } - } - -} - - -private fun VisionCounterTree.topToBottom(): Sequence = sequence { - yield(this@topToBottom) - children.values.forEach { - yieldAll(it.topToBottom()) - } -} - -public fun SolidGroup.markLayers(thresholds: List = listOf(500, 1000, 20000, 50000)) { - val logger = manager?.context?.logger - val counterTree = VisionCounterTree(Name.EMPTY, this, hashMapOf()) - val totalCount = counterTree.childrenCount - if (totalCount > thresholds.firstOrNull() ?: 0) { - val allNodes = counterTree.topToBottom().distinct().toMutableList() - //println("tree construction finished") - allNodes.sortWith( - compareBy( - { it.name.length }, - { (it.children.size + 1) * it.selfCount } - ).reversed() - ) - - //mark layers - var remaining = totalCount - - for (node in allNodes) { - val layerIndex = if (remaining > thresholds.last()) - thresholds.size - else - thresholds.indexOfLast { remaining < it } - - if (layerIndex == 0) break - - node.vision.layer = layerIndex - remaining -= node.selfCount * (node.children.size + 1) - logger?.apply { - if (node.selfCount > 1) { - info { "Prototype with name ${node.name} moved to layer $layerIndex. $remaining nodes remains" } - } else { - info { "Vision with name ${node.name} moved to layer $layerIndex. $remaining nodes remains" } - } - } - } - } -} \ No newline at end of file diff --git a/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt b/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt index 9fb80095..a364996c 100644 --- a/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt +++ b/visionforge-gdml/src/commonTest/kotlin/TestCubes.kt @@ -26,7 +26,7 @@ class TestCubes { val smallBoxPrototype = vision.getPrototype("solids.smallBox".toName()) as? Box assertNotNull(smallBoxPrototype) assertEquals(30.0, smallBoxPrototype.xSize.toDouble()) - val smallBoxVision = vision["composite-111.smallBox"]?.unref as? Box + val smallBoxVision = vision["composite-111.smallBox"]?.prototype as? Box assertNotNull(smallBoxVision) assertEquals(30.0, smallBoxVision.xSize.toDouble()) } diff --git a/visionforge-gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/gdmlJs.kt b/visionforge-gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/gdmlJs.kt new file mode 100644 index 00000000..f90eec29 --- /dev/null +++ b/visionforge-gdml/src/jsMain/kotlin/space/kscience/visionforge/gdml/gdmlJs.kt @@ -0,0 +1,8 @@ +package space.kscience.visionforge.gdml + +public actual class Counter { + private var count: Int = 0 + public actual fun get(): Int = count + + public actual fun incrementAndGet(): Int = count++ +} \ No newline at end of file diff --git a/visionforge-gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/gdmlJVM.kt b/visionforge-gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/gdmlJVM.kt index b67e231e..7bd17a5c 100644 --- a/visionforge-gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/gdmlJVM.kt +++ b/visionforge-gdml/src/jvmMain/kotlin/space/kscience/visionforge/gdml/gdmlJVM.kt @@ -4,6 +4,9 @@ import space.kscience.gdml.Gdml import space.kscience.gdml.decodeFromFile import space.kscience.visionforge.solid.SolidGroup import java.nio.file.Path +import java.util.concurrent.atomic.AtomicInteger + +public actual typealias Counter = AtomicInteger public fun SolidGroup.gdml( file: Path, diff --git a/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/bmanStatistics.kt b/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/bmanStatistics.kt index 1f1da074..eccac343 100644 --- a/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/bmanStatistics.kt +++ b/visionforge-gdml/src/jvmTest/kotlin/space/kscience/visionforge/gdml/bmanStatistics.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.withContext import nl.adaptivity.xmlutil.StAXReader import space.kscience.gdml.Gdml import space.kscience.gdml.decodeFromReader +import space.kscience.visionforge.solid.prototype import space.kscience.visionforge.visitor.countDistinct import space.kscience.visionforge.visitor.flowStatistics import java.io.File @@ -22,7 +23,7 @@ suspend fun main() { vision.flowStatistics>{ _, child -> - child::class + child.prototype::class }.countDistinct().forEach { (depth, size) -> println("$depth\t$size") } diff --git a/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt b/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt index 2c9ed631..6f0a3959 100644 --- a/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt +++ b/visionforge-server/src/main/kotlin/space/kscience/visionforge/three/server/VisionServer.kt @@ -19,9 +19,7 @@ import io.ktor.server.engine.embeddedServer import io.ktor.websocket.WebSockets import io.ktor.websocket.webSocket import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.html.* import kotlinx.html.stream.createHTML @@ -133,15 +131,6 @@ public class VisionServer internal constructor( application.log.debug("Opened server socket for $name") val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") - launch { - incoming.consumeEach { - val change = visionManager.jsonFormat.decodeFromString( - VisionChange.serializer(), it.data.decodeToString() - ) - vision.update(change) - } - } - try { withContext(visionManager.context.coroutineContext) { vision.flowChanges(visionManager, Duration.milliseconds(updateInterval)).collect { update -> diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt index d1d41d09..35cf7ab6 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Composite.kt @@ -6,8 +6,7 @@ import space.kscience.dataforge.meta.update import space.kscience.visionforge.* public enum class CompositeType { - SUM, // Dumb sum of meshes - UNION, //CSG union + UNION, INTERSECT, SUBTRACT } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt index 560ec496..211e3af2 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSegment.kt @@ -15,11 +15,11 @@ import kotlin.math.sin @Serializable @SerialName("solid.cone") public class ConeSegment( - public val bottomRadius: Float, - public val height: Float, - public val topRadius: Float, - public val startAngle: Float = 0f, - public val angle: Float = PI2 + public var bottomRadius: Float, + public var height: Float, + public var topRadius: Float, + public var startAngle: Float = 0f, + public var angle: Float = PI2 ) : SolidBase(), GeometrySolid { override fun toGeometry(geometryBuilder: GeometryBuilder) { @@ -83,14 +83,10 @@ public inline fun VisionContainerBuilder.cone( bottomRadius: Number, height: Number, upperRadius: Number = 0.0, - startAngle: Number = 0f, - angle: Number = PI2, name: String? = null, block: ConeSegment.() -> Unit = {} ): ConeSegment = ConeSegment( bottomRadius.toFloat(), height.toFloat(), - topRadius = upperRadius.toFloat(), - startAngle = startAngle.toFloat(), - angle = angle.toFloat() + topRadius = upperRadius.toFloat() ).apply(block).also { set(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt index a9c5622b..caa8c746 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/ConeSurface.kt @@ -16,13 +16,13 @@ import kotlin.math.sin @Serializable @SerialName("solid.coneSurface") public class ConeSurface( - public val bottomRadius: Float, - public val bottomInnerRadius: Float, - public val height: Float, - public val topRadius: Float, - public val topInnerRadius: Float, - public val startAngle: Float = 0f, - public val angle: Float = PI2, + public var bottomRadius: Float, + public var bottomInnerRadius: Float, + public var height: Float, + public var topRadius: Float, + public var topInnerRadius: Float, + public var startAngle: Float = 0f, + public var angle: Float = PI2, ) : SolidBase(), GeometrySolid { init { @@ -148,8 +148,6 @@ public inline fun VisionContainerBuilder.coneSurface( height: Number, topOuterRadius: Number, topInnerRadius: Number, - startAngle: Number = 0f, - angle: Number = PI2, name: String? = null, block: ConeSurface.() -> Unit = {}, ): ConeSurface = ConeSurface( @@ -158,6 +156,4 @@ public inline fun VisionContainerBuilder.coneSurface( height = height.toFloat(), topRadius = topOuterRadius.toFloat(), topInnerRadius = topInnerRadius.toFloat(), - startAngle = startAngle.toFloat(), - angle = angle.toFloat() ).apply(block).also { set(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt index 40e03512..d5943e3a 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Extruded.kt @@ -2,8 +2,9 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable -import space.kscience.dataforge.meta.Config -import space.kscience.visionforge.* +import space.kscience.visionforge.VisionBuilder +import space.kscience.visionforge.VisionContainerBuilder +import space.kscience.visionforge.set import kotlin.math.PI import kotlin.math.cos import kotlin.math.sin @@ -12,7 +13,7 @@ import kotlin.math.sin public typealias Shape2D = List @Serializable -public class Shape2DBuilder(private val points: ArrayList = ArrayList()) { +public class Shape2DBuilder(private val points: MutableList = ArrayList()) { public fun point(x: Number, y: Number) { points.add(Point2D(x, y)) @@ -37,9 +38,19 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo @Serializable @SerialName("solid.extrude") public class Extruded( - public val shape: List, - public val layers: List -) : SolidBase(), GeometrySolid, VisionPropertyContainer { + public var shape: List = ArrayList(), + public var layers: MutableList = ArrayList() +) : SolidBase(), GeometrySolid { + + public fun shape(block: Shape2DBuilder.() -> Unit) { + this.shape = Shape2DBuilder().apply(block).build() + //TODO send invalidation signal + } + + public fun layer(z: Number, x: Number = 0.0, y: Number = 0.0, scale: Number = 1.0) { + layers.add(Layer(x.toFloat(), y.toFloat(), z.toFloat(), scale.toFloat())) + //TODO send invalidation signal + } override fun toGeometry(geometryBuilder: GeometryBuilder) { val shape: Shape2D = shape @@ -92,24 +103,6 @@ public class Extruded( } } -public class ExtrudeBuilder( - public var shape: List = emptyList(), - public var layers: ArrayList = ArrayList(), - config: Config = Config() -) : SimpleVisionPropertyContainer(config) { - public fun shape(block: Shape2DBuilder.() -> Unit) { - this.shape = Shape2DBuilder().apply(block).build() - } - - public fun layer(z: Number, x: Number = 0.0, y: Number = 0.0, scale: Number = 1.0) { - layers.add(Layer(x.toFloat(), y.toFloat(), z.toFloat(), scale.toFloat())) - } - - internal fun build(): Extruded = Extruded(shape, layers).apply { configure(config) } -} - @VisionBuilder -public fun VisionContainerBuilder.extrude( - name: String? = null, - action: ExtrudeBuilder.() -> Unit = {} -): Extruded = ExtrudeBuilder().apply(action).build().also { set(name, it) } \ No newline at end of file +public fun VisionContainerBuilder.extrude(name: String? = null, action: Extruded.() -> Unit = {}): Extruded = + Extruded().apply(action).also { set(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt index cd9f2cbd..41fe9903 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/PolyLine.kt @@ -13,7 +13,7 @@ import space.kscience.visionforge.set @Serializable @SerialName("solid.line") -public class PolyLine(public val points: List) : SolidBase(), Solid { +public class PolyLine(public var points: List) : SolidBase(), Solid { //var lineType by string() public var thickness: Number by allProperties(inherit = false).number(1.0, diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt index a4c8e69c..d7d03c90 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Solid.kt @@ -1,7 +1,9 @@ package space.kscience.visionforge.solid -import space.kscience.dataforge.meta.* +import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.enum +import space.kscience.dataforge.meta.int import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.plus @@ -12,28 +14,14 @@ import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY import space.kscience.visionforge.solid.Solid.Companion.DETAIL_KEY import space.kscience.visionforge.solid.Solid.Companion.IGNORE_KEY import space.kscience.visionforge.solid.Solid.Companion.LAYER_KEY -import space.kscience.visionforge.solid.Solid.Companion.POSITION_KEY -import space.kscience.visionforge.solid.Solid.Companion.ROTATION_KEY -import space.kscience.visionforge.solid.Solid.Companion.SCALE_KEY -import space.kscience.visionforge.solid.Solid.Companion.X_KEY -import space.kscience.visionforge.solid.Solid.Companion.X_POSITION_KEY -import space.kscience.visionforge.solid.Solid.Companion.X_ROTATION_KEY -import space.kscience.visionforge.solid.Solid.Companion.X_SCALE_KEY -import space.kscience.visionforge.solid.Solid.Companion.Y_KEY -import space.kscience.visionforge.solid.Solid.Companion.Y_POSITION_KEY -import space.kscience.visionforge.solid.Solid.Companion.Y_ROTATION_KEY -import space.kscience.visionforge.solid.Solid.Companion.Y_SCALE_KEY -import space.kscience.visionforge.solid.Solid.Companion.Z_KEY -import space.kscience.visionforge.solid.Solid.Companion.Z_POSITION_KEY -import space.kscience.visionforge.solid.Solid.Companion.Z_ROTATION_KEY -import space.kscience.visionforge.solid.Solid.Companion.Z_SCALE_KEY -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty /** * Interface for 3-dimensional [Vision] */ public interface Solid : Vision { + public var position: Point3D? + public var rotation: Point3D? + public var scale: Point3D? override val descriptor: NodeDescriptor get() = Companion.descriptor @@ -49,7 +37,7 @@ public interface Solid : Vision { public val Y_KEY: Name = "y".asName() public val Z_KEY: Name = "z".asName() - public val POSITION_KEY: Name = "position".asName() + public val POSITION_KEY: Name = "pos".asName() public val X_POSITION_KEY: Name = POSITION_KEY + X_KEY public val Y_POSITION_KEY: Name = POSITION_KEY + Y_KEY @@ -84,23 +72,6 @@ public interface Solid : Vision { hide() } - node(POSITION_KEY){ - hide() - } - - node(ROTATION_KEY){ - hide() - } - - node(SCALE_KEY){ - hide() - } - - value(DETAIL_KEY) { - type(ValueType.NUMBER) - hide() - } - item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor) enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) { @@ -108,6 +79,22 @@ public interface Solid : Vision { } } } + + internal fun solidEquals(first: Solid, second: Solid): Boolean { + if (first.position != second.position) return false + if (first.rotation != second.rotation) return false + if (first.scale != second.scale) return false + if (first.meta != second.meta) return false + return true + } + + internal fun solidHashCode(solid: Solid): Int { + var result = +(solid.position?.hashCode() ?: 0) + result = 31 * result + (solid.rotation?.hashCode() ?: 0) + result = 31 * result + (solid.scale?.hashCode() ?: 0) + result = 31 * result + solid.allProperties().hashCode() + return result + } } } @@ -158,51 +145,74 @@ public var Vision.ignore: Boolean? // get() = getProperty(SELECTED_KEY).boolean // set(value) = setProperty(SELECTED_KEY, value) -internal fun float(name: Name, default: Number): ReadWriteProperty = - object : ReadWriteProperty { - override fun getValue(thisRef: Solid, property: KProperty<*>): Number { - return thisRef.getOwnProperty(name)?.number ?: default - } +private fun Solid.position(): Point3D = + position ?: Point3D(0.0, 0.0, 0.0).also { position = it } - override fun setValue(thisRef: Solid, property: KProperty<*>, value: Number) { - thisRef.setProperty(name, value) - } +public var Solid.x: Number + get() = position?.x ?: 0f + set(value) { + position().x = value.toDouble() + invalidateProperty(Solid.X_POSITION_KEY) } -internal fun point(name: Name, default: Float): ReadWriteProperty = - object : ReadWriteProperty { - override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? { - val item = thisRef.getOwnProperty(name) ?: return null - return object : Point3D { - override val x: Float get() = item[X_KEY]?.float ?: default - override val y: Float get() = item[Y_KEY]?.float ?: default - override val z: Float get() = item[Z_KEY]?.float ?: default - } - } - - override fun setValue(thisRef: Solid, property: KProperty<*>, value: Point3D?) { - if (value == null) { - thisRef.setProperty(name, null) - } else { - thisRef.setProperty(name + X_KEY, value.x) - thisRef.setProperty(name + Y_KEY, value.y) - thisRef.setProperty(name + Z_KEY, value.z) - } - } +public var Solid.y: Number + get() = position?.y ?: 0f + set(value) { + position().y = value.toDouble() + invalidateProperty(Solid.Y_POSITION_KEY) } -public var Solid.position: Point3D? by point(POSITION_KEY, 0f) -public var Solid.rotation: Point3D? by point(ROTATION_KEY, 0f) -public var Solid.scale: Point3D? by point(SCALE_KEY, 1f) +public var Solid.z: Number + get() = position?.z ?: 0f + set(value) { + position().z = value.toDouble() + invalidateProperty(Solid.Z_POSITION_KEY) + } -public var Solid.x: Number by float(X_POSITION_KEY, 0f) -public var Solid.y: Number by float(Y_POSITION_KEY, 0f) -public var Solid.z: Number by float(Z_POSITION_KEY, 0f) +private fun Solid.rotation(): Point3D = + rotation ?: Point3D(0.0, 0.0, 0.0).also { rotation = it } -public var Solid.rotationX: Number by float(X_ROTATION_KEY, 0f) -public var Solid.rotationY: Number by float(Y_ROTATION_KEY, 0f) -public var Solid.rotationZ: Number by float(Z_ROTATION_KEY, 0f) +public var Solid.rotationX: Number + get() = rotation?.x ?: 0f + set(value) { + rotation().x = value.toDouble() + invalidateProperty(Solid.X_ROTATION_KEY) + } -public var Solid.scaleX: Number by float(X_SCALE_KEY, 1f) -public var Solid.scaleY: Number by float(Y_SCALE_KEY, 1f) -public var Solid.scaleZ: Number by float(Z_SCALE_KEY, 1f) \ No newline at end of file +public var Solid.rotationY: Number + get() = rotation?.y ?: 0f + set(value) { + rotation().y = value.toDouble() + invalidateProperty(Solid.Y_ROTATION_KEY) + } + +public var Solid.rotationZ: Number + get() = rotation?.z ?: 0f + set(value) { + rotation().z = value.toDouble() + invalidateProperty(Solid.Z_ROTATION_KEY) + } + +private fun Solid.scale(): Point3D = + scale ?: Point3D(1.0, 1.0, 1.0).also { scale = it } + +public var Solid.scaleX: Number + get() = scale?.x ?: 1f + set(value) { + scale().x = value.toDouble() + invalidateProperty(Solid.X_SCALE_KEY) + } + +public var Solid.scaleY: Number + get() = scale?.y ?: 1f + set(value) { + scale().y = value.toDouble() + invalidateProperty(Solid.Y_SCALE_KEY) + } + +public var Solid.scaleZ: Number + get() = scale?.z ?: 1f + set(value) { + scale().z = value.toDouble() + invalidateProperty(Solid.Z_SCALE_KEY) + } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt index d2fee33a..e69f729c 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidBase.kt @@ -2,13 +2,21 @@ package space.kscience.visionforge.solid import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.descriptors.NodeDescriptor +import space.kscience.dataforge.meta.float +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.node import space.kscience.visionforge.VisionBase import space.kscience.visionforge.VisionChange @Serializable @SerialName("solid") -public open class SolidBase : VisionBase(), Solid { +public open class SolidBase( + override var position: Point3D? = null, + override var rotation: Point3D? = null, + override var scale: Point3D? = null, +) : VisionBase(), Solid { override val descriptor: NodeDescriptor get() = Solid.descriptor override fun update(change: VisionChange) { @@ -16,3 +24,15 @@ public open class SolidBase : VisionBase(), Solid { super.update(change) } } + +internal fun Meta.toVector(default: Float = 0f) = Point3D( + this[Solid.X_KEY].float ?: default, + this[Solid.Y_KEY].float ?: default, + this[Solid.Z_KEY].float ?: default +) + +internal fun Solid.updatePosition(meta: Meta?) { + meta[Solid.POSITION_KEY].node?.toVector()?.let { position = it } + meta[Solid.ROTATION_KEY].node?.toVector()?.let { rotation = it } + meta[Solid.SCALE_KEY].node?.toVector(1f)?.let { scale = it } +} \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt index b78ec4a5..20ca8afc 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidGroup.kt @@ -1,25 +1,23 @@ package space.kscience.visionforge.solid +import kotlinx.serialization.KSerializer +import kotlinx.serialization.PolymorphicSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.MapSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import space.kscience.dataforge.meta.MetaItem import space.kscience.dataforge.meta.descriptors.NodeDescriptor import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.NameToken import space.kscience.visionforge.* -/** - * A container with prototype support - */ public interface PrototypeHolder { - /** - * Build or update prototype tree - */ @VisionBuilder public fun prototypes(builder: VisionContainerBuilder.() -> Unit) - /** - * Resolve a prototype from this container. Should never return a ref. - */ public fun getPrototype(name: Name): Solid? } @@ -29,35 +27,40 @@ public interface PrototypeHolder { */ @Serializable @SerialName("group.solid") -public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder { - - override val children: Map get() = super.childrenInternal.filter { it.key != PROTOTYPES_TOKEN } - - private var prototypes: MutableVisionGroup? - get() = childrenInternal[PROTOTYPES_TOKEN] as? MutableVisionGroup - set(value) { - set(PROTOTYPES_TOKEN, value) - } +public class SolidGroup( + @Serializable(PrototypeSerializer::class) internal var prototypes: MutableVisionGroup? = null, + override var position: Point3D? = null, + override var rotation: Point3D? = null, + override var scale: Point3D? = null, +) : VisionGroupBase(), Solid, PrototypeHolder { + init { + prototypes?.parent = this + } override val descriptor: NodeDescriptor get() = Solid.descriptor /** - * Get a prototype redirecting the request to the parent if prototype is not found. - * If prototype is a ref, then it is unfolded automatically. + * Ger a prototype redirecting the request to the parent if prototype is not found */ override fun getPrototype(name: Name): Solid? = - prototypes?.get(name)?.unref ?: (parent as? PrototypeHolder)?.getPrototype(name) + (prototypes?.get(name) as? Solid) ?: (parent as? PrototypeHolder)?.getPrototype(name) /** * Create or edit prototype node as a group */ override fun prototypes(builder: VisionContainerBuilder.() -> Unit): Unit { - (prototypes ?: SolidGroup().also { + (prototypes ?: Prototypes().also { prototypes = it + it.parent = this }).run(builder) } +// /** +// * TODO add special static group to hold statics without propagation +// */ +// override fun addStatic(child: VisualObject) = setChild(NameToken("@static(${child.hashCode()})"), child) + override fun createGroup(): SolidGroup = SolidGroup() override fun update(change: VisionChange) { @@ -87,3 +90,50 @@ public fun VisionContainerBuilder.group( @VisionBuilder public fun VisionContainerBuilder.group(name: String, action: SolidGroup.() -> Unit = {}): SolidGroup = SolidGroup().apply(action).also { set(name, it) } + +/** + * A special class which works as a holder for prototypes + */ +internal class Prototypes( + children: MutableMap = hashMapOf(), +) : VisionGroupBase(children), PrototypeHolder { + + override fun getProperty( + name: Name, + inherit: Boolean, + includeStyles: Boolean, + includeDefaults: Boolean, + ): MetaItem? = null + + override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { + error("Can't set property of a prototypes container") + } + + override val descriptor: NodeDescriptor? = null + + override fun prototypes(builder: VisionContainerBuilder.() -> Unit) { + apply(builder) + } + + override fun getPrototype(name: Name): Solid? = get(name) as? Solid +} + +internal class PrototypeSerializer : KSerializer { + + private val mapSerializer: KSerializer> = + MapSerializer( + NameToken.serializer(), + PolymorphicSerializer(Vision::class) + ) + + override val descriptor: SerialDescriptor get() = mapSerializer.descriptor + + override fun deserialize(decoder: Decoder): MutableVisionGroup { + val map = mapSerializer.deserialize(decoder) + return Prototypes(map.toMutableMap()) + } + + override fun serialize(encoder: Encoder, value: MutableVisionGroup) { + mapSerializer.serialize(encoder, value.children) + } +} \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidLabel.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidLabel.kt index 3e725d0d..ac45e22e 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidLabel.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidLabel.kt @@ -9,9 +9,9 @@ import space.kscience.visionforge.set @Serializable @SerialName("solid.label") public class SolidLabel( - public val text: String, - public val fontSize: Double, - public val fontFamily: String, + public var text: String, + public var fontSize: Double, + public var fontFamily: String, ) : SolidBase(), Solid @VisionBuilder diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt index 1cb5eac8..17ee9141 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SolidReference.kt @@ -11,52 +11,25 @@ import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.* import space.kscience.visionforge.* - -public interface SolidReference : VisionGroup { - /** - * The prototype for this reference. Always returns a "real" prototype, not a reference - */ +public interface SolidReference : Solid { public val prototype: Solid } - -/** - * Get a vision prototype if it is a [SolidReference] or vision itself if it is not. - * Unref is recursive, so it always returns a non-reference. - */ -public val Vision.unref: Solid - get() = when (this) { - is SolidReference -> prototype.unref - is Solid -> this - else -> error("This Vision is neither Solid nor SolidReference") - } - - private fun SolidReference.getRefProperty( name: Name, inherit: Boolean, includeStyles: Boolean, includeDefaults: Boolean, -): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) { - getOwnProperty(name) -} else { - buildList { - add(getOwnProperty(name)) - if (includeStyles) { - addAll(getStyleItems(name)) - } - add(prototype.getProperty(name, inherit, includeStyles, includeDefaults)) - if (inherit) { - add(parent?.getProperty(name, inherit)) - } - }.merge() -} - -private fun childToken(childName: Name): NameToken = - NameToken(SolidReferenceGroup.REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) - -private fun childPropertyName(childName: Name, propertyName: Name): Name = - childToken(childName) + propertyName +): MetaItem? = buildList { + add(getOwnProperty(name)) + if (includeStyles) { + addAll(getStyleItems(name)) + } + add(prototype.getProperty(name, inherit, includeStyles, includeDefaults)) + if (inherit) { + add(parent?.getProperty(name, inherit)) + } +}.merge() /** * A reference [Solid] to reuse a template object @@ -65,24 +38,48 @@ private fun childPropertyName(childName: Name, propertyName: Name): Name = @SerialName("solid.ref") public class SolidReferenceGroup( public val refName: Name, -) : VisionBase(), SolidReference, VisionGroup, Solid { +) : SolidBase(), SolidReference, VisionGroup { /** * Recursively search for defined template in the parent */ - override val prototype: Solid by lazy { - if (parent == null) error("No parent is present for SolidReferenceGroup") - if (parent !is PrototypeHolder) error("Parent does not hold prototypes") - (parent as? PrototypeHolder)?.getPrototype(refName) ?: error("Prototype with name $refName not found") - } + override val prototype: Solid + get() { + if (parent == null) error("No parent is present for SolidReferenceGroup") + if (parent !is SolidGroup) error("Reference parent is not a group") + return (parent as? SolidGroup)?.getPrototype(refName) + ?: error("Prototype with name $refName not found") + } override val children: Map get() = (prototype as? VisionGroup)?.children - ?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN } + ?.filter { !it.key.toString().startsWith("@") } ?.mapValues { - ReferenceChild(this, it.key.asName()) + ReferenceChild(it.key.asName()) } ?: emptyMap() + private fun childToken(childName: Name): NameToken = + NameToken(REFERENCE_CHILD_PROPERTY_PREFIX, childName.toString()) + + private fun childPropertyName(childName: Name, propertyName: Name): Name = + childToken(childName) + propertyName + + private fun getChildProperty(childName: Name, propertyName: Name): MetaItem? { + return getOwnProperty(childPropertyName(childName, propertyName)) + } + + private fun setChildProperty(childName: Name, propertyName: Name, item: MetaItem?, notify: Boolean) { + setProperty(childPropertyName(childName, propertyName), item, notify) + } + + private fun prototypeFor(name: Name): Solid { + return if (name.isEmpty()) prototype else { + val proto = (prototype as? SolidGroup)?.get(name) + ?: error("Prototype with name $name not found in SolidReferenceGroup $refName") + proto as? Solid ?: error("Prototype with name $name is ${proto::class} but expected Solid") + } + } + override fun getProperty( name: Name, inherit: Boolean, @@ -97,32 +94,37 @@ public class SolidReferenceGroup( * A ProxyChild is created temporarily only to interact with properties, it does not store any values * (properties are stored in external cache) and created and destroyed on-demand). */ - private class ReferenceChild( - val owner: SolidReferenceGroup, - private val refName: Name - ) : SolidReference, VisionGroup, Solid { + private inner class ReferenceChild(private val childName: Name) : SolidReference, VisionGroup { - override val prototype: Solid by lazy { - if (refName.isEmpty()) owner.prototype else { - val proto = (owner.prototype as? VisionGroup)?.get(refName) - ?: error("Prototype with name $refName not found in SolidReferenceGroup ${owner.refName}") - proto.unref as? Solid - ?: error("Prototype with name $refName is ${proto::class} but expected Solid") + //TODO replace by properties + override var position: Point3D? + get() = prototype.position + set(_) { + error("Can't set position of reference") } - } + override var rotation: Point3D? + get() = prototype.rotation + set(_) { + error("Can't set position of reference") + } + override var scale: Point3D? + get() = prototype.scale + set(_) { + error("Can't set position of reference") + } + override val prototype: Solid get() = prototypeFor(childName) override val children: Map get() = (prototype as? VisionGroup)?.children - ?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN } + ?.filter { !it.key.toString().startsWith("@") } ?.mapValues { (key, _) -> - ReferenceChild(owner, refName + key.asName()) + ReferenceChild(childName + key.asName()) } ?: emptyMap() - override fun getOwnProperty(name: Name): MetaItem? = - owner.getOwnProperty(childPropertyName(refName, name)) + override fun getOwnProperty(name: Name): MetaItem? = getChildProperty(childName, name) override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { - owner.setProperty(childPropertyName(refName, name), item, notify) + setChildProperty(childName, name, item, notify) } override fun getProperty( @@ -130,12 +132,16 @@ public class SolidReferenceGroup( inherit: Boolean, includeStyles: Boolean, includeDefaults: Boolean, - ): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults) + ): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) { + getOwnProperty(name) + } else { + getRefProperty(name, inherit, includeStyles, includeDefaults) + } override var parent: VisionGroup? get() { - val parentName = refName.cutLast() - return if (parentName.isEmpty()) owner else ReferenceChild(owner, parentName) + val parentName = childName.cutLast() + return if (parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName) } set(_) { error("Setting a parent for a reference child is not possible") @@ -143,8 +149,8 @@ public class SolidReferenceGroup( @DFExperimental override val propertyChanges: Flow - get() = owner.propertyChanges.mapNotNull { name -> - if (name.startsWith(childToken(refName))) { + get() = this@SolidReferenceGroup.propertyChanges.mapNotNull { name -> + if (name.startsWith(childToken(childName))) { name.cutFirst() } else { null @@ -152,7 +158,7 @@ public class SolidReferenceGroup( } override fun invalidateProperty(propertyName: Name) { - owner.invalidateProperty(childPropertyName(refName, propertyName)) + this@SolidReferenceGroup.invalidateProperty(childPropertyName(childName, propertyName)) } override fun update(change: VisionChange) { @@ -170,6 +176,12 @@ public class SolidReferenceGroup( } } +/** + * Get a vision prototype if it is a [SolidReferenceGroup] or vision itself if it is not + */ +public val Vision.prototype: Vision + get() = if (this is SolidReference) prototype.prototype else this + /** * Create ref for existing prototype */ diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt index bae8d83d..c4d67f97 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/Sphere.kt @@ -4,7 +4,6 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import space.kscience.visionforge.VisionBuilder import space.kscience.visionforge.VisionContainerBuilder -import space.kscience.visionforge.VisionPropertyContainer import space.kscience.visionforge.set import kotlin.math.PI import kotlin.math.cos @@ -13,12 +12,12 @@ import kotlin.math.sin @Serializable @SerialName("solid.sphere") public class Sphere( - public val radius: Float, - public val phiStart: Float = 0f, - public val phi: Float = PI2, - public val thetaStart: Float = 0f, - public val theta: Float = PI .toFloat(), -) : SolidBase(), GeometrySolid, VisionPropertyContainer { + public var radius: Float, + public var phiStart: Float = 0f, + public var phi: Float = PI2, + public var thetaStart: Float = 0f, + public var theta: Float = PI.toFloat(), +) : SolidBase(), GeometrySolid { override fun toGeometry(geometryBuilder: GeometryBuilder) { fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D { diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt index 05149e2e..83e358e2 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/SphereLayer.kt @@ -15,12 +15,12 @@ import kotlin.math.sin @Serializable @SerialName("solid.sphereLayer") public class SphereLayer( - public val outerRadius: Float, - public val innerRadius: Float, - public val phiStart: Float = 0f, - public val phi: Float = PI2, - public val thetaStart: Float = 0f, - public val theta: Float = PI.toFloat(), + public var outerRadius: Float, + public var innerRadius: Float, + public var phiStart: Float = 0f, + public var phi: Float = PI2, + public var thetaStart: Float = 0f, + public var theta: Float = PI.toFloat(), ) : SolidBase(), GeometrySolid { override fun toGeometry(geometryBuilder: GeometryBuilder): Unit = geometryBuilder.run { @@ -72,17 +72,9 @@ public class SphereLayer( public inline fun VisionContainerBuilder.sphereLayer( outerRadius: Number, innerRadius: Number, - phiStart: Number = 0f, - phi: Number = PI2, - thetaStart: Number = 0f, - theta: Number = PI.toFloat(), name: String? = null, action: SphereLayer.() -> Unit = {}, ): SphereLayer = SphereLayer( outerRadius.toFloat(), innerRadius.toFloat(), - phiStart.toFloat(), - phi.toFloat(), - thetaStart.toFloat(), - theta.toFloat() ).apply(action).also { set(name, it) } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt index f5aa2d4a..35fc731f 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/geometry.kt @@ -1,68 +1,39 @@ package space.kscience.visionforge.solid -import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.encoding.Encoder -import space.kscience.dataforge.meta.* -import space.kscience.visionforge.solid.Solid.Companion.X_KEY -import space.kscience.visionforge.solid.Solid.Companion.Y_KEY -import space.kscience.visionforge.solid.Solid.Companion.Z_KEY +import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.MetaBuilder +import space.kscience.dataforge.meta.double +import space.kscience.dataforge.meta.get import kotlin.math.PI -import kotlin.math.pow -import kotlin.math.sqrt public const val PI2: Float = 2 * PI.toFloat() @Serializable -public data class Point2D(public var x: Float, public var y: Float) +public data class Point2D(public var x: Double, public var y: Double) -public fun Point2D(x: Number, y: Number): Point2D = Point2D(x.toFloat(), y.toFloat()) +public fun Point2D(x: Number, y: Number): Point2D = Point2D(x.toDouble(), y.toDouble()) public fun Point2D.toMeta(): Meta = Meta { - X_KEY put x - Y_KEY put y + Solid.X_KEY put x + Solid.Y_KEY put y } -internal fun Meta.point2D(): Point2D = Point2D(this["x"].float ?: 0f, this["y"].float ?: 0f) - -@Serializable(Point3DSerializer::class) -public interface Point3D { - public val x: Float - public val y: Float - public val z: Float +internal fun Meta.point2D(): Point2D = Point2D(this["x"].double ?: 0.0, this["y"].double ?: 0.0) +@Serializable +public data class Point3D( + public var x: Double, + public var y: Double, + public var z: Double, +) { public companion object { public val ZERO: Point3D = Point3D(0.0, 0.0, 0.0) public val ONE: Point3D = Point3D(1.0, 1.0, 1.0) } } -@Serializable(Point3DSerializer::class) -public interface MutablePoint3D : Point3D { - override var x: Float - override var y: Float - override var z: Float -} - -@Serializable -private class Point3DImpl(override var x: Float, override var y: Float, override var z: Float) : MutablePoint3D - -internal object Point3DSerializer : KSerializer { - - override val descriptor: SerialDescriptor = Point3DImpl.serializer().descriptor - - - override fun deserialize(decoder: Decoder): Point3D = decoder.decodeSerializableValue(Point3DImpl.serializer()) - - override fun serialize(encoder: Encoder, value: Point3D) { - val impl: Point3DImpl = (value as? Point3DImpl) ?: Point3DImpl(value.x, value.y, value.z) - encoder.encodeSerializableValue(Point3DImpl.serializer(), impl) - } -} - -public fun Point3D(x: Number, y: Number, z: Number): Point3D = Point3DImpl(x.toFloat(), y.toFloat(), z.toFloat()) +public fun Point3D(x: Number, y: Number, z: Number): Point3D = Point3D(x.toDouble(), y.toDouble(), z.toDouble()) public operator fun Point3D.plus(other: Point3D): Point3D = Point3D( this.x + other.x, @@ -70,52 +41,10 @@ public operator fun Point3D.plus(other: Point3D): Point3D = Point3D( this.z + other.z ) -public operator fun Point3D.minus(other: Point3D): Point3D = Point3D( - this.x - other.x, - this.y - other.y, - this.z - other.z -) - -public operator fun Point3D.unaryMinus(): Point3D = Point3D( - -x, - -y, - -z -) - -public infix fun Point3D.cross(other: Point3D): Point3D = Point3D( - y * other.z - z * other.y, - z * other.x - x * other.z, - x * other.y - y * other.x -) - -public fun MutablePoint3D.normalizeInPlace() { - val norm = sqrt(x.pow(2) + y.pow(2) + z.pow(2)) - x /= norm - y /= norm - z /= norm -} - -internal fun ItemProvider.point3D(default: Float = 0f) = object : Point3D { - override val x: Float by float(default) - override val y: Float by float(default) - override val z: Float by float(default) -} +internal fun Meta.point3D() = Point3D(this["x"].double ?: 0.0, this["y"].double ?: 0.0, this["y"].double ?: 0.0) public fun Point3D.toMeta(): MetaBuilder = Meta { - X_KEY put x - Y_KEY put y - Z_KEY put z -} - - -internal fun Meta.toVector(default: Float = 0f) = Point3D( - this[Solid.X_KEY].float ?: default, - this[Solid.Y_KEY].float ?: default, - this[Solid.Z_KEY].float ?: default -) - -internal fun Solid.updatePosition(meta: Meta?) { - meta[Solid.POSITION_KEY].node?.toVector()?.let { position = it } - meta[Solid.ROTATION_KEY].node?.toVector()?.let { rotation = it } - meta[Solid.SCALE_KEY].node?.toVector(1f)?.let { scale = it } + Solid.X_KEY put x + Solid.Y_KEY put y + Solid.Z_KEY put z } \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt index 905fa234..b6209b54 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/specifications/Camera.kt @@ -18,6 +18,7 @@ public class Camera : Scheme() { public var distance: Double by double(INITIAL_DISTANCE) public var azimuth: Double by double(INITIAL_AZIMUTH) public var latitude: Double by double(INITIAL_LATITUDE) + public val zenith: Double get() = PI / 2 - latitude public companion object : SchemeSpec(::Camera) { public const val INITIAL_DISTANCE: Double = 300.0 @@ -50,6 +51,4 @@ public class Camera : Scheme() { } } } -} - -public val Camera.zenith: Double get() = PI / 2 - latitude \ No newline at end of file +} \ No newline at end of file diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt index a51aaa25..5d07303e 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/RemoveSingleChild.kt @@ -1,38 +1,35 @@ package space.kscience.visionforge.solid.transform -import space.kscience.dataforge.meta.itemSequence import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.names.asName -import space.kscience.visionforge.MutableVisionGroup -import space.kscience.visionforge.Vision -import space.kscience.visionforge.VisionGroup -import space.kscience.visionforge.meta +import space.kscience.visionforge.* import space.kscience.visionforge.solid.* -private operator fun Number.plus(other: Number) = toFloat() + other.toFloat() -private operator fun Number.times(other: Number) = toFloat() * other.toFloat() - @DFExperimental -internal fun Vision.updateFrom(other: Vision): Vision { - if (this is Solid && other is Solid) { - x += other.x - y += other.y - z += other.y - rotationX += other.rotationX - rotationY += other.rotationY - rotationZ += other.rotationZ - scaleX *= other.scaleX - scaleY *= other.scaleY - scaleZ *= other.scaleZ - other.meta.itemSequence().forEach { (name, item) -> - if (getProperty(name) == null) { - setProperty(name, item) +internal fun mergeChild(parent: VisionGroup, child: Vision): Vision { + return child.apply { + + configure(parent.meta) + + //parent.properties?.let { config.update(it) } + + if (this is Solid && parent is Solid) { + position = (position ?: Point3D.ZERO) + (parent.position ?: Point3D.ZERO) + rotation = (parent.rotation ?: Point3D.ZERO) + (parent.rotation ?: Point3D.ZERO) + scale = when { + scale == null && parent.scale == null -> null + scale == null -> parent.scale + parent.scale == null -> scale + else -> Point3D( + scale!!.x * parent.scale!!.x, + scale!!.y * parent.scale!!.y, + scale!!.z * parent.scale!!.z + ) } } - } - return this -} + } +} @DFExperimental internal object RemoveSingleChild : VisualTreeTransform() { @@ -46,7 +43,7 @@ internal object RemoveSingleChild : VisualTreeTransform() { } if (parent is VisionGroup && parent.children.size == 1) { val child = parent.children.values.first() - val newParent = child.updateFrom(parent) + val newParent = mergeChild(parent, child) newParent.parent = null set(childName.asName(), newParent) } diff --git a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/UnRef.kt b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/UnRef.kt index 890c8291..b6d7c2e6 100644 --- a/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/UnRef.kt +++ b/visionforge-solid/src/commonMain/kotlin/space/kscience/visionforge/solid/transform/UnRef.kt @@ -31,7 +31,7 @@ internal object UnRef : VisualTreeTransform() { } children.filter { (it.value as? SolidReferenceGroup)?.refName == name }.forEach { (key, value) -> val reference = value as SolidReferenceGroup - val newChild = reference.prototype.updateFrom(reference) + val newChild = mergeChild(reference, reference.prototype) newChild.parent = null set(key.asName(), newChild) // replace proxy with merged object } diff --git a/visionforge-threejs/build.gradle.kts b/visionforge-threejs/build.gradle.kts index a6407d13..60c00061 100644 --- a/visionforge-threejs/build.gradle.kts +++ b/visionforge-threejs/build.gradle.kts @@ -4,6 +4,6 @@ plugins { dependencies { api(project(":visionforge-solid")) - implementation(npm("three", "0.130.1")) - implementation(npm("three-csg-ts", "3.1.6")) + implementation(npm("three", "0.124.0")) + implementation(npm("three-csg-ts", "2.2.2")) } diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/THREE.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/THREE.kt index 3f8028cb..d3844561 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/THREE.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/THREE.kt @@ -24,7 +24,6 @@ @file:JsModule("three") @file:JsNonModule -@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING", "unused") package info.laht.threekt diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/animation/AnimationAction.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/animation/AnimationAction.kt index d0e9b073..8abbc8ae 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/animation/AnimationAction.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/animation/AnimationAction.kt @@ -1,4 +1,3 @@ -@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING", "unused") @file:JsModule("three") @file:JsNonModule diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferAttribute.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferAttribute.kt index 6a773330..001aa20c 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferAttribute.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferAttribute.kt @@ -44,7 +44,6 @@ abstract external class BufferAttribute protected constructor( * Default is 0. Position at whcih to start update. */ var offset: Int - /** * Default is -1, which means don't use update ranges. @@ -57,19 +56,16 @@ abstract external class BufferAttribute protected constructor( * UUID of this object instance. This gets automatically assigned and this shouldn't be edited. */ val uuid: String - /** * Optional name for this attribute instance. Default is an empty string. */ var name: String var array: dynamic - /** * The length of vectors that are being stored in the array. */ val itemSize: Int - /** * Stores the array's length divided by the itemSize. * @@ -77,7 +73,6 @@ abstract external class BufferAttribute protected constructor( * then this will count the number of such vectors stored. */ val count: Int - /** * Indicates how the underlying data in the buffer maps to the values in the GLSL shader code. See the constructor above for details. */ @@ -91,7 +86,6 @@ abstract external class BufferAttribute protected constructor( * This corresponds to the gl.DYNAMIC_DRAW flag. */ var dynamic: Boolean - /** * This can be used to only update some components of stored vectors ( * for example, just the component related to color). @@ -105,16 +99,8 @@ abstract external class BufferAttribute protected constructor( */ var needsUpdate: Boolean - /** - * A callback function that is executed after the Renderer has transferred the attribute array data to the GPU. - */ var onUploadCallback: () -> Unit - /** - * Sets the value of the [onUploadCallback] property. - */ - fun onUpload(callback: () -> Unit) - /** * A version number, incremented every time the needsUpdate property is set to true. */ @@ -133,7 +119,6 @@ abstract external class BufferAttribute protected constructor( fun getW(index: Int): Number fun copy(source: BufferAttribute): BufferAttribute - /** * Copy a vector from bufferAttribute[index2] to array[index1]. */ @@ -220,22 +205,3 @@ abstract external class BufferAttribute protected constructor( */ fun setXYZW(index: Int, x: Number, y: Number, z: Number, w: Number) } - - -external class Float32BufferAttribute( - array: Array, - itemSize: Int, - normalized: Boolean = definedExternally -) : BufferAttribute - -external class Int32BufferAttribute( - array: IntArray, - itemSize: Int, - normalized: Boolean = definedExternally -) : BufferAttribute - -external class Int16BufferAttribute( - array: ShortArray, - itemSize: Int, - normalized: Boolean = definedExternally -) : BufferAttribute \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferGeometry.kt index 251627a7..c3fd093b 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/BufferGeometry.kt @@ -85,14 +85,14 @@ open external class BufferGeometry { fun clearGroups() fun addGroup(start: Int, count: Int, materialIndex: Int = definedExternally) - fun setAttribute(name: String, attribute: BufferAttribute) + fun addAttribute(name: String, attribute: BufferAttribute) fun getAttribute(name: String): BufferAttribute - fun deleteAttribute(name: String): BufferGeometry + fun removeAttribute(name: String): BufferGeometry fun setIndex(index: BufferAttribute) - fun setIndex(index: Array) fun setDrawRange(start: Int, count: Int) + fun fromGeometry(geometry: Geometry) fun setFromObject(`object`: Object3D): BufferGeometry fun updateFromObject(`object`: Object3D): BufferGeometry fun setFromPoints(points: Array): BufferGeometry diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/DirectGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/DirectGeometry.kt new file mode 100644 index 00000000..bae8ce28 --- /dev/null +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/DirectGeometry.kt @@ -0,0 +1,48 @@ +/* + * The MIT License + * + * Copyright 2017-2018 Lars Ivar Hatledal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +@file:JsModule("three") +@file:JsNonModule + +package info.laht.threekt.core + +import info.laht.threekt.math.Box3 +import info.laht.threekt.math.Sphere + +external class DirectGeometry { + + var verticesNeedUpdate: Boolean + var normalsNeedUpdate: Boolean + var colorsNeedUpdate: Boolean + var uvsNeedUpdate: Boolean + var groupsNeedUpdate: Boolean + + fun computeBoundingBox(): Box3 + fun computeBoundingSphere(): Sphere + + fun dispose() + + fun fromGeometry(geometry: Geometry) + +} \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/Geometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/Geometry.kt new file mode 100644 index 00000000..522ab07c --- /dev/null +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/core/Geometry.kt @@ -0,0 +1,109 @@ +/* + * The MIT License + * + * Copyright 2017-2018 Lars Ivar Hatledal + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +@file:JsModule("three") +@file:JsNonModule + +package info.laht.threekt.core + +import info.laht.threekt.math.* +import info.laht.threekt.objects.Mesh + +external interface MorphTarget { + val name: String + val vertices: Array +} + +external interface MorphNormal { + val name: String + val normals: Array +} + +open external class Geometry { + + val id: Int + + var vertices: Array + var colors: Array + var faces: Array + var faceVertexUvs: Array> + + var morphTargets: Array + var morphNormals: Array + + var skinWeights: Array + var skinIndices: Array + + var lineDistances: List + + var boundingBox: Box3? + var boundingSphere: Sphere? + + // update flags + + var elementsNeedUpdate: Boolean + var verticesNeedUpdate: Boolean + var uvsNeedUpdate: Boolean + var normalsNeedUpdate: Boolean + var colorsNeedUpdate: Boolean + var lineDistancesNeedUpdate: Boolean + var groupsNeedUpdate: Boolean + + fun applyMatrix4(matrix: Matrix4): Geometry + fun rotateX(angle: Number): Geometry + fun rotateY(angle: Number): Geometry + fun rotateZ(angle: Number): Geometry + fun translate(x: Number, y: Number, z: Number): Geometry + fun scale(x: Number, y: Number, z: Number): Geometry + fun lookAt(vector: Vector3): Geometry + fun fromBufferGeometry(geometry: BufferGeometry): Geometry + fun addFace(a: Int, b: Int, c: Int, materialIndexOffset: Int = definedExternally) + fun center(): Vector3 + fun normalize(): Geometry + fun computeFaceNormals() + fun computeVertexNormals(areaWeighted: Boolean = definedExternally) + fun computeFlatVertexNormals() + fun computeMorphNormals() + fun computeLineDistances() + fun computeBoundingBox() + fun computeBoundingSphere() + + fun merge(geometry: Geometry, matrix: Matrix4 = definedExternally, materialIndexOffset: Int = definedExternally) + + fun mergeMesh(mesh: Mesh) + + fun mergeVertices() + + fun setFromPoint(points: Array): Geometry + + fun sortFacesByMaterialIndex() + + fun toJSON(): Any + + open fun clone(): Geometry + fun copy(geometry: Geometry): Geometry + + fun dispose() + +} diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/BoxGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/BoxGeometry.kt index 8d0e5ec5..6cf5d5a7 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/BoxGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/BoxGeometry.kt @@ -4,6 +4,7 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry external class BoxGeometry( @@ -13,4 +14,14 @@ external class BoxGeometry( widthSegments: Int = definedExternally, heightSegments: Int = definedExternally, depthSegments: Int = definedExternally +) : Geometry + + +external class BoxBufferGeometry( + width: Number, + height: Number, + depth: Number, + widthSegments: Int = definedExternally, + heightSegments: Int = definedExternally, + depthSegments: Int = definedExternally ) : BufferGeometry diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ConeGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ConeGeometry.kt index f98ad2dd..ae143fa8 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ConeGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ConeGeometry.kt @@ -4,7 +4,7 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry - +import info.laht.threekt.core.Geometry external class ConeGeometry( radius: Number = definedExternally, @@ -14,4 +14,14 @@ external class ConeGeometry( openEnded: Boolean = definedExternally, thetaStart: Boolean = definedExternally, thetaLength: Boolean = definedExternally +) : Geometry + +external class ConeBufferGeometry( + radius: Number = definedExternally, + height: Number = definedExternally, + radialSegments: Int = definedExternally, + heightSegments: Int = definedExternally, + openEnded: Boolean = definedExternally, + thetaStart: Boolean = definedExternally, + thetaLength: Boolean = definedExternally ) : BufferGeometry \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/CylinderGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/CylinderGeometry.kt index b10e5c11..a4801453 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/CylinderGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/CylinderGeometry.kt @@ -4,6 +4,7 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry external class CylinderGeometry( radiusTop: Number, @@ -14,4 +15,15 @@ external class CylinderGeometry( openEnded: Boolean = definedExternally, thetaStart: Number = definedExternally, thetaLength: Number = definedExternally +) : Geometry + +external class CylinderBufferGeometry( + radiusTop: Number, + radiusBottom: Number, + height: Number, + radialSegments: Int = definedExternally, + heightSegments: Int = definedExternally, + openEnded: Boolean = definedExternally, + thetaStart: Number = definedExternally, + thetaLength: Number = definedExternally ) : BufferGeometry \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/EdgesGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/EdgesGeometry.kt index b0a5c91b..8e6568da 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/EdgesGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/EdgesGeometry.kt @@ -4,5 +4,8 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry -public external class EdgesGeometry(geometry: BufferGeometry, thresholdAngle: Int = definedExternally) : BufferGeometry \ No newline at end of file +external class EdgesGeometry(geometry: Geometry, thresholdAngle: Int = definedExternally) : BufferGeometry { + constructor(geometry: BufferGeometry, thresholdAngle: Int = definedExternally) +} \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ExtrudeGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ExtrudedGeometry.kt similarity index 83% rename from visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ExtrudeGeometry.kt rename to visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ExtrudedGeometry.kt index 4b30e7af..ab17c116 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ExtrudeGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/ExtrudedGeometry.kt @@ -11,6 +11,7 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry import info.laht.threekt.extras.core.Shape import info.laht.threekt.math.Vector2 @@ -77,3 +78,15 @@ external open class ExtrudeBufferGeometry : BufferGeometry { var WorldUVGenerator: UVGenerator } } + +external open class ExtrudeGeometry : Geometry { + constructor(shapes: Shape, options: ExtrudeGeometryOptions?) + constructor(shapes: Array, options: ExtrudeGeometryOptions?) + + open fun addShapeList(shapes: Array, options: Any? = definedExternally) + open fun addShape(shape: Shape, options: Any? = definedExternally) + + companion object { + var WorldUVGenerator: UVGenerator + } +} \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/PlaneGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/PlaneGeometry.kt index 0b1072ef..ad758af4 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/PlaneGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/PlaneGeometry.kt @@ -4,6 +4,7 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry external class PlaneGeometry( @@ -12,4 +13,13 @@ external class PlaneGeometry( widthSegments: Int = definedExternally, heightSegments: Int = definedExternally +) : Geometry + +external class PlaneBufferGeometry( + + width: Number, + height: Number, + widthSegments: Int = definedExternally, + heightSegments: Int = definedExternally + ) : BufferGeometry \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/SphereGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/SphereGeometry.kt index 5b0c9fc6..8117ff4c 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/SphereGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/SphereGeometry.kt @@ -4,6 +4,7 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry external class SphereGeometry( radius: Number, @@ -13,4 +14,14 @@ external class SphereGeometry( phiLength: Number = definedExternally, thetaStart: Number = definedExternally, thetaLength: Number = definedExternally +) : Geometry + +external class SphereBufferGeometry( + radius: Number, + widthSegments: Int = definedExternally, + heightSegments: Int = definedExternally, + phiStart: Number = definedExternally, + phiLength: Number = definedExternally, + thetaStart: Number = definedExternally, + thetaLength: Number = definedExternally ) : BufferGeometry \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TextGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TextBufferGeometry.kt similarity index 83% rename from visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TextGeometry.kt rename to visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TextBufferGeometry.kt index b8c935f3..ca9f93b3 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TextGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TextBufferGeometry.kt @@ -34,7 +34,10 @@ external interface TextGeometryParameters { set(value) = definedExternally } -external class TextBufferGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) : - ExtrudeBufferGeometry { +external class TextBufferGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) : ExtrudeBufferGeometry { + val parameters: TextGeometryParameters +} + +external class TextGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) : ExtrudeGeometry { val parameters: TextGeometryParameters } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TorusGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TorusGeometry.kt index 0a53525f..7406afb3 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TorusGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TorusGeometry.kt @@ -4,7 +4,7 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry - +import info.laht.threekt.core.Geometry external class TorusGeometry( radius: Number = definedExternally, @@ -12,4 +12,12 @@ external class TorusGeometry( radialSegments: Int = definedExternally, tubularSegments: Int = definedExternally, arc: Number = definedExternally +) : Geometry + +external class TorusBufferGeometry( + radius: Number = definedExternally, + tube: Number = definedExternally, + radialSegments: Int = definedExternally, + tubularSegments: Int = definedExternally, + arc: Number = definedExternally ) : BufferGeometry \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TubeGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TubeGeometry.kt index 36afa6ae..38bc560a 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TubeGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/TubeGeometry.kt @@ -1,10 +1,10 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry import info.laht.threekt.extras.core.Curve import info.laht.threekt.math.Vector3 - /** * Creates a tube that extrudes along a 3d curve. */ @@ -16,6 +16,25 @@ external class TubeGeometry( radiusSegments: Int = definedExternally, closed: Boolean = definedExternally +) : Geometry { + + var tangents: Array + var normals: Array + var binormals: Array + +} + +/** + * Creates a tube that extrudes along a 3d curve. + */ +external class TubeBufferGeometry( + + path: Curve, + tubularSegments: Int = definedExternally, + radius: Number = definedExternally, + radiusSegments: Int = definedExternally, + closed: Boolean = definedExternally + ) : BufferGeometry { val parameters: dynamic diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/WireframeGeometry.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/WireframeGeometry.kt index f52c61cc..4c0a72ee 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/WireframeGeometry.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/geometries/WireframeGeometry.kt @@ -4,8 +4,14 @@ package info.laht.threekt.geometries import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry /** * This can be used as a helper object to view a Geometry object as a wireframe. */ -external class WireframeGeometry(geometry: BufferGeometry) : BufferGeometry \ No newline at end of file +external class WireframeGeometry : BufferGeometry { + + constructor(geometry: Geometry) + constructor(geometry: BufferGeometry) + +} \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/LineSegments.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/LineSegments.kt index 03bef34e..b5a83ff0 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/LineSegments.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/LineSegments.kt @@ -28,10 +28,12 @@ package info.laht.threekt.objects import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry import info.laht.threekt.core.Object3D import info.laht.threekt.materials.Material open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D { + constructor(geometry: Geometry, material: Material) var geometry: BufferGeometry var material: Material diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/Mesh.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/Mesh.kt index fc91e333..78dcb9f6 100644 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/Mesh.kt +++ b/visionforge-threejs/src/main/kotlin/info/laht/threekt/objects/Mesh.kt @@ -27,16 +27,16 @@ package info.laht.threekt.objects -import info.laht.threekt.core.BufferGeometry -import info.laht.threekt.core.Intersect -import info.laht.threekt.core.Object3D -import info.laht.threekt.core.Raycaster +import info.laht.threekt.core.* import info.laht.threekt.materials.Material -open external class Mesh(geometry: BufferGeometry?, material: Material?) : Object3D { +open external class Mesh : Object3D { - var geometry: BufferGeometry + constructor(geometry: Geometry?, material: Material?) + constructor(geometry: BufferGeometry?, material: Material?) + + var geometry: dynamic var material: Material var drawMode: Int diff --git a/visionforge-threejs/src/main/kotlin/info/laht/threekt/utils/BufferGeometryUtils.kt b/visionforge-threejs/src/main/kotlin/info/laht/threekt/utils/BufferGeometryUtils.kt deleted file mode 100644 index dd15b514..00000000 --- a/visionforge-threejs/src/main/kotlin/info/laht/threekt/utils/BufferGeometryUtils.kt +++ /dev/null @@ -1,15 +0,0 @@ -@file:JsModule("three/examples/jsm/utils/BufferGeometryUtils") -@file:JsNonModule -package info.laht.threekt.utils - -import info.laht.threekt.core.BufferGeometry - - -public external object BufferGeometryUtils { - /** - * Merges a set of geometries into a single instance. All geometries must have compatible attributes. If merge does not succeed, the method returns null. - * @param geometries -- Array of BufferGeometry instances. - * @param useGroups -- Whether groups should be generated for the merged geometry or not. - */ - public fun mergeBufferGeometries(geometries: Array, useGroups: Boolean): BufferGeometry -} \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt index 7cbd4d3d..b01dbd1a 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/MeshThreeFactory.kt @@ -30,14 +30,16 @@ public abstract class MeshThreeFactory( override fun invoke(three: ThreePlugin, obj: T): Mesh { val geometry = buildGeometry(obj) + //JS sometimes tries to pass Geometry as BufferGeometry + @Suppress("USELESS_IS_CHECK") if (geometry !is BufferGeometry) error("BufferGeometry expected") + //val meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply { matrixAutoUpdate = false //set position for mesh updatePosition(obj) - applyProperties(obj) - } + }.applyProperties(obj) //add listener to object properties obj.onPropertyChange(three.updateScope) { name -> @@ -74,9 +76,9 @@ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply { updateMaterial(obj) applyEdges(obj) //applyWireFrame(obj) - layers.set(obj.layer) + layers.enable(obj.layer) children.forEach { - it.layers.set(obj.layer) + it.layers.enable(obj.layer) } } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt index a5b50688..dad57066 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeBoxFactory.kt @@ -1,12 +1,12 @@ package space.kscience.visionforge.solid.three -import info.laht.threekt.geometries.BoxGeometry +import info.laht.threekt.geometries.BoxBufferGeometry import space.kscience.visionforge.solid.Box import space.kscience.visionforge.solid.detail public object ThreeBoxFactory : MeshThreeFactory(Box::class) { - override fun buildGeometry(obj: Box): BoxGeometry = + override fun buildGeometry(obj: Box): BoxBufferGeometry = obj.detail?.let { detail -> - BoxGeometry(obj.xSize, obj.ySize, obj.zSize, detail, detail, detail) - } ?: BoxGeometry(obj.xSize, obj.ySize, obj.zSize) + BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize, detail, detail, detail) + } ?: BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize) } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt index 3afb6776..7dd89a32 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvas.kt @@ -250,11 +250,8 @@ public class ThreeCanvas( } public fun render(vision: Solid) { - if (root != null) { - three.logger.info { "Replacing root node in three canvas" } - scene.findChild("@root".asName())?.let { scene.remove(it) } - root?.dispose() - } + three.logger.info { "Replacing root node in three canvas" } + scene.findChild("@root".asName())?.let { scene.remove(it) } val object3D = three.buildObject3D(vision) object3D.name = "@root" diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt index 4855abcd..bbce8f19 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCanvasLabelFactory.kt @@ -2,7 +2,7 @@ package space.kscience.visionforge.solid.three import info.laht.threekt.DoubleSide import info.laht.threekt.core.Object3D -import info.laht.threekt.geometries.PlaneGeometry +import info.laht.threekt.geometries.PlaneBufferGeometry import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.objects.Mesh import info.laht.threekt.textures.Texture @@ -46,7 +46,7 @@ public object ThreeCanvasLabelFactory : ThreeFactory { } val mesh = Mesh( - PlaneGeometry(canvas.width, canvas.height), + PlaneBufferGeometry(canvas.width, canvas.height), material ) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt index 2cdd12d3..1f8c9328 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeCompositeFactory.kt @@ -1,60 +1,29 @@ package space.kscience.visionforge.solid.three import CSG -import info.laht.threekt.core.Object3D +import info.laht.threekt.core.BufferGeometry import info.laht.threekt.objects.Mesh -import space.kscience.dataforge.names.startsWith -import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.solid.Composite import space.kscience.visionforge.solid.CompositeType -import kotlin.reflect.KClass /** * This should be inner, because it uses object builder */ -public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory { +public class ThreeCompositeFactory(public val three: ThreePlugin) : MeshThreeFactory(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") -// //first.updateMatrix() -// val second = three.buildObject3D(obj.second) as? Mesh ?: error("Second part of composite is not a mesh") -// //second.updateMatrix() -// val firstCSG = CSG.fromMesh(first) -// val secondCSG = CSG.fromMesh(second) -//// val resultCSG = when (obj.compositeType) { -//// CompositeType.UNION -> firstCSG.union(secondCSG) -//// CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) -//// CompositeType.SUBTRACT -> firstCSG.subtract(secondCSG) -//// } -//// return resultCSG.toGeometry(second.matrix) -// -// val resultMesh: Mesh = when (obj.compositeType) { -// CompositeType.UNION -> CSG.union(first,second) -// CompositeType.INTERSECT -> CSG.intersect(first,second) -// CompositeType.SUBTRACT -> CSG.subtract(first,second) -// } -// return resultMesh.geometry -// } - - override val type: KClass get() = Composite::class - - override fun invoke(three: ThreePlugin, obj: Composite): Object3D { + override fun buildGeometry(obj: Composite): BufferGeometry { val first = three.buildObject3D(obj.first) as? Mesh ?: error("First part of composite is not a mesh") + first.updateMatrix() val second = three.buildObject3D(obj.second) as? Mesh ?: error("Second part of composite is not a mesh") - return when (obj.compositeType) { - CompositeType.SUM, CompositeType.UNION -> CSG.union(first, second) - CompositeType.INTERSECT -> CSG.intersect(first, second) - CompositeType.SUBTRACT -> CSG.subtract(first, second) - }.apply { - updatePosition(obj) - applyProperties(obj) - obj.onPropertyChange(three.updateScope) { name -> - when { - //name.startsWith(WIREFRAME_KEY) -> mesh.applyWireFrame(obj) - name.startsWith(MeshThreeFactory.EDGES_KEY) -> applyEdges(obj) - else -> updateProperty(obj, name) - } - } + second.updateMatrix() + val firstCSG = CSG.fromMesh(first) + val secondCSG = CSG.fromMesh(second) + val resultCSG = when (obj.compositeType) { + CompositeType.UNION -> firstCSG.union(secondCSG) + CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) + CompositeType.SUBTRACT -> firstCSG.subtract(secondCSG) } + return resultCSG.toGeometry().toBufferGeometry() } + } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt index 5789c30e..c8eece9a 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeConeFactory.kt @@ -1,7 +1,7 @@ package space.kscience.visionforge.solid.three import info.laht.threekt.core.BufferGeometry -import info.laht.threekt.geometries.CylinderGeometry +import info.laht.threekt.geometries.CylinderBufferGeometry import space.kscience.visionforge.solid.ConeSegment import space.kscience.visionforge.solid.detail import kotlin.math.PI @@ -11,7 +11,7 @@ public object ThreeConeFactory : MeshThreeFactory(ConeSegment::clas override fun buildGeometry(obj: ConeSegment): BufferGeometry { val cylinder = obj.detail?.let { val segments = it.toDouble().pow(0.5).toInt() - CylinderGeometry( + CylinderBufferGeometry( radiusTop = obj.topRadius, radiusBottom = obj.bottomRadius, height = obj.height, @@ -21,7 +21,7 @@ public object ThreeConeFactory : MeshThreeFactory(ConeSegment::clas thetaStart = obj.startAngle, thetaLength = obj.angle ) - } ?: CylinderGeometry( + } ?: CylinderBufferGeometry( radiusTop = obj.topRadius, radiusBottom = obj.bottomRadius, height = obj.height, diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt index c62abd77..e231c4ce 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeFactory.kt @@ -63,7 +63,9 @@ public fun Object3D.updateProperty(source: Vision, propertyName: Name) { * Generic factory for elements which provide inside geometry builder */ public object ThreeShapeFactory : MeshThreeFactory(GeometrySolid::class) { - override fun buildGeometry(obj: GeometrySolid): BufferGeometry = ThreeGeometryBuilder().apply { - obj.toGeometry(this) - }.build() + override fun buildGeometry(obj: GeometrySolid): BufferGeometry { + return obj.run { + ThreeGeometryBuilder().apply { toGeometry(this) }.build() + } + } } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt index c1c5aa72..c1568ffd 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeGeometryBuilder.kt @@ -1,63 +1,52 @@ package space.kscience.visionforge.solid.three import info.laht.threekt.core.BufferGeometry -import info.laht.threekt.core.Float32BufferAttribute +import info.laht.threekt.core.Face3 +import info.laht.threekt.core.Geometry import info.laht.threekt.math.Vector3 import space.kscience.dataforge.meta.Meta +import space.kscience.dataforge.meta.get +import space.kscience.dataforge.meta.int import space.kscience.visionforge.solid.GeometryBuilder import space.kscience.visionforge.solid.Point3D -import space.kscience.visionforge.solid.cross -import space.kscience.visionforge.solid.minus internal fun Point3D.toVector() = Vector3(x, y, z) -internal fun MutableList.add(vararg values: T) { - values.forEach { - add(it) - } -} - /** * An implementation of geometry builder for Three.js [BufferGeometry] */ public class ThreeGeometryBuilder : GeometryBuilder { - private val indices = ArrayList() - private val positions = ArrayList() - private val normals = ArrayList() -// private val colors = ArrayList() + private val vertices = ArrayList() + private val faces = ArrayList() - private val vertexCache = HashMap() - private var counter: Short = -1 + private val vertexCache = HashMap() - private fun vertex(vertex: Point3D, normal: Point3D): Short = vertexCache.getOrPut(vertex) { - //add vertex and update cache if needed - positions.add(vertex.x, vertex.y, vertex.z) - normals.add(normal.x, vertex.y, vertex.z) - //colors.add(1f, 1f, 1f) - counter++ - counter + private fun append(vertex: Point3D): Int { + val index = vertexCache[vertex] ?: -1//vertices.indexOf(vertex) + return if (index > 0) { + index + } else { + vertices.add(vertex.toVector()) + vertexCache[vertex] = vertices.size - 1 + vertices.size - 1 + } } override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) { - val actualNormal: Point3D = normal ?: (vertex3 - vertex2) cross (vertex1 - vertex2) - indices.add( - vertex(vertex1, actualNormal), - vertex(vertex2, actualNormal), - vertex(vertex3, actualNormal) - ) + val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal?.toVector() ?: Vector3(0, 0, 0)) + meta["materialIndex"].int?.let { face.materialIndex = it } + meta["color"]?.getColor()?.let { face.color = it } + faces.add(face) } - override fun build(): BufferGeometry = BufferGeometry().apply { - setIndex(indices.toTypedArray()) - setAttribute("position", Float32BufferAttribute(positions.toTypedArray(), 3)) - setAttribute("normal", Float32BufferAttribute(normals.toTypedArray(), 3)) - //setAttribute("color", Float32BufferAttribute(colors.toFloatArray(), 3)) - //a temporary fix for CSG problem - val uvsArray = Array((counter+1)*2){0f} - setAttribute("uv", Float32BufferAttribute(uvsArray, 2)) - - computeBoundingSphere() + override fun build(): BufferGeometry { + return Geometry().apply { + vertices = this@ThreeGeometryBuilder.vertices.toTypedArray() + faces = this@ThreeGeometryBuilder.faces.toTypedArray() + computeBoundingSphere() + computeFaceNormals() + }.toBufferGeometry() } } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt index b2858fdb..cc9c58ce 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeLineFactory.kt @@ -1,6 +1,6 @@ package space.kscience.visionforge.solid.three -import info.laht.threekt.core.BufferGeometry +import info.laht.threekt.core.Geometry import info.laht.threekt.core.Object3D import info.laht.threekt.math.Color import info.laht.threekt.objects.LineSegments @@ -16,8 +16,8 @@ public object ThreeLineFactory : ThreeFactory { override val type: KClass get() = PolyLine::class override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D { - val geometry = BufferGeometry().apply { - setFromPoints(Array(obj.points.size) { obj.points[it].toVector() }) + val geometry = Geometry().apply { + vertices = Array(obj.points.size) { obj.points[it].toVector() } } val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true) diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt index e6d85dc5..68fa692a 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreePlugin.kt @@ -49,7 +49,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer { } public fun buildObject3D(obj: Solid): Object3D = when (obj) { - is ThreeJsVision -> obj.render(this) + is ThreeVision -> obj.render(this) is SolidReferenceGroup -> ThreeReferenceFactory(this, obj) is SolidGroup -> { val group = ThreeGroup() diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt index 8932a6c8..8f117516 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeSphereFactory.kt @@ -1,14 +1,14 @@ package space.kscience.visionforge.solid.three import info.laht.threekt.core.BufferGeometry -import info.laht.threekt.geometries.SphereGeometry +import info.laht.threekt.geometries.SphereBufferGeometry import space.kscience.visionforge.solid.Sphere import space.kscience.visionforge.solid.detail public object ThreeSphereFactory : MeshThreeFactory(Sphere::class) { override fun buildGeometry(obj: Sphere): BufferGeometry { return obj.detail?.let {detail -> - SphereGeometry( + SphereBufferGeometry( radius = obj.radius, phiStart = obj.phiStart, phiLength = obj.phi, @@ -17,7 +17,7 @@ public object ThreeSphereFactory : MeshThreeFactory(Sphere::class) { widthSegments = detail, heightSegments = detail ) - }?: SphereGeometry( + }?: SphereBufferGeometry( radius = obj.radius, phiStart = obj.phiStart, phiLength = obj.phi, diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeVision.kt similarity index 83% rename from visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt rename to visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeVision.kt index 27a5da44..16d1bd4f 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeJsVision.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/ThreeVision.kt @@ -6,6 +6,6 @@ import space.kscience.visionforge.solid.SolidBase /** * A custom visual object that has its own Three.js renderer */ -public abstract class ThreeJsVision : SolidBase() { +public abstract class ThreeVision : SolidBase() { public abstract fun render(three: ThreePlugin): Object3D } diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt index 50e48ee9..af99f66e 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/csg.kt @@ -1,52 +1,49 @@ -@file:Suppress( - "INTERFACE_WITH_SUPERCLASS", +@file:Suppress("INTERFACE_WITH_SUPERCLASS", "OVERRIDING_FINAL_MEMBER", "RETURN_TYPE_MISMATCH_ON_OVERRIDE", "CONFLICTING_OVERLOADS", - "EXTERNAL_DELEGATION" -) + "EXTERNAL_DELEGATION") @file:JsModule("three-csg-ts") @file:JsNonModule -import info.laht.threekt.core.BufferGeometry import info.laht.threekt.math.Matrix4 import info.laht.threekt.math.Vector3 import info.laht.threekt.objects.Mesh -public external class CSG { - public fun clone(): CSG - public fun toPolygons(): Array - public fun toGeometry(toMatrix: Matrix4): BufferGeometry - public fun union(csg: CSG): CSG - public fun subtract(csg: CSG): CSG - public fun intersect(csg: CSG): CSG - public fun inverse(): CSG +external open class CSG { + open fun clone(): CSG + open fun toPolygons(): Array + open fun union(csg: CSG): CSG + open fun subtract(csg: CSG): CSG + open fun intersect(csg: CSG): CSG + open fun inverse(): CSG - - public companion object { + companion object { fun fromPolygons(polygons: Array): CSG - fun fromGeometry(geom: BufferGeometry, objectIndex: dynamic = definedExternally): CSG - fun fromMesh(mesh: Mesh, objectIndex: dynamic = definedExternally): CSG - fun toGeometry(csg: CSG, toMatrix: Matrix4): BufferGeometry + fun fromGeometry(geom: Any): CSG + fun fromMesh(mesh: Mesh): CSG fun toMesh(csg: CSG, toMatrix: Matrix4): Mesh fun iEval(tokens: Mesh, index: Number? = definedExternally) fun eval(tokens: Mesh, doRemove: Boolean): Mesh - fun union(meshA: Mesh, meshB: Mesh): Mesh - fun subtract(meshA: Mesh, meshB: Mesh): Mesh - fun intersect(meshA: Mesh, meshB: Mesh): Mesh + var _tmpm3: Any + var doRemove: Any + var currentOp: Any + var currentPrim: Any + var nextPrim: Any + var sourceMesh: Any } } -external class Vector(x: Number, y: Number, z: Number) : Vector3 { - fun negated(): Vector - fun plus(a: Vector): Vector - fun minus(a: Vector): Vector - fun times(a: Number): Vector - fun dividedBy(a: Number): Vector - fun lerp(a: Vector, t: Number): Any - fun unit(): Vector - fun cross(a: Vector): Any +external open class Vector(x: Number, y: Number, z: Number) : Vector3 { + open fun negated(): Vector + open fun plus(a: Vector): Vector + open fun minus(a: Vector): Vector + open fun times(a: Number): Vector + open fun dividedBy(a: Number): Vector + open fun lerp(a: Vector, t: Number): Any + open fun unit(): Vector + open fun cross(a: Vector): Any } external interface IVector { @@ -55,21 +52,21 @@ external interface IVector { var z: Number } -external class Vertex(pos: IVector, normal: IVector, uv: IVector? = definedExternally) { - var pos: Vector - var normal: Vector - var uv: Vector - fun clone(): Vertex - fun flip() - fun interpolate(other: Vertex, t: Number): Vertex +external open class Vertex(pos: IVector, normal: IVector, uv: IVector? = definedExternally) { + open var pos: Vector + open var normal: Vector + open var uv: Vector + open fun clone(): Vertex + open fun flip() + open fun interpolate(other: Vertex, t: Number): Vertex } -external class Plane(normal: Vector, w: Number) { - var normal: Vector - var w: Number - fun clone(): Plane - fun flip() - fun splitPolygon( +external open class Plane(normal: Vector, w: Number) { + open var normal: Vector + open var w: Number + open fun clone(): Plane + open fun flip() + open fun splitPolygon( polygon: Polygon, coplanarFront: Array, coplanarBack: Array, @@ -83,10 +80,10 @@ external class Plane(normal: Vector, w: Number) { } } -external class Polygon(vertices: Array, shared: Any? = definedExternally) { - var plane: Plane - var vertices: Array - var shared: Any - fun clone(): Polygon - fun flip() +external open class Polygon(vertices: Array, shared: Any? = definedExternally) { + open var plane: Plane + open var vertices: Array + open var shared: Any + open fun clone(): Polygon + open fun flip() } \ No newline at end of file diff --git a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt index 9e78800b..fa13e91d 100644 --- a/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt +++ b/visionforge-threejs/src/main/kotlin/space/kscience/visionforge/solid/three/three.kt @@ -1,7 +1,7 @@ package space.kscience.visionforge.solid.three -import info.laht.threekt.core.BufferGeometry -import info.laht.threekt.core.Layers +import CSG +import info.laht.threekt.core.* import info.laht.threekt.external.controls.OrbitControls import info.laht.threekt.materials.Material import info.laht.threekt.math.Euler @@ -19,13 +19,53 @@ public val Solid.euler: Euler get() = Euler(rotationX, rotationY, rotationZ, rot public val MetaItem.vector: Vector3 get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f) +public fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) } internal fun Double.toRadians() = this * PI / 180 +public fun CSG.toGeometry(): Geometry { + val geom = Geometry() + + val vertices = ArrayList() + val faces = ArrayList() + + for (polygon in toPolygons()) { + val v0 = vertices.size + val pvs = polygon.vertices + + for (pv in pvs) { + vertices.add(Vector3().copy(pv.pos)) + } + + for (j in 3..polygon.vertices.size) { + val fc = Face3(v0, v0 + j - 2, v0 + j - 1, Vector3()) + fc.vertexNormals = arrayOf( + Vector3().copy(pvs[0].normal), + Vector3().copy(pvs[j - 2].normal), + Vector3().copy(pvs[j - 1].normal) + ) + + fc.normal = Vector3().copy(polygon.plane.normal) + faces.add(fc) + } + } + geom.vertices = vertices.toTypedArray() + geom.faces = faces.toTypedArray() +// val inv: Matrix4 = Matrix4().apply { getInverse(toMatrix) } +// geom.applyMatrix(toMatrix) + geom.verticesNeedUpdate = true + geom.elementsNeedUpdate = true + geom.normalsNeedUpdate = true + geom.computeBoundingSphere() + 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()