Merge pull request #4 from kiruma524/tutorial

Smth
This commit is contained in:
kiruma524 2021-08-05 16:41:48 +03:00 committed by GitHub
commit 0dafbb2a34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 1465 additions and 1446 deletions

View File

@ -19,6 +19,11 @@
- VisionGroup builder accepts `null` as name for statics instead of `""` - VisionGroup builder accepts `null` as name for statics instead of `""`
- gdml sphere is rendered as a SphereLayer instead of Sphere (#35) - gdml sphere is rendered as a SphereLayer instead of Sphere (#35)
- Tube is replaced by more general ConeSurface - 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 ### Deprecated

View File

@ -20,7 +20,7 @@ allprojects {
} }
group = "space.kscience" group = "space.kscience"
version = "0.2.0-dev-21" version = "0.2.0-dev-22"
} }
subprojects { subprojects {

View File

@ -10,7 +10,7 @@ import kotlin.test.Test
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertNotNull import kotlin.test.assertNotNull
class GDMLVisualTest { class GDMLVisionTest {
// @Test // @Test
// fun testCubesStyles(){ // fun testCubesStyles(){

View File

@ -10,6 +10,7 @@ import space.kscience.dataforge.context.fetch
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.gdml.Gdml import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromString import space.kscience.gdml.decodeFromString
import space.kscience.visionforge.gdml.markLayers
import space.kscience.visionforge.gdml.toVision import space.kscience.visionforge.gdml.toVision
import space.kscience.visionforge.ring.ThreeCanvasWithControls import space.kscience.visionforge.ring.ThreeCanvasWithControls
import space.kscience.visionforge.ring.tab import space.kscience.visionforge.ring.tab
@ -32,7 +33,11 @@ val GDMLApp = functionalComponent<GDMLAppProps>("GDMLApp") { props ->
val parsedVision = when { val parsedVision = when {
name.endsWith(".gdml") || name.endsWith(".xml") -> { name.endsWith(".gdml") || name.endsWith(".xml") -> {
val gdml = Gdml.decodeFromString(data) val gdml = Gdml.decodeFromString(data)
gdml.toVision() gdml.toVision().apply {
root(visionManager)
console.info("Marking layers for file $name")
markLayers()
}
} }
name.endsWith(".json") -> visionManager.decodeFromString(data) name.endsWith(".json") -> visionManager.decodeFromString(data)
else -> { else -> {

View File

@ -1,8 +1,5 @@
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.css.height import kotlinx.css.*
import kotlinx.css.vh
import kotlinx.css.vw
import kotlinx.css.width
import react.child import react.child
import react.dom.render import react.dom.render
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
@ -32,6 +29,8 @@ private class JsPlaygroundApp : Application {
render(element) { render(element) {
styledDiv { styledDiv {
css{ css{
padding(0.pt)
margin(0.pt)
height = 100.vh height = 100.vh
width = 100.vw width = 100.vw
} }

View File

@ -2,10 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>js-playground</title> <title>js-playground</title>
<script src="js-playground.js"></script> <script src="js-playground.js"></script>
</head> </head>
<body> <body class="application">
<div id="playground"></div> <div id="playground"></div>
</body> </body>
</html> </html>

View File

@ -11,7 +11,7 @@ import space.kscience.visionforge.solid.plus
class SC1( class SC1(
val name: String, val name: String,
val center: Point3D, val center: Point3D,
val xSize: Double = PIXEL_XY_SIZE, val ySize: Double = PIXEL_XY_SIZE, val zSize: Double = PIXEL_Z_SIZE val xSize: Float = PIXEL_XY_SIZE, val ySize: Float = PIXEL_XY_SIZE, val zSize: Float = PIXEL_Z_SIZE
) )
class SC16( class SC16(
@ -121,12 +121,12 @@ internal expect fun readMonitorConfig(): String
object Monitor { object Monitor {
const val GEOMETRY_TOLERANCE = 0.01 const val GEOMETRY_TOLERANCE = 0.01
const val PIXEL_XY_SIZE = 122.0 const val PIXEL_XY_SIZE = 122.0f
const val PIXEL_XY_SPACING = 123.2 const val PIXEL_XY_SPACING = 123.2f
const val PIXEL_Z_SIZE = 30.0 const val PIXEL_Z_SIZE = 30.0f
const val CENTRAL_LAYER_Z = 0.0 const val CENTRAL_LAYER_Z = 0.0f
const val UPPER_LAYER_Z = -166.0 const val UPPER_LAYER_Z = -166.0f
const val LOWER_LAYER_Z = 180.0 const val LOWER_LAYER_Z = 180.0f
/** /**
* Build map for the whole monitor * Build map for the whole monitor

View File

@ -17,19 +17,19 @@ import kotlin.random.Random
internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) { internal class SC1Aux(val sc: SC1, var efficiency: Double = 1.0) {
// val layer: Layer = findLayer(center.z); // val layer: Layer = findLayer(center.z);
private val upLayer = private val upLayer =
findLayer(sc.center.z + sc.zSize / 2.0)//Layer("${name}_up", center.z + zSize / 2.0); findLayer(sc.center.z + sc.zSize / 2f)//Layer("${name}_up", center.z + zSize / 2.0);
private val bottomLayer = private val bottomLayer =
findLayer(sc.center.z - sc.zSize / 2.0)//Layer("${name}_bottom", center.z - zSize / 2.0); findLayer(sc.center.z - sc.zSize / 2f)//Layer("${name}_bottom", center.z - zSize / 2.0);
private val centralLayer = findLayer(sc.center.z) private val centralLayer = findLayer(sc.center.z)
private val center = Vector3D(sc.center.x, sc.center.y, sc.center.z) private val center = Vector3D(sc.center.x.toDouble(), sc.center.y.toDouble(), sc.center.z.toDouble())
private val sideLayers: Array<Plane> = arrayOf( private val sideLayers: Array<Plane> = arrayOf(
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.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(-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)), 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(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.0)), Vector3D(0.0, -1.0, 0.0), GEOMETRY_TOLERANCE)
); )
//TODO add efficiency //TODO add efficiency
private fun containsPoint(x: Double, y: Double, z: Double, tolerance: Double = GEOMETRY_TOLERANCE): Boolean { 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 * The layer number from up to bottom
*/ */
fun getLayerNumber(): Int { fun getLayerNumber(): Int {
return when (this.center.z) { return when (this.center.z.toFloat()) {
UPPER_LAYER_Z -> 1; UPPER_LAYER_Z -> 1
CENTRAL_LAYER_Z -> 2; CENTRAL_LAYER_Z -> 2;
LOWER_LAYER_Z -> 3; LOWER_LAYER_Z -> 3;
else -> throw RuntimeException("Unknown layer"); else -> throw RuntimeException("Unknown layer");

View File

@ -45,7 +45,7 @@ fun makeTrack(start: Vector3D, direction: Vector3D): Line {
fun makeTrack(x: Double, y: Double, theta: Double, phi: Double): Line { fun makeTrack(x: Double, y: Double, theta: Double, phi: Double): Line {
//TODO check angle definitions //TODO check angle definitions
return makeTrack( return makeTrack(
Vector3D(x, y, CENTRAL_LAYER_Z), Vector3D(x, y, CENTRAL_LAYER_Z.toDouble()),
Vector3D(phi, theta) Vector3D(phi, theta)
) )
} }

View File

@ -13,12 +13,12 @@ import ru.mipt.npm.muon.monitor.readResource
internal const val MINIMAL_TRACK_LENGTH = 10.0 internal const val MINIMAL_TRACK_LENGTH = 10.0
private val layerCache = HashMap<Double, Plane>() private val layerCache = HashMap<Float, Plane>()
fun findLayer(z: Double): Plane = layerCache.getOrPut(z) { fun findLayer(z: Float): Plane = layerCache.getOrPut(z) {
Plane( Plane(
Vector3D(0.0, 0.0, z), Vector3D(0.0, 0.0, 1.0), Vector3D(0.0, 0.0, z.toDouble()), Vector3D(0.0, 0.0, 1.0),
Monitor.GEOMETRY_TOLERANCE Monitor.GEOMETRY_TOLERANCE.toDouble()
) )
} }

View File

@ -28,8 +28,8 @@ interface TrackGenerator {
*/ */
class UniformTrackGenerator( class UniformTrackGenerator(
override val rnd: RandomGenerator, override val rnd: RandomGenerator,
val maxX: Double = 4 * PIXEL_XY_SIZE, val maxX: Float = 4 * PIXEL_XY_SIZE,
val maxY: Double = 4 * PIXEL_XY_SIZE val maxY: Float = 4 * PIXEL_XY_SIZE
) : ) :
TrackGenerator { TrackGenerator {
override fun generate(): Line { override fun generate(): Line {
@ -44,8 +44,8 @@ class UniformTrackGenerator(
class FixedAngleGenerator( class FixedAngleGenerator(
override val rnd: RandomGenerator, override val rnd: RandomGenerator,
val phi: Double, val theta: Double, val phi: Double, val theta: Double,
val maxX: Double = 4 * PIXEL_XY_SIZE, val maxX: Float = 4 * PIXEL_XY_SIZE,
val maxY: Double = 4 * PIXEL_XY_SIZE val maxY: Float = 4 * PIXEL_XY_SIZE
) : TrackGenerator { ) : TrackGenerator {
override fun generate(): Line { override fun generate(): Line {
val x = (1 - rnd.nextDouble() * 2.0) * maxX val x = (1 - rnd.nextDouble() * 2.0) * maxX
@ -60,8 +60,8 @@ class FixedAngleGenerator(
class Cos2TrackGenerator( class Cos2TrackGenerator(
override val rnd: RandomGenerator, override val rnd: RandomGenerator,
val power: Double = 2.0, val power: Double = 2.0,
val maxX: Double = 4 * PIXEL_XY_SIZE, val maxX: Float = 4 * PIXEL_XY_SIZE,
val maxY: Double = 4 * PIXEL_XY_SIZE val maxY: Float = 4 * PIXEL_XY_SIZE
) : ) :
TrackGenerator { TrackGenerator {
override fun generate(): Line { override fun generate(): Line {

View File

@ -27,7 +27,7 @@ fun VisionLayout<Solid>.demo(name: String, title: String = name, block: SolidGro
} }
val canvasOptions = Canvas3DOptions { val canvasOptions = Canvas3DOptions {
size{ size {
minSize = 400 minSize = 400
} }
axes { axes {
@ -57,9 +57,8 @@ fun VisionLayout<Solid>.showcase() {
rotationX = PI / 4 rotationX = PI / 4
color("blue") color("blue")
} }
sphereLayer(50,40){ sphereLayer(50, 40, theta = PI / 2) {
theta = (PI/2).toFloat() rotationX = -PI * 3 / 4
rotationX = - PI * 3 / 4
z = 110 z = 110
color(Colors.pink) color(Colors.pink)
} }

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.solid.demo package space.kscience.visionforge.solid.demo
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.BoxBufferGeometry import info.laht.threekt.geometries.BoxGeometry
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import space.kscience.dataforge.meta.int import space.kscience.dataforge.meta.int
import space.kscience.dataforge.meta.number import space.kscience.dataforge.meta.number
@ -23,10 +23,10 @@ internal fun SolidGroup.varBox(
action: VariableBox.() -> Unit = {}, action: VariableBox.() -> Unit = {},
): VariableBox = VariableBox(xSize, ySize).apply(action).also { set(name, it) } ): VariableBox = VariableBox(xSize, ySize).apply(action).also { set(name, it) }
internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeVision() { internal class VariableBox(val xSize: Number, val ySize: Number) : ThreeJsVision() {
override fun render(three: ThreePlugin): Object3D { override fun render(three: ThreePlugin): Object3D {
val geometry = BoxBufferGeometry(xSize, ySize, 1) val geometry = BoxGeometry(xSize, ySize, 1)
val material = ThreeMaterials.DEFAULT.clone() val material = ThreeMaterials.DEFAULT.clone()

View File

@ -14,6 +14,7 @@ class FXDemoApp : App(FXDemoGrid::class) {
stage.height = 600.0 stage.height = 600.0
view.showcase() view.showcase()
view.showcaseCSG()
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -1,209 +1,373 @@
#Tutorial # Tutorial
###The main goal of this tutorial is to show all capabilities of ... (this part will be supplemented) #### The main goal of this tutorial is to show main capabilities of the visualization instrument.
The simple visualization can be made with function `main`. (this part will be supplemented as well) The simple visualization can be made with function `main`. (this part will be supplemented)
```kotlin ```kotlin
import kotlinx.html.div import kotlinx.html.div
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
import space.kscience.visionforge.html.ResourceLocation import space.kscience.visionforge.html.ResourceLocation
import space.kscience.visionforge.solid.* import space.kscience.visionforge.solid.*
import java.nio.file.Paths import java.nio.file.Paths
fun main(){ fun main(){
val context = Context{ val context = Context{
plugin(Solids) plugin(Solids)
} }
context.makeVisionFile ( context.makeVisionFile (
Paths.get("customFile.html"), Paths.get("nameFile.html"),
resourceLocation = ResourceLocation.EMBED resourceLocation = ResourceLocation.EMBED
){ ){
div { div {
vision { //first vision
solid { vision {
} solid {
} //solids which you want to visualize
} }
} }
} //second vision
``` vision {
##Solids properties solid {
**We will analyze which basic properties solids have using `box` solid.** //solids which you want to visualize
}
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)` }
4. position, which is given by values `x`, `y`, `z`. Initial values are `x = 0`, `y = 0`, `z = 0` ```
## Solids properties
Let's see how properties are set in solids. **We will analyze which basic properties solids have using `box` solid.**
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.
*Basic properties:*
***You can see that `box` take four values. Later, we will discuss what they are doing in more detail. Now, it does not really matter.*** 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.
```kotlin 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.
box(10, 10, 10, name = "small box"){ 3. `rotation` - it's the point, which set rotations along axes. 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`.
x = 0 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.
y = 0
z = 0 Let's see how properties are set in solids.
opacity = 1 //100% opacity The `small box` will have elemental values of properties. If you don't set properties, it will have the same `position`, `color`, `rotation`, and `opacity` values.
color("red") //as string
rotation = Point3D(0, 0, 0) ***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"){
![](../docs/images/small-box.png) x = 0
y = 0
The `big box` will have properties with custom values. z = 0
```kotlin opacity = 1 //100% opacity
box(40, 40, 40, name = "big box"){ color("red") //as string
x = 20 rotation = Point3D(0, 0, 0)
y = 10 }
z = 60 ```
opacity = 0.5 //50% opacity ![](../docs/images/small-box.png)
color(0u, 179u, 179u) //color in rgb
rotation = Point3D(60, 80, 0) The `big box` will have properties with custom values.
} ```kotlin
``` box(40, 40, 40, name = "big box"){
![](../docs/images/big-rotated-box.png) x = 20
y = 10
If we compare these boxes, we will see all differences. z = 60
opacity = 0.5 //50% opacity
Here is the function `main` with both boxes. color(0u, 179u, 179u) //color in rgb
```kotlin rotation = Point3D(60, 80, 0)
fun main(){ }
val context = Context{ ```
plugin(Solids) ![](../docs/images/big-rotated-box.png)
} If we compare these boxes, we will see all differences.
context.makeVisionFile ( Here is the function `main` with both boxes.
Paths.get("customFile.html"), ```kotlin
resourceLocation = ResourceLocation.EMBED fun main(){
){ val context = Context{
div { plugin(Solids)
vision { }
solid {
box(10, 10, 10, name = "small box"){ context.makeVisionFile (
x = 0 Paths.get("customFile.html"),
y = 0 resourceLocation = ResourceLocation.EMBED
z = 0 ){
opacity = 1 //100% opacity div {
color("red") //as string vision {
rotation = Point3D(0, 0, 0) solid {
} box(10, 10, 10, name = "small box"){
box(40, 40, 40, name = "big box"){ x = 0
x = 20 y = 0
y = 10 z = 0
z = 60 opacity = 1 //100% opacity
opacity = 0.5 //50% opacity color("red") //as string
color(0u, 179u, 179u) //rgb rotation = Point3D(0, 0, 0)
rotation = Point3D(60, 80, 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) }
}
###Basic Solids }
}
Now, let's see which solids can be visualized: }
1) PolyLine ```
2) Box ![](../docs/images/two-boxes-1.png)
```kotlin ![](../docs/images/two-boxes-2.png)
box(50, 50, 50, name = "box") {
x = 0 ***There is plenty of other properties, especially of those, which you can create by yourself. Here we mention just small part.***
y = 0
z = 0 ## Basic Solids
color("pink") Now, let's see which solids can be visualized:
} ### 1) PolyLine
```
![](../docs/images/box.png) It's scarcely a solid, but it can be visualized, so we mention it.
```kotlin `polyline` build lines, obviously. Let's take a look at it's work.
box(10, 25, 10, name = "high_box") {
x = 0 `polyline` requires two values - `points`, and `name`:
y = 0 * `points` is a `vararg` with `Point3D` type. It takes pairs of points, which you want to connect.
z = 0 * `name` is an identifier of *any solid*, but in this case it is an identifier of `polyline`.
color("black") It's type is `String`. **This value can be required by any solid;
} you can set it, you can not to set it, but without you won't be able to control solid, since it won't be inherited.**
```
![](../docs/images/high-box.png) This is an example of polyline with other solid `box`:
```kotlin
```kotlin box(100, 100, 100, name = "box"){
box(65, 40, 40, name = "wide_box") { x = -10
x = 0 y = -10
y = 0 z = -10
z = 0 opacity = 0.4
color("black") }
} polyline(Point3D(30, 20, 10), Point3D(30, -100, 30), Point3D(30, -100, 30), Point3D(50, -100, 30), name = "polyline"){
``` color("red")
![](../docs/images/wide-box.png) }
```
3) Sphere
```kotlin ![](../docs/images/polyline-points.png)
sphere(50, name = "sphere") { ![](../docs/images/polyline-points-2.png)
x = 0
y = 0 ### 2) Box
z = 0
color("blue") 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`
![](../docs/images/sphere.png) * `z` - z-axis length of the `box`
4) Hexagon
```kotlin These values have `Float` type.
hexagon(
Point3D(25, 30, 25), *`x`, `y`, and `z` are necessary values, which cannot be ignored. You have to set them.*
Point3D(35, 30, 25),
Point3D(35, 30, 15), * `name` - `box`'es identifier. You've already met it.
Point3D(25, 30, 15),
Point3D(30, 18, 20), Let's create just usual `box` with equal ribs.
Point3D(40, 18, 20),
Point3D(40, 18, 10), ```kotlin
Point3D(30, 18, 10), box(50, 50, 50, name = "box") {
name = "classic_hexagon"){ color("pink")
color("green") }
} ```
``` ![](../docs/images/box.png)
![](../docs/images/classic-hexagon.png)
```kotlin Now, let's make `box` with bigger `y` value.
hexagon( ```kotlin
Point3D(5, 30, 5), box(10, 25, 10, name = "high box") {
Point3D(24, 30, 8), color("black")
Point3D(20, 30, -10), }
Point3D(5, 30, -7), ```
Point3D(8, 16, 0), As you can see, only rib of `y-axis` differs from other ribs.
Point3D(12, 16, 0),
Point3D(10, 16, -5), ![](../docs/images/high-box.png)
Point3D(6.5, 12, -3),
name = "custom_hexagon"){ For final trial, let's create `box` with bigger `x` value.
color("brown")
} ```kotlin
``` box(65, 40, 40, name = "wide box") {
![](../docs/images/custom-hexagon.png) x = 0
5) Cone y = 0
```kotlin z = 0
cone(60, 80, name = "cone") { color("black")
x = 0 }
y = 0 ```
z = 0 Predictably, only `x-axis` rib bigger than other ribs.
color("beige")
} ![](../docs/images/wide-box.png)
```
![](../docs/images/cone-1.png) ### 3) Sphere
![](../docs/images/cone-2.png)
6) Cone Surface It takes in two values: `radius`, and `name`.
```kotlin We bring you to mind that `name` is a general value for all solids, so do not wonder, since all solids need their own identifier.
coneSurface(60, 50, 30, 10, 100, name = "cone_surface") {
x = 0 As for `radius`, it has `Float` type, and, as you can guess, it sets radius of the sphere, which will be created.
y = 0 ```kotlin
z = 0 sphere(50, name = "sphere") {
color("red") x = 0
rotation = Point3D(2, 50, -9) y = 0
} z = 0
``` opacity = 0.9
![](../docs/images/cone-surface-1.png) color("blue")
![](../docs/images/cone-surface-2.png) }
7) Extruded ```
![](../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
`extruded` is set by two values: `shape`, and `layer`.
* `shape` is a value of `List<Point2D>` type. It' s just a list of all points of the solid. *`shape` has to consist of not less than two points!*
* `layer` is `MutableList` types variable. (here is a sentence with description of the work of this function). *The amount of `layer`-s has to be more than one*

View File

@ -4,9 +4,9 @@ plugins {
description = "Jupyter api artifact for GDML rendering" description = "Jupyter api artifact for GDML rendering"
kotlin{ kotlin {
explicitApi = null explicitApi = null
js{ js {
useCommonJs() useCommonJs()
browser { browser {
webpackTask { webpackTask {
@ -25,19 +25,17 @@ kotlin{
tasks.getByName<ProcessResources>("jvmProcessResources") { tasks.getByName<ProcessResources>("jvmProcessResources") {
dependsOn(jsBrowserDistribution) dependsOn(jsBrowserDistribution)
afterEvaluate { from(jsBrowserDistribution)
from(jsBrowserDistribution)
}
} }
} }
sourceSets{ sourceSets {
commonMain { commonMain {
dependencies { dependencies {
api(project(":visionforge-solid")) api(project(":visionforge-solid"))
} }
} }
jvmMain{ jvmMain {
dependencies { dependencies {
implementation(project(":visionforge-gdml")) implementation(project(":visionforge-gdml"))
} }
@ -52,10 +50,10 @@ kotlin{
} }
} }
kscience{ kscience {
useJupyter() useJupyter()
} }
readme{ readme {
maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL maturity = ru.mipt.npm.gradle.Maturity.EXPERIMENTAL
} }

View File

@ -16,8 +16,6 @@ pluginManagement {
} }
} }
//enableFeaturePreview("GRADLE_METADATA")
rootProject.name = "visionforge" rootProject.name = "visionforge"

View File

@ -22,7 +22,6 @@ dependencies{
implementation(npm("@jetbrains/icons", "3.14.1")) implementation(npm("@jetbrains/icons", "3.14.1"))
implementation(npm("@jetbrains/ring-ui", "4.0.7")) implementation(npm("@jetbrains/ring-ui", "4.0.7"))
implementation(npm("core-js","3.12.1"))
implementation(npm("file-saver", "2.0.2")) implementation(npm("file-saver", "2.0.2"))
compileOnly(npm("url-loader","4.1.1")) compileOnly(npm("url-loader","4.1.1"))
compileOnly(npm("postcss-loader","5.2.0")) compileOnly(npm("postcss-loader","5.2.0"))

View File

@ -28,11 +28,10 @@ internal data class PropertyListener(
*/ */
@Serializable @Serializable
@SerialName("vision") @SerialName("vision")
public open class VisionBase : Vision { public open class VisionBase(
override @Transient var parent: VisionGroup? = null,
protected var properties: Config? = null protected var properties: Config? = null
) : Vision {
@Transient
override var parent: VisionGroup? = null
@Synchronized @Synchronized
protected fun getOrCreateProperties(): Config { protected fun getOrCreateProperties(): Config {
@ -75,9 +74,12 @@ public open class VisionBase : Vision {
} }
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
getOrCreateProperties().setItem(name, item) val oldItem = properties?.getItem(name)
if (notify) { if(oldItem!= item) {
invalidateProperty(name) getOrCreateProperties().setItem(name, item)
if (notify) {
invalidateProperty(name)
}
} }
} }

View File

@ -65,14 +65,14 @@ private fun Vision.isolate(manager: VisionManager): Vision {
} }
/** /**
* @param void flag showing that this vision child should be removed * @param delete flag showing that this vision child should be removed
* @param vision a new value for vision content * @param vision a new value for vision content
* @param properties updated properties * @param properties updated properties
* @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [void] flag to true. * @param children a map of children changed in ths [VisionChange]. If a child to be removed, set [delete] flag to true.
*/ */
@Serializable @Serializable
public data class VisionChange( public data class VisionChange(
public val void: Boolean = false, public val delete: Boolean = false,
public val vision: Vision? = null, public val vision: Vision? = null,
@Serializable(MetaSerializer::class) public val properties: Meta? = null, @Serializable(MetaSerializer::class) public val properties: Meta? = null,
public val children: Map<Name, VisionChange>? = null, public val children: Map<Name, VisionChange>? = null,

View File

@ -58,6 +58,7 @@ public operator fun VisionGroup.iterator(): Iterator<Vision> = children.values.i
public val VisionGroup.isEmpty: Boolean get() = this.children.isEmpty() public val VisionGroup.isEmpty: Boolean get() = this.children.isEmpty()
public interface VisionContainerBuilder<in V : Vision> { public interface VisionContainerBuilder<in V : Vision> {
//TODO add documentation
public operator fun set(name: Name?, child: V?) public operator fun set(name: Name?, child: V?)
} }

View File

@ -17,7 +17,7 @@ import space.kscience.dataforge.names.*
@Serializable @Serializable
@SerialName("vision.group") @SerialName("vision.group")
public open class VisionGroupBase( public open class VisionGroupBase(
@SerialName("children") internal val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap(), @SerialName("children") protected val childrenInternal: MutableMap<NameToken, Vision> = LinkedHashMap(),
) : VisionBase(), MutableVisionGroup { ) : VisionBase(), MutableVisionGroup {
/** /**
@ -134,7 +134,7 @@ public open class VisionGroupBase(
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
change.children?.forEach { (name, change) -> change.children?.forEach { (name, change) ->
when { when {
change.void -> set(name, null) change.delete -> set(name, null)
change.vision != null -> set(name, change.vision) change.vision != null -> set(name, change.vision)
else -> get(name)?.update(change) else -> get(name)?.update(change)
} }

View File

@ -39,6 +39,8 @@ public class VisionManager(meta: Meta) : AbstractPlugin(meta) {
public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string) public fun decodeFromString(string: String): Vision = jsonFormat.decodeFromString(visionSerializer, string)
public fun encodeToString(vision: Vision): String = jsonFormat.encodeToString(visionSerializer, vision) 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) public fun decodeFromJson(json: JsonElement): Vision = jsonFormat.decodeFromJsonElement(visionSerializer, json)

View File

@ -1,12 +1,15 @@
package space.kscience.visionforge package space.kscience.visionforge
import space.kscience.dataforge.meta.Config
import space.kscience.dataforge.meta.MetaItem import space.kscience.dataforge.meta.MetaItem
import space.kscience.dataforge.meta.get
import space.kscience.dataforge.meta.set
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
/** /**
* Property containers are used to create a symmetric behaviors for vision properties and style builders * Property containers are used to create a symmetric behaviors for vision properties and style builders
*/ */
public interface VisionPropertyContainer<out T> { public interface VisionPropertyContainer<out V: Vision> {
public fun getProperty( public fun getProperty(
name: Name, name: Name,
inherit: Boolean = false, inherit: Boolean = false,
@ -15,4 +18,18 @@ public interface VisionPropertyContainer<out T> {
): MetaItem? ): MetaItem?
public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true) public fun setProperty(name: Name, item: MetaItem?, notify: Boolean = true)
}
public open class SimpleVisionPropertyContainer<out V: Vision>(protected val config: Config): VisionPropertyContainer<V>{
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
}
} }

View File

@ -2,6 +2,9 @@ package space.kscience.visionforge
import kotlinx.browser.document import kotlinx.browser.document
import kotlinx.browser.window 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.*
import org.w3c.dom.url.URL import org.w3c.dom.url.URL
import space.kscience.dataforge.context.* import space.kscience.dataforge.context.*
@ -13,14 +16,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_ENDPOINT_ATTRIBUTE
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_FETCH_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_FETCH_ATTRIBUTE
import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE import space.kscience.visionforge.html.VisionTagConsumer.Companion.OUTPUT_NAME_ATTRIBUTE
import kotlin.collections.set
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.time.Duration
public class VisionClient : AbstractPlugin() { public class VisionClient : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
private val visionManager: VisionManager by require(VisionManager) private val visionManager: VisionManager by require(VisionManager)
private val visionMap = HashMap<Element, Vision>() //private val visionMap = HashMap<Element, Vision>()
/** /**
* Up-going tree traversal in search for endpoint attribute * Up-going tree traversal in search for endpoint attribute
@ -53,11 +56,73 @@ public class VisionClient : AbstractPlugin() {
private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null private fun Element.getFlag(attribute: String): Boolean = attributes[attribute]?.value != null
private fun renderVision(element: Element, vision: Vision?, outputMeta: Meta) { private fun renderVision(name: String, element: Element, vision: Vision?, outputMeta: Meta) {
if (vision != null) { if (vision != null) {
visionMap[element] = vision
val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision") val renderer = findRendererFor(vision) ?: error("Could nof find renderer for $vision")
renderer.render(element, vision, outputMeta) 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'")
}
}
}
} }
} }
@ -79,85 +144,39 @@ public class VisionClient : AbstractPlugin() {
visionManager.decodeFromString(it) visionManager.decodeFromString(it)
} }
if (embeddedVision != null) { when {
logger.info { "Found embedded vision for output with name $name" } embeddedVision != null -> {
renderVision(element, embeddedVision, outputMeta) logger.info { "Found embedded vision for output with name $name" }
} renderVision(name, element, embeddedVision, outputMeta)
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)
} }
element.attributes[OUTPUT_FETCH_ATTRIBUTE] != null -> {
val attr = element.attributes[OUTPUT_FETCH_ATTRIBUTE]!!
logger.info { "Fetching vision data from $fetchUrl" } val fetchUrl = if (attr.value.isBlank() || attr.value == "auto") {
window.fetch(fetchUrl).then { response -> val endpoint = resolveEndpoint(element)
if (response.ok) { logger.info { "Vision server is resolved to $endpoint" }
response.text().then { text -> URL(endpoint).apply {
val vision = visionManager.decodeFromString(text) pathname += "/vision"
renderVision(element, vision, outputMeta)
} }
} else { } else {
logger.error { "Failed to fetch initial vision state from $fetchUrl" } URL(attr.value)
}.apply {
searchParams.append("name", name)
} }
} logger.info { "Fetching vision data from $fetchUrl" }
} window.fetch(fetchUrl).then { response ->
if (response.ok) {
element.attributes[OUTPUT_CONNECT_ATTRIBUTE]?.let { attr -> response.text().then { text ->
val wsUrl = if (attr.value.isBlank() || attr.value == "auto") { val vision = visionManager.decodeFromString(text)
val endpoint = resolveEndpoint(element) renderVision(name, element, vision, outputMeta)
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 { } else {
console.error("WebSocket message data is not a string") logger.error { "Failed to fetch initial vision state from $fetchUrl" }
} }
} }
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")
} }
} }

View File

@ -22,7 +22,7 @@ dependencies {
exclude(group = "org.openjfx") exclude(group = "org.openjfx")
} }
api("org.fxyz3d:fxyz3d:0.5.2") { api("org.fxyz3d:fxyz3d:0.5.4") {
exclude(module = "slf4j-simple") exclude(module = "slf4j-simple")
} }
api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}") api("org.jetbrains.kotlinx:kotlinx-coroutines-javafx:${ru.mipt.npm.gradle.KScienceVersions.coroutinesVersion}")

View File

@ -15,7 +15,7 @@ import space.kscience.dataforge.names.asName
import space.kscience.dataforge.values.* import space.kscience.dataforge.values.*
import tornadofx.* import tornadofx.*
class TextValueChooser : ValueChooserBase<TextField>() { public class TextValueChooser : ValueChooserBase<TextField>() {
private val displayText: String private val displayText: String
get() = currentValue().let { get() = currentValue().let {

View File

@ -14,7 +14,6 @@ import space.kscience.dataforge.meta.descriptors.ValueDescriptor
import space.kscience.dataforge.misc.Named import space.kscience.dataforge.misc.Named
import space.kscience.dataforge.misc.Type import space.kscience.dataforge.misc.Type
import space.kscience.dataforge.names.toName import space.kscience.dataforge.names.toName
import space.kscience.dataforge.provider.provideByType
import space.kscience.dataforge.values.Null import space.kscience.dataforge.values.Null
import space.kscience.dataforge.values.Value import space.kscience.dataforge.values.Value
import space.kscience.visionforge.widget import space.kscience.visionforge.widget
@ -63,7 +62,7 @@ public interface ValueChooser {
public fun setCallback(callback: ValueCallback) public fun setCallback(callback: ValueCallback)
@Type("space.kscience.dataforge.vis.fx.valueChooserFactory") @Type("space.kscience..fx.valueChooserFactory")
public interface Factory : Named { public interface Factory : Named {
public operator fun invoke(meta: Meta = Meta.EMPTY): ValueChooser public operator fun invoke(meta: Meta = Meta.EMPTY): ValueChooser
} }
@ -75,7 +74,7 @@ public interface ValueChooser {
TextValueChooser.name -> TextValueChooser TextValueChooser.name -> TextValueChooser
ColorValueChooser.name -> ColorValueChooser ColorValueChooser.name -> ColorValueChooser
ComboBoxValueChooser.name -> ComboBoxValueChooser ComboBoxValueChooser.name -> ComboBoxValueChooser
else -> context.provideByType(type)//Search for additional factories in the plugin else -> null//context.provideByType(type)//Search for additional factories in the plugin
} }
} }
@ -101,7 +100,7 @@ public interface ValueChooser {
} }
} }
fun build( public fun build(
context: Context, context: Context,
value: ObservableValue<Value?>, value: ObservableValue<Value?>,
descriptor: ValueDescriptor? = null, descriptor: ValueDescriptor? = null,

View File

@ -18,16 +18,18 @@ import tornadofx.*
* *
* @author Alexander Nozik * @author Alexander Nozik
*/ */
abstract class ValueChooserBase<out T : Node> : ValueChooser { public abstract class ValueChooserBase<out T : Node> : ValueChooser {
override val node by lazy { buildNode() } override val node: T by lazy { buildNode() }
final override val valueProperty = SimpleObjectProperty<Value>(Null) final override val valueProperty: SimpleObjectProperty<Value> =
final override val descriptorProperty = SimpleObjectProperty<ValueDescriptor>() SimpleObjectProperty<Value>(Null)
final override val descriptorProperty: SimpleObjectProperty<ValueDescriptor> =
SimpleObjectProperty<ValueDescriptor>()
override var descriptor: ValueDescriptor? by descriptorProperty override var descriptor: ValueDescriptor? by descriptorProperty
override var value: Value? by valueProperty override var value: Value? by valueProperty
fun resetValue() { public fun resetValue() {
setDisplayValue(currentValue()) setDisplayValue(currentValue())
} }

View File

@ -13,13 +13,13 @@ import space.kscience.dataforge.meta.update
import space.kscience.visionforge.* import space.kscience.visionforge.*
import tornadofx.* import tornadofx.*
class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() { public class VisualObjectEditorFragment(public val selector: (Vision) -> Meta) : Fragment() {
val itemProperty = SimpleObjectProperty<Vision>() public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
var item: Vision? by itemProperty public var item: Vision? by itemProperty
val descriptorProperty = SimpleObjectProperty<NodeDescriptor>() public val descriptorProperty: SimpleObjectProperty<NodeDescriptor> = SimpleObjectProperty<NodeDescriptor>()
constructor( public constructor(
item: Vision?, item: Vision?,
descriptor: NodeDescriptor?, descriptor: NodeDescriptor?,
selector: (Vision) -> MutableItemProvider = { it.allProperties() }, selector: (Vision) -> MutableItemProvider = { it.allProperties() },
@ -30,13 +30,13 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
private var currentConfig: Config? = null private var currentConfig: Config? = null
private val configProperty: Binding<Config?> = itemProperty.objectBinding { visualObject -> private val configProperty: Binding<Config?> = itemProperty.objectBinding { vision ->
if (visualObject == null) return@objectBinding null if (vision == null) return@objectBinding null
val meta = selector(visualObject) val meta = selector(vision)
val config = Config().apply { val config = Config().apply {
update(meta) update(meta)
onChange(this@VisualObjectEditorFragment) { key, _, after -> onChange(this@VisualObjectEditorFragment) { key, _, after ->
visualObject.setProperty(key, after) vision.setProperty(key, after)
} }
} }
//remember old config reference to cleanup listeners //remember old config reference to cleanup listeners
@ -51,7 +51,7 @@ class VisualObjectEditorFragment(val selector: (Vision) -> Meta) : Fragment() {
} }
} }
private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding() { private val styleBoxProperty: Binding<Node?> = configProperty.objectBinding {
VBox().apply { VBox().apply {
item?.styles?.forEach { styleName -> item?.styles?.forEach { styleName ->
val styleMeta = item?.getStyle(styleName) val styleMeta = item?.getStyle(styleName)

View File

@ -3,6 +3,7 @@ package space.kscience.visionforge.editor
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.scene.control.SelectionMode import javafx.scene.control.SelectionMode
import javafx.scene.control.TreeItem import javafx.scene.control.TreeItem
import javafx.scene.layout.VBox
import space.kscience.visionforge.Vision import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup import space.kscience.visionforge.VisionGroup
import tornadofx.* import tornadofx.*
@ -29,13 +30,13 @@ private fun toTreeItem(vision: Vision, title: String): TreeItem<Pair<String, Vis
} }
class VisualObjectTreeFragment : Fragment() { public class VisualObjectTreeFragment : Fragment() {
val itemProperty = SimpleObjectProperty<Vision>() public val itemProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
var item: Vision? by itemProperty public var item: Vision? by itemProperty
val selectedProperty = SimpleObjectProperty<Vision>() public val selectedProperty: SimpleObjectProperty<Vision> = SimpleObjectProperty<Vision>()
override val root = vbox { override val root: VBox = vbox {
titledpane("Object tree", collapsible = false) { titledpane("Object tree", collapsible = false) {
treeview<Pair<String, Vision>> { treeview<Pair<String, Vision>> {
cellFormat { cellFormat {
@ -47,7 +48,9 @@ class VisualObjectTreeFragment : Fragment() {
} }
} }
selectionModel.selectionMode = SelectionMode.SINGLE selectionModel.selectionMode = SelectionMode.SINGLE
val selectedValue = selectionModel.selectedItemProperty().objectBinding { it?.value?.second } val selectedValue = selectionModel.selectedItemProperty().objectBinding {
it?.value?.second
}
selectedProperty.bind(selectedValue) selectedProperty.bind(selectedValue)
} }
} }

View File

@ -23,7 +23,7 @@ import kotlin.collections.set
import kotlin.math.PI import kotlin.math.PI
import kotlin.reflect.KClass import kotlin.reflect.KClass
class FX3DPlugin : AbstractPlugin() { public class FX3DPlugin : AbstractPlugin() {
override val tag: PluginTag get() = Companion.tag override val tag: PluginTag get() = Companion.tag
private val objectFactories = HashMap<KClass<out Solid>, FX3DFactory<*>>() private val objectFactories = HashMap<KClass<out Solid>, FX3DFactory<*>>()
@ -42,7 +42,7 @@ class FX3DPlugin : AbstractPlugin() {
as FX3DFactory<Solid>? as FX3DFactory<Solid>?
} }
fun buildNode(obj: Solid): Node { public fun buildNode(obj: Solid): Node {
val binding = VisualObjectFXBinding(this, obj) val binding = VisualObjectFXBinding(this, obj)
return when (obj) { return when (obj) {
is SolidReferenceGroup -> referenceFactory(obj, binding) is SolidReferenceGroup -> referenceFactory(obj, binding)
@ -65,6 +65,8 @@ class FX3DPlugin : AbstractPlugin() {
} }
is SolidLabel -> Text(obj.text).apply { is SolidLabel -> Text(obj.text).apply {
font = Font.font(obj.fontFamily, obj.fontSize) 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 x = -layoutBounds.width / 2
y = layoutBounds.height / 2 y = layoutBounds.height / 2
} }
@ -129,10 +131,10 @@ class FX3DPlugin : AbstractPlugin() {
} }
} }
companion object : PluginFactory<FX3DPlugin> { public companion object : PluginFactory<FX3DPlugin> {
override val tag = PluginTag("vision.fx3D", PluginTag.DATAFORGE_GROUP) override val tag: PluginTag = PluginTag("vision.fx3D", PluginTag.DATAFORGE_GROUP)
override val type = FX3DPlugin::class override val type: KClass<FX3DPlugin> = FX3DPlugin::class
override fun invoke(meta: Meta, context: Context) = FX3DPlugin() override fun invoke(meta: Meta, context: Context): FX3DPlugin = FX3DPlugin()
} }
} }
@ -140,14 +142,14 @@ class FX3DPlugin : AbstractPlugin() {
* Builder and updater for three.js object * Builder and updater for three.js object
*/ */
@Type(TYPE) @Type(TYPE)
interface FX3DFactory<in T : Solid> { public interface FX3DFactory<in T : Solid> {
val type: KClass<in T> public val type: KClass<in T>
operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node public operator fun invoke(obj: T, binding: VisualObjectFXBinding): Node
companion object { public companion object {
const val TYPE = "fx3DFactory" public const val TYPE: String = "fx3DFactory"
} }
} }

View File

@ -4,6 +4,7 @@ import javafx.application.Platform
import javafx.beans.property.ObjectProperty import javafx.beans.property.ObjectProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.scene.* import javafx.scene.*
import javafx.scene.layout.BorderPane
import javafx.scene.paint.Color import javafx.scene.paint.Color
import org.fxyz3d.scene.Axes import org.fxyz3d.scene.Axes
import space.kscience.dataforge.context.Context import space.kscience.dataforge.context.Context
@ -11,31 +12,32 @@ import space.kscience.dataforge.context.ContextAware
import space.kscience.visionforge.solid.specifications.Canvas3DOptions import space.kscience.visionforge.solid.specifications.Canvas3DOptions
import tornadofx.* import tornadofx.*
class FXCanvas3D( public class FXCanvas3D(
val plugin: FX3DPlugin, public val fx3d: FX3DPlugin,
val spec: Canvas3DOptions = Canvas3DOptions.empty(), public val options: Canvas3DOptions = Canvas3DOptions.empty(),
) : Fragment(), ContextAware { ) : Fragment(), ContextAware {
override val context: Context get() = plugin.context override val context: Context get() = fx3d.context
val world = Group().apply { public val world: Group = Group().apply {
//transforms.add(Rotate(180.0, Rotate.Z_AXIS)) //transforms.add(Rotate(180.0, Rotate.Z_AXIS))
} }
val axes = Axes().also { public val axes: Axes = Axes().also {
it.setHeight(spec.axes.size) it.setHeight(options.axes.size)
it.setRadius(spec.axes.width) it.setRadius(options.axes.width)
it.isVisible = spec.axes.visible it.isVisible = options.axes.visible
world.add(it) world.add(it)
} }
val light = AmbientLight() public val light: AmbientLight = AmbientLight()
private val camera = PerspectiveCamera().apply { private val camera = PerspectiveCamera().apply {
nearClip = spec.camera.nearClip nearClip = options.camera.nearClip
farClip = spec.camera.farClip farClip = options.camera.farClip
fieldOfView = spec.camera.fov.toDouble() fieldOfView = options.camera.fov.toDouble()
this.add(light)
add(light)
} }
private val canvas = SubScene( private val canvas = SubScene(
@ -49,19 +51,19 @@ class FXCanvas3D(
scene.camera = camera scene.camera = camera
} }
override val root = borderpane { override val root: BorderPane = borderpane {
center = canvas center = canvas
} }
val controls = camera.orbitControls(canvas, spec.camera).also { public val controls: OrbitControls = camera.orbitControls(canvas, options.camera).also {
world.add(it.centerMarker) world.add(it.centerMarker)
} }
val rootObjectProperty: ObjectProperty<Solid> = SimpleObjectProperty() public val rootObjectProperty: ObjectProperty<Solid> = SimpleObjectProperty()
var rootObject: Solid? by rootObjectProperty public var rootObject: Solid? by rootObjectProperty
private val rootNodeProperty = rootObjectProperty.objectBinding { private val rootNodeProperty = rootObjectProperty.objectBinding {
it?.let { plugin.buildNode(it) } it?.let { fx3d.buildNode(it) }
} }
init { init {
@ -79,7 +81,7 @@ class FXCanvas3D(
} }
} }
fun render(vision: Solid) { public fun render(vision: Solid) {
rootObject = vision rootObject = vision
} }
} }

View File

@ -38,7 +38,7 @@ private fun MeshView.toCSG(): CSG {
return CSG.fromPolygons(polygons) return CSG.fromPolygons(polygons)
} }
class FXCompositeFactory(val plugin: FX3DPlugin) : FX3DFactory<Composite> { public class FXCompositeFactory(public val plugin: FX3DPlugin) : FX3DFactory<Composite> {
override val type: KClass<in Composite> override val type: KClass<in Composite>
get() = Composite::class get() = Composite::class
@ -48,7 +48,7 @@ class FXCompositeFactory(val plugin: FX3DPlugin) : FX3DFactory<Composite> {
val firstCSG = first.toCSG() val firstCSG = first.toCSG()
val secondCSG = second.toCSG() val secondCSG = second.toCSG()
val resultCSG = when (obj.compositeType) { val resultCSG = when (obj.compositeType) {
CompositeType.UNION -> firstCSG.union(secondCSG) CompositeType.SUM, CompositeType.UNION -> firstCSG.union(secondCSG)
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) CompositeType.INTERSECT -> firstCSG.intersect(secondCSG)
CompositeType.SUBTRACT -> firstCSG.difference(secondCSG) CompositeType.SUBTRACT -> firstCSG.difference(secondCSG)
} }

View File

@ -7,11 +7,14 @@ import javafx.scene.Node
import kotlin.reflect.KClass import kotlin.reflect.KClass
object FXConvexFactory : FX3DFactory<Convex> { public object FXConvexFactory : FX3DFactory<Convex> {
override val type: KClass<in Convex> get() = Convex::class override val type: KClass<in Convex> get() = Convex::class
override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node { override fun invoke(obj: Convex, binding: VisualObjectFXBinding): Node {
val hull = HullUtil.hull(obj.points.map { Vector3d.xyz(it.x, it.y, it.z) }, PropertyStorage()) val hull = HullUtil.hull(
obj.points.map { Vector3d.xyz(it.x.toDouble(), it.y.toDouble(), it.z.toDouble()) },
PropertyStorage()
)
return hull.toNode() return hull.toNode()
} }

View File

@ -7,7 +7,7 @@ import space.kscience.visionforge.Vision
import space.kscience.visionforge.onPropertyChange import space.kscience.visionforge.onPropertyChange
import kotlin.reflect.KClass import kotlin.reflect.KClass
class FXReferenceFactory(val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> { public class FXReferenceFactory(public val plugin: FX3DPlugin) : FX3DFactory<SolidReferenceGroup> {
override val type: KClass<in SolidReferenceGroup> get() = SolidReferenceGroup::class override val type: KClass<in SolidReferenceGroup> get() = SolidReferenceGroup::class
override fun invoke(obj: SolidReferenceGroup, binding: VisualObjectFXBinding): Node { override fun invoke(obj: SolidReferenceGroup, binding: VisualObjectFXBinding): Node {

View File

@ -8,7 +8,7 @@ import org.fxyz3d.geometry.Face3
import space.kscience.dataforge.meta.Meta import space.kscience.dataforge.meta.Meta
import kotlin.reflect.KClass import kotlin.reflect.KClass
object FXShapeFactory : FX3DFactory<GeometrySolid> { public object FXShapeFactory : FX3DFactory<GeometrySolid> {
override val type: KClass<in GeometrySolid> get() = GeometrySolid::class override val type: KClass<in GeometrySolid> get() = GeometrySolid::class
override fun invoke(obj: GeometrySolid, binding: VisualObjectFXBinding): MeshView { override fun invoke(obj: GeometrySolid, binding: VisualObjectFXBinding): MeshView {

View File

@ -1,127 +1,110 @@
package space.kscience.visionforge.solid package space.kscience.visionforge.solid
import javafx.beans.InvalidationListener import javafx.beans.property.SimpleBooleanProperty
import javafx.beans.property.SimpleDoubleProperty import javafx.beans.property.SimpleDoubleProperty
import javafx.event.EventHandler import javafx.event.EventHandler
import javafx.geometry.Point3D
import javafx.scene.Camera import javafx.scene.Camera
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.SubScene import javafx.scene.SubScene
import javafx.scene.input.MouseEvent
import javafx.scene.input.ScrollEvent
import javafx.scene.shape.Sphere import javafx.scene.shape.Sphere
import javafx.scene.transform.Rotate 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 javafx.scene.transform.Translate
import space.kscience.dataforge.meta.useProperty
import tornadofx.* import tornadofx.*
import kotlin.math.* import kotlin.math.PI
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
import space.kscience.visionforge.solid.specifications.Camera as CameraSpec import space.kscience.visionforge.solid.specifications.Camera as CameraSpec
public class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec: CameraSpec) {
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
val distanceProperty = SimpleDoubleProperty(spec.distance) /**
var distance by distanceProperty * Zenith angle in radians
*/
public val zenithProperty: SimpleDoubleProperty = SimpleDoubleProperty().apply {
spec.useProperty(CameraSpec::latitude){
set(PI / 2 - spec.latitude)
}
}
val azimuthProperty = SimpleDoubleProperty(spec.azimuth) public var zenith: Double by zenithProperty
var azimuth by azimuthProperty
val zenithProperty = SimpleDoubleProperty(PI / 2 - spec.latitude)
var zenith by zenithProperty
val latitudeProperty = zenithProperty.unaryMinus().plus(PI / 2) private val baseTranslate = Translate(0.0, 0.0, 0.0)
val latitude by latitudeProperty
val baseXProperty = SimpleDoubleProperty(0.0) public var x: Double by baseTranslate.xProperty()
var x by baseXProperty public var y: Double by baseTranslate.yProperty()
val baseYProperty = SimpleDoubleProperty(0.0) public var z: Double by baseTranslate.zProperty()
var y by baseYProperty
val baseZProperty = SimpleDoubleProperty(0.0)
var z by baseZProperty
private val baseTranslate = Translate() private val distanceProperty = SimpleDoubleProperty().apply {
spec.useProperty(CameraSpec::distance) {
set(it)
}
}
// val basePositionProperty: ObjectBinding<Point3D> = private val distanceTranslation = Translate().apply {
// nonNullObjectBinding(baseXProperty, baseYProperty, baseZProperty) { zProperty().bind(-distanceProperty)
// Point3D(x, y, z) }
// }
//
// val basePosition by basePositionProperty
val centerMarker by lazy { 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 {
Sphere(10.0).also { Sphere(10.0).also {
it.transforms.setAll(baseTranslate) 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 { init {
camera.transforms.setAll(ry, rx, translate) camera.transforms.setAll(
update() baseTranslate,
val listener = InvalidationListener { yUpRotation,
update() azimuthRotation,
} zenithRotation,
distanceProperty.addListener(listener) distanceTranslation,
azimuthProperty.addListener(listener) centering,
zenithProperty.addListener(listener) )
baseXProperty.addListener(listener)
baseYProperty.addListener(listener)
baseZProperty.addListener(listener)
canvas.apply { canvas.apply {
// center.xProperty().bind(widthProperty().divide(2))
// center.zProperty().bind(heightProperty().divide(2))
handleMouse() 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() { private fun Node.handleMouse() {
var mousePosX = 0.0 var mousePosX = 0.0
@ -131,20 +114,21 @@ class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec:
var mouseDeltaX: Double var mouseDeltaX: Double
var mouseDeltaY: Double var mouseDeltaY: Double
onMousePressed = EventHandler<MouseEvent> { me -> onMousePressed = EventHandler { me ->
mousePosX = me.sceneX mousePosX = me.sceneX
mousePosY = me.sceneY mousePosY = me.sceneY
mouseOldX = me.sceneX mouseOldX = me.sceneX
mouseOldY = me.sceneY mouseOldY = me.sceneY
inProgressProperty.set(true)
} }
onMouseDragged = EventHandler<MouseEvent> { me -> onMouseDragged = EventHandler { me ->
mouseOldX = mousePosX mouseOldX = mousePosX
mouseOldY = mousePosY mouseOldY = mousePosY
mousePosX = me.sceneX mousePosX = me.sceneX
mousePosY = me.sceneY mousePosY = me.sceneY
mouseDeltaX = mousePosX - mouseOldX mouseDeltaX = mouseOldX - mousePosX
mouseDeltaY = mousePosY - mouseOldY mouseDeltaY = mouseOldY - mousePosY
val modifier = when { val modifier = when {
me.isControlDown -> CONTROL_MULTIPLIER me.isControlDown -> CONTROL_MULTIPLIER
@ -153,19 +137,24 @@ class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec:
} }
if (me.isPrimaryButtonDown) { if (me.isPrimaryButtonDown) {
azimuth = (azimuth - mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED).coerceIn(0.0, 2 * PI) azimuth = (azimuth - mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED)
zenith = (zenith - mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED).coerceIn(0.0,PI) zenith = (zenith - mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED).coerceIn(-PI/2, PI/2)
} else if (me.isSecondaryButtonDown) { } else if (me.isSecondaryButtonDown) {
x += MOUSE_SPEED * modifier * TRACK_SPEED * (mouseDeltaX * cos(azimuth) + mouseDeltaY * sin(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)) z += MOUSE_SPEED * modifier * TRACK_SPEED * ( mouseDeltaX * sin(azimuth) + mouseDeltaY * cos(azimuth))
} }
} }
onScroll = EventHandler<ScrollEvent> { event ->
onMouseReleased = EventHandler {
inProgressProperty.set(false)
}
onScroll = EventHandler { event ->
distance = max(1.0, distance - MOUSE_SPEED * event.deltaY * RESIZE_SPEED) distance = max(1.0, distance - MOUSE_SPEED * event.deltaY * RESIZE_SPEED)
} }
} }
companion object { public companion object {
private const val CONTROL_MULTIPLIER = 0.1 private const val CONTROL_MULTIPLIER = 0.1
private const val SHIFT_MULTIPLIER = 10.0 private const val SHIFT_MULTIPLIER = 10.0
private const val MOUSE_SPEED = 0.1 private const val MOUSE_SPEED = 0.1
@ -175,5 +164,5 @@ class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec:
} }
} }
fun Camera.orbitControls(canvas: SubScene, spec: CameraSpec) = public fun Camera.orbitControls(canvas: SubScene, spec: CameraSpec): OrbitControls =
OrbitControls(this, canvas, spec) OrbitControls(this, canvas, spec)

View File

@ -14,7 +14,7 @@ import tornadofx.*
/** /**
* A caching binding collection for [Vision] properties * A caching binding collection for [Vision] properties
*/ */
public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vision) { public class VisualObjectFXBinding(private val fx: FX3DPlugin, public val obj: Vision) {
private val bindings = HashMap<Name, ObjectBinding<MetaItem?>>() private val bindings = HashMap<Name, ObjectBinding<MetaItem?>>()
init { init {
@ -33,15 +33,13 @@ public class VisualObjectFXBinding(public val fx: FX3DPlugin, public val obj: Vi
} }
} }
public operator fun get(key: Name): ObjectBinding<MetaItem?> { public operator fun get(key: Name): ObjectBinding<MetaItem?> = bindings.getOrPut(key) {
return bindings.getOrPut(key) { object : ObjectBinding<MetaItem?>() {
object : ObjectBinding<MetaItem?>() { override fun computeValue(): MetaItem? = obj.getProperty(key)
override fun computeValue(): MetaItem? = obj.getProperty(key)
}
} }
} }
public operator fun get(key: String) = get(key.toName()) public operator fun get(key: String): ObjectBinding<TypedMetaItem<*>?> = get(key.toName())
} }
public fun ObjectBinding<MetaItem?>.value(): Binding<Value?> = objectBinding { it.value } public fun ObjectBinding<MetaItem?>.value(): Binding<Value?> = objectBinding { it.value }

View File

@ -1,83 +0,0 @@
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<Int, Counter>()
//
// 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)
//}

View File

@ -41,7 +41,7 @@ public class GdmlTransformer {
internal val styleCache = HashMap<Name, Meta>() internal val styleCache = HashMap<Name, Meta>()
public fun Solid.useStyle(name: String, builder: MetaBuilder.() -> Unit) { public fun Solid.registerAndUseStyle(name: String, builder: MetaBuilder.() -> Unit) {
styleCache.getOrPut(name.toName()) { styleCache.getOrPut(name.toName()) {
Meta(builder) Meta(builder)
} }
@ -49,7 +49,7 @@ public class GdmlTransformer {
} }
public fun Solid.transparent() { public fun Solid.transparent() {
useStyle("transparent") { registerAndUseStyle("transparent") {
SolidMaterial.MATERIAL_OPACITY_KEY put 0.3 SolidMaterial.MATERIAL_OPACITY_KEY put 0.3
"edges.enabled" put true "edges.enabled" put true
} }
@ -75,7 +75,7 @@ public class GdmlTransformer {
if (parent.physVolumes.isNotEmpty()) transparent() if (parent.physVolumes.isNotEmpty()) transparent()
useStyle(styleName) { registerAndUseStyle(styleName) {
val vfMaterial = SolidMaterial().apply { val vfMaterial = SolidMaterial().apply {
configurePaint(material, solid) configurePaint(material, solid)
} }
@ -125,7 +125,11 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
fun Solid.configureSolid(root: Gdml, parent: GdmlVolume, solid: GdmlSolid) { fun Solid.configureSolid(root: Gdml, parent: GdmlVolume, solid: GdmlSolid) {
val material = parent.materialref.resolve(root) ?: GdmlElement(parent.materialref.ref) val material = parent.materialref.resolve(root) ?: GdmlElement(parent.materialref.ref)
settings.run { configureSolid(parent, solid, material) } with(settings) {
with(this@configureSolid) {
configureSolid(parent, solid, material)
}
}
} }
private fun proxySolid(root: Gdml, group: SolidGroup, solid: GdmlSolid, name: String): SolidReferenceGroup { private fun proxySolid(root: Gdml, group: SolidGroup, solid: GdmlSolid, name: String): SolidReferenceGroup {
@ -159,25 +163,26 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
newScale: GdmlScale? = null, newScale: GdmlScale? = null,
): T = apply { ): T = apply {
newPos?.let { newPos?.let {
val point = Point3D(it.x(settings.lUnit), it.y(settings.lUnit), it.z(settings.lUnit)) val gdmlX = it.x(settings.lUnit)
if (point != Point3D.ZERO) { if (gdmlX != 0f) x = gdmlX
position = point val gdmlY = it.y(settings.lUnit)
} if (gdmlY != 0f) y = gdmlY
val gdmlZ = it.z(settings.lUnit)
if (gdmlZ != 0f) z = gdmlZ
} }
newRotation?.let { newRotation?.let {
val point = Point3D(it.x(settings.aUnit), it.y(settings.aUnit), it.z(settings.aUnit)) val gdmlX = it.x(settings.aUnit)
if (point != Point3D.ZERO) { if (gdmlX != 0f) rotationX = gdmlX
rotation = point val gdmlY = it.y(settings.aUnit)
} if (gdmlY != 0f) rotationY = gdmlY
//this@withPosition.rotationOrder = RotationOrder.ZXY val gdmlZ = it.z(settings.aUnit)
if (gdmlZ != 0f) rotationZ = gdmlZ
} }
newScale?.let { newScale?.let {
val point = Point3D(it.x, it.y, it.z) if (it.x != 1f) scaleX = it.x
if (point != Point3D.ONE) { if (it.y != 1f) scaleY = it.y
scale = point if (it.z != 1f) scaleZ = it.z
}
} }
//TODO convert units if needed
} }
fun <T : Solid> T.withPosition(root: Gdml, physVolume: GdmlPhysVolume): T = withPosition( fun <T : Solid> T.withPosition(root: Gdml, physVolume: GdmlPhysVolume): T = withPosition(
@ -227,11 +232,10 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
bottomRadius = solid.rmax1 * lScale, bottomRadius = solid.rmax1 * lScale,
height = solid.z * lScale, height = solid.z * lScale,
upperRadius = solid.rmax2 * lScale, upperRadius = solid.rmax2 * lScale,
startAngle = solid.startphi * aScale,
angle = solid.deltaphi * aScale,
name = name name = name
) { )
startAngle = solid.startphi * aScale
angle = solid.deltaphi * aScale
}
} else { } else {
coneSurface( coneSurface(
bottomOuterRadius = solid.rmax1 * lScale, bottomOuterRadius = solid.rmax1 * lScale,
@ -239,11 +243,10 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
height = solid.z * lScale, height = solid.z * lScale,
topOuterRadius = solid.rmax2 * lScale, topOuterRadius = solid.rmax2 * lScale,
topInnerRadius = solid.rmin2 * lScale, topInnerRadius = solid.rmin2 * lScale,
startAngle = solid.startphi * aScale,
angle = solid.deltaphi * aScale,
name = name name = name
) { )
startAngle = solid.startphi * aScale
angle = solid.deltaphi * aScale
}
} }
is GdmlXtru -> extrude(name) { is GdmlXtru -> extrude(name) {
shape { shape {
@ -271,12 +274,15 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
scaleZ = solid.scale.z.toFloat() scaleZ = solid.scale.z.toFloat()
} }
} }
is GdmlSphere -> sphereLayer(solid.rmax * lScale, solid.rmin * lScale, name) { is GdmlSphere -> sphereLayer(
phi = solid.deltaphi * aScale outerRadius = solid.rmax * lScale,
theta = solid.deltatheta * aScale innerRadius = solid.rmin * lScale,
phiStart = solid.startphi * aScale phi = solid.deltaphi * aScale,
thetaStart = solid.starttheta * aScale theta = solid.deltatheta * aScale,
} phiStart = solid.startphi * aScale,
thetaStart = solid.starttheta * aScale,
name = name,
)
is GdmlOrb -> sphere(solid.r * lScale, name = name) is GdmlOrb -> sphere(solid.r * lScale, name = name)
is GdmlPolyhedra -> extrude(name) { is GdmlPolyhedra -> extrude(name) {
//getting the radius of first //getting the radius of first
@ -297,7 +303,7 @@ private class GdmlTransformerEnv(val settings: GdmlTransformer) {
val first: GdmlSolid = solid.first.resolve(root) ?: error("") val first: GdmlSolid = solid.first.resolve(root) ?: error("")
val second: GdmlSolid = solid.second.resolve(root) ?: error("") val second: GdmlSolid = solid.second.resolve(root) ?: error("")
val type: CompositeType = when (solid) { val type: CompositeType = when (solid) {
is GdmlUnion -> CompositeType.UNION is GdmlUnion -> CompositeType.SUM // dumb sum for better performance
is GdmlSubtraction -> CompositeType.SUBTRACT is GdmlSubtraction -> CompositeType.SUBTRACT
is GdmlIntersection -> CompositeType.INTERSECT is GdmlIntersection -> CompositeType.INTERSECT
} }

View File

@ -0,0 +1,89 @@
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<Name, VisionCounterTree>
) {
// self count for prototypes
var selfCount = 1
val children: Map<NameToken, VisionCounterTree> 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<VisionCounterTree> = sequence {
yield(this@topToBottom)
children.values.forEach {
yieldAll(it.topToBottom())
}
}
public fun SolidGroup.markLayers(thresholds: List<Int> = 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<VisionCounterTree>(
{ 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" }
}
}
}
}
}

View File

@ -26,7 +26,7 @@ class TestCubes {
val smallBoxPrototype = vision.getPrototype("solids.smallBox".toName()) as? Box val smallBoxPrototype = vision.getPrototype("solids.smallBox".toName()) as? Box
assertNotNull(smallBoxPrototype) assertNotNull(smallBoxPrototype)
assertEquals(30.0, smallBoxPrototype.xSize.toDouble()) assertEquals(30.0, smallBoxPrototype.xSize.toDouble())
val smallBoxVision = vision["composite-111.smallBox"]?.prototype as? Box val smallBoxVision = vision["composite-111.smallBox"]?.unref as? Box
assertNotNull(smallBoxVision) assertNotNull(smallBoxVision)
assertEquals(30.0, smallBoxVision.xSize.toDouble()) assertEquals(30.0, smallBoxVision.xSize.toDouble())
} }

View File

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

View File

@ -4,9 +4,6 @@ import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromFile import space.kscience.gdml.decodeFromFile
import space.kscience.visionforge.solid.SolidGroup import space.kscience.visionforge.solid.SolidGroup
import java.nio.file.Path import java.nio.file.Path
import java.util.concurrent.atomic.AtomicInteger
public actual typealias Counter = AtomicInteger
public fun SolidGroup.gdml( public fun SolidGroup.gdml(
file: Path, file: Path,

View File

@ -5,7 +5,6 @@ import kotlinx.coroutines.withContext
import nl.adaptivity.xmlutil.StAXReader import nl.adaptivity.xmlutil.StAXReader
import space.kscience.gdml.Gdml import space.kscience.gdml.Gdml
import space.kscience.gdml.decodeFromReader import space.kscience.gdml.decodeFromReader
import space.kscience.visionforge.solid.prototype
import space.kscience.visionforge.visitor.countDistinct import space.kscience.visionforge.visitor.countDistinct
import space.kscience.visionforge.visitor.flowStatistics import space.kscience.visionforge.visitor.flowStatistics
import java.io.File import java.io.File
@ -23,7 +22,7 @@ suspend fun main() {
vision.flowStatistics<KClass<*>>{ _, child -> vision.flowStatistics<KClass<*>>{ _, child ->
child.prototype::class child::class
}.countDistinct().forEach { (depth, size) -> }.countDistinct().forEach { (depth, size) ->
println("$depth\t$size") println("$depth\t$size")
} }

View File

@ -19,7 +19,9 @@ import io.ktor.server.engine.embeddedServer
import io.ktor.websocket.WebSockets import io.ktor.websocket.WebSockets
import io.ktor.websocket.webSocket import io.ktor.websocket.webSocket
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.html.* import kotlinx.html.*
import kotlinx.html.stream.createHTML import kotlinx.html.stream.createHTML
@ -131,6 +133,15 @@ public class VisionServer internal constructor(
application.log.debug("Opened server socket for $name") application.log.debug("Opened server socket for $name")
val vision: Vision = visions[name.toName()] ?: error("Plot with id='$name' not registered") 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 { try {
withContext(visionManager.context.coroutineContext) { withContext(visionManager.context.coroutineContext) {
vision.flowChanges(visionManager, Duration.milliseconds(updateInterval)).collect { update -> vision.flowChanges(visionManager, Duration.milliseconds(updateInterval)).collect { update ->

View File

@ -6,7 +6,8 @@ import space.kscience.dataforge.meta.update
import space.kscience.visionforge.* import space.kscience.visionforge.*
public enum class CompositeType { public enum class CompositeType {
UNION, SUM, // Dumb sum of meshes
UNION, //CSG union
INTERSECT, INTERSECT,
SUBTRACT SUBTRACT
} }

View File

@ -15,11 +15,11 @@ import kotlin.math.sin
@Serializable @Serializable
@SerialName("solid.cone") @SerialName("solid.cone")
public class ConeSegment( public class ConeSegment(
public var bottomRadius: Float, public val bottomRadius: Float,
public var height: Float, public val height: Float,
public var topRadius: Float, public val topRadius: Float,
public var startAngle: Float = 0f, public val startAngle: Float = 0f,
public var angle: Float = PI2 public val angle: Float = PI2
) : SolidBase(), GeometrySolid { ) : SolidBase(), GeometrySolid {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
@ -83,10 +83,14 @@ public inline fun VisionContainerBuilder<Solid>.cone(
bottomRadius: Number, bottomRadius: Number,
height: Number, height: Number,
upperRadius: Number = 0.0, upperRadius: Number = 0.0,
startAngle: Number = 0f,
angle: Number = PI2,
name: String? = null, name: String? = null,
block: ConeSegment.() -> Unit = {} block: ConeSegment.() -> Unit = {}
): ConeSegment = ConeSegment( ): ConeSegment = ConeSegment(
bottomRadius.toFloat(), bottomRadius.toFloat(),
height.toFloat(), height.toFloat(),
topRadius = upperRadius.toFloat() topRadius = upperRadius.toFloat(),
startAngle = startAngle.toFloat(),
angle = angle.toFloat()
).apply(block).also { set(name, it) } ).apply(block).also { set(name, it) }

View File

@ -16,13 +16,13 @@ import kotlin.math.sin
@Serializable @Serializable
@SerialName("solid.coneSurface") @SerialName("solid.coneSurface")
public class ConeSurface( public class ConeSurface(
public var bottomRadius: Float, public val bottomRadius: Float,
public var bottomInnerRadius: Float, public val bottomInnerRadius: Float,
public var height: Float, public val height: Float,
public var topRadius: Float, public val topRadius: Float,
public var topInnerRadius: Float, public val topInnerRadius: Float,
public var startAngle: Float = 0f, public val startAngle: Float = 0f,
public var angle: Float = PI2, public val angle: Float = PI2,
) : SolidBase(), GeometrySolid { ) : SolidBase(), GeometrySolid {
init { init {
@ -148,6 +148,8 @@ public inline fun VisionContainerBuilder<Solid>.coneSurface(
height: Number, height: Number,
topOuterRadius: Number, topOuterRadius: Number,
topInnerRadius: Number, topInnerRadius: Number,
startAngle: Number = 0f,
angle: Number = PI2,
name: String? = null, name: String? = null,
block: ConeSurface.() -> Unit = {}, block: ConeSurface.() -> Unit = {},
): ConeSurface = ConeSurface( ): ConeSurface = ConeSurface(
@ -156,4 +158,6 @@ public inline fun VisionContainerBuilder<Solid>.coneSurface(
height = height.toFloat(), height = height.toFloat(),
topRadius = topOuterRadius.toFloat(), topRadius = topOuterRadius.toFloat(),
topInnerRadius = topInnerRadius.toFloat(), topInnerRadius = topInnerRadius.toFloat(),
startAngle = startAngle.toFloat(),
angle = angle.toFloat()
).apply(block).also { set(name, it) } ).apply(block).also { set(name, it) }

View File

@ -2,9 +2,8 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.visionforge.VisionBuilder import space.kscience.dataforge.meta.Config
import space.kscience.visionforge.VisionContainerBuilder import space.kscience.visionforge.*
import space.kscience.visionforge.set
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
import kotlin.math.sin import kotlin.math.sin
@ -13,7 +12,7 @@ import kotlin.math.sin
public typealias Shape2D = List<Point2D> public typealias Shape2D = List<Point2D>
@Serializable @Serializable
public class Shape2DBuilder(private val points: MutableList<Point2D> = ArrayList()) { public class Shape2DBuilder(private val points: ArrayList<Point2D> = ArrayList()) {
public fun point(x: Number, y: Number) { public fun point(x: Number, y: Number) {
points.add(Point2D(x, y)) points.add(Point2D(x, y))
@ -38,19 +37,9 @@ public data class Layer(var x: Float, var y: Float, var z: Float, var scale: Flo
@Serializable @Serializable
@SerialName("solid.extrude") @SerialName("solid.extrude")
public class Extruded( public class Extruded(
public var shape: List<Point2D> = ArrayList(), public val shape: List<Point2D>,
public var layers: MutableList<Layer> = ArrayList() public val layers: List<Layer>
) : SolidBase(), GeometrySolid { ) : SolidBase(), GeometrySolid, VisionPropertyContainer<Extruded> {
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 <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
val shape: Shape2D = shape val shape: Shape2D = shape
@ -103,6 +92,26 @@ public class Extruded(
} }
} }
public class ExtrudeBuilder(
public var shape: List<Point2D> = emptyList(),
public var layers: MutableList<Layer> = ArrayList(),
config: Config = Config()
) : SimpleVisionPropertyContainer<Extruded>(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 @VisionBuilder
public fun VisionContainerBuilder<Solid>.extrude(name: String? = null, action: Extruded.() -> Unit = {}): Extruded = public fun VisionContainerBuilder<Solid>.extruded(
Extruded().apply(action).also { set(name, it) } name: String? = null,
action: ExtrudeBuilder.() -> Unit = {}
): Extruded = ExtrudeBuilder().apply(action).build().also { set(name, it) }

View File

@ -13,7 +13,7 @@ import space.kscience.visionforge.set
@Serializable @Serializable
@SerialName("solid.line") @SerialName("solid.line")
public class PolyLine(public var points: List<Point3D>) : SolidBase(), Solid { public class PolyLine(public val points: List<Point3D>) : SolidBase(), Solid {
//var lineType by string() //var lineType by string()
public var thickness: Number by allProperties(inherit = false).number(1.0, public var thickness: Number by allProperties(inherit = false).number(1.0,

View File

@ -1,9 +1,7 @@
package space.kscience.visionforge.solid package space.kscience.visionforge.solid
import space.kscience.dataforge.meta.boolean import space.kscience.dataforge.meta.*
import space.kscience.dataforge.meta.descriptors.NodeDescriptor 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.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.dataforge.names.plus import space.kscience.dataforge.names.plus
@ -14,14 +12,28 @@ import space.kscience.visionforge.Vision.Companion.VISIBLE_KEY
import space.kscience.visionforge.solid.Solid.Companion.DETAIL_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.IGNORE_KEY
import space.kscience.visionforge.solid.Solid.Companion.LAYER_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] * Interface for 3-dimensional [Vision]
*/ */
public interface Solid : Vision { public interface Solid : Vision {
public var position: Point3D?
public var rotation: Point3D?
public var scale: Point3D?
override val descriptor: NodeDescriptor get() = Companion.descriptor override val descriptor: NodeDescriptor get() = Companion.descriptor
@ -37,7 +49,7 @@ public interface Solid : Vision {
public val Y_KEY: Name = "y".asName() public val Y_KEY: Name = "y".asName()
public val Z_KEY: Name = "z".asName() public val Z_KEY: Name = "z".asName()
public val POSITION_KEY: Name = "pos".asName() public val POSITION_KEY: Name = "position".asName()
public val X_POSITION_KEY: Name = POSITION_KEY + X_KEY public val X_POSITION_KEY: Name = POSITION_KEY + X_KEY
public val Y_POSITION_KEY: Name = POSITION_KEY + Y_KEY public val Y_POSITION_KEY: Name = POSITION_KEY + Y_KEY
@ -72,6 +84,23 @@ public interface Solid : Vision {
hide() 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) item(SolidMaterial.MATERIAL_KEY.toString(), SolidMaterial.descriptor)
enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) { enum(ROTATION_ORDER_KEY, default = RotationOrder.XYZ) {
@ -79,22 +108,6 @@ 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
}
} }
} }
@ -145,74 +158,51 @@ public var Vision.ignore: Boolean?
// get() = getProperty(SELECTED_KEY).boolean // get() = getProperty(SELECTED_KEY).boolean
// set(value) = setProperty(SELECTED_KEY, value) // set(value) = setProperty(SELECTED_KEY, value)
private fun Solid.position(): Point3D = internal fun float(name: Name, default: Number): ReadWriteProperty<Solid, Number> =
position ?: Point3D(0.0, 0.0, 0.0).also { position = it } object : ReadWriteProperty<Solid, Number> {
override fun getValue(thisRef: Solid, property: KProperty<*>): Number {
return thisRef.getOwnProperty(name)?.number ?: default
}
public var Solid.x: Number override fun setValue(thisRef: Solid, property: KProperty<*>, value: Number) {
get() = position?.x ?: 0f thisRef.setProperty(name, value)
set(value) { }
position().x = value.toDouble()
invalidateProperty(Solid.X_POSITION_KEY)
} }
public var Solid.y: Number internal fun point(name: Name, default: Float): ReadWriteProperty<Solid, Point3D?> =
get() = position?.y ?: 0f object : ReadWriteProperty<Solid, Point3D?> {
set(value) { override fun getValue(thisRef: Solid, property: KProperty<*>): Point3D? {
position().y = value.toDouble() val item = thisRef.getOwnProperty(name) ?: return null
invalidateProperty(Solid.Y_POSITION_KEY) 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.z: Number public var Solid.position: Point3D? by point(POSITION_KEY, 0f)
get() = position?.z ?: 0f public var Solid.rotation: Point3D? by point(ROTATION_KEY, 0f)
set(value) { public var Solid.scale: Point3D? by point(SCALE_KEY, 1f)
position().z = value.toDouble()
invalidateProperty(Solid.Z_POSITION_KEY)
}
private fun Solid.rotation(): Point3D = public var Solid.x: Number by float(X_POSITION_KEY, 0f)
rotation ?: Point3D(0.0, 0.0, 0.0).also { rotation = it } public var Solid.y: Number by float(Y_POSITION_KEY, 0f)
public var Solid.z: Number by float(Z_POSITION_KEY, 0f)
public var Solid.rotationX: Number public var Solid.rotationX: Number by float(X_ROTATION_KEY, 0f)
get() = rotation?.x ?: 0f public var Solid.rotationY: Number by float(Y_ROTATION_KEY, 0f)
set(value) { public var Solid.rotationZ: Number by float(Z_ROTATION_KEY, 0f)
rotation().x = value.toDouble()
invalidateProperty(Solid.X_ROTATION_KEY)
}
public var Solid.rotationY: Number public var Solid.scaleX: Number by float(X_SCALE_KEY, 1f)
get() = rotation?.y ?: 0f public var Solid.scaleY: Number by float(Y_SCALE_KEY, 1f)
set(value) { public var Solid.scaleZ: Number by float(Z_SCALE_KEY, 1f)
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)
}

View File

@ -2,21 +2,13 @@ package space.kscience.visionforge.solid
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta
import space.kscience.dataforge.meta.descriptors.NodeDescriptor 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.VisionBase
import space.kscience.visionforge.VisionChange import space.kscience.visionforge.VisionChange
@Serializable @Serializable
@SerialName("solid") @SerialName("solid")
public open class SolidBase( public open class SolidBase : VisionBase(), Solid {
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 val descriptor: NodeDescriptor get() = Solid.descriptor
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
@ -24,15 +16,3 @@ public open class SolidBase(
super.update(change) 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 }
}

View File

@ -1,23 +1,25 @@
package space.kscience.visionforge.solid package space.kscience.visionforge.solid
import kotlinx.serialization.KSerializer
import kotlinx.serialization.PolymorphicSerializer
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable 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.meta.descriptors.NodeDescriptor
import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.Name
import space.kscience.dataforge.names.NameToken import space.kscience.dataforge.names.NameToken
import space.kscience.visionforge.* import space.kscience.visionforge.*
/**
* A container with prototype support
*/
public interface PrototypeHolder { public interface PrototypeHolder {
/**
* Build or update prototype tree
*/
@VisionBuilder @VisionBuilder
public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit) public fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit)
/**
* Resolve a prototype from this container. Should never return a ref.
*/
public fun getPrototype(name: Name): Solid? public fun getPrototype(name: Name): Solid?
} }
@ -27,40 +29,35 @@ public interface PrototypeHolder {
*/ */
@Serializable @Serializable
@SerialName("group.solid") @SerialName("group.solid")
public class SolidGroup( public class SolidGroup : VisionGroupBase(), Solid, PrototypeHolder {
@Serializable(PrototypeSerializer::class) internal var prototypes: MutableVisionGroup? = null,
override var position: Point3D? = null, override val children: Map<NameToken, Vision> get() = super.childrenInternal.filter { it.key != PROTOTYPES_TOKEN }
override var rotation: Point3D? = null,
override var scale: Point3D? = null, private var prototypes: MutableVisionGroup?
) : VisionGroupBase(), Solid, PrototypeHolder { get() = childrenInternal[PROTOTYPES_TOKEN] as? MutableVisionGroup
set(value) {
set(PROTOTYPES_TOKEN, value)
}
init {
prototypes?.parent = this
}
override val descriptor: NodeDescriptor get() = Solid.descriptor override val descriptor: NodeDescriptor get() = Solid.descriptor
/** /**
* Ger a prototype redirecting the request to the parent if prototype is not found * Get a prototype redirecting the request to the parent if prototype is not found.
* If prototype is a ref, then it is unfolded automatically.
*/ */
override fun getPrototype(name: Name): Solid? = override fun getPrototype(name: Name): Solid? =
(prototypes?.get(name) as? Solid) ?: (parent as? PrototypeHolder)?.getPrototype(name) prototypes?.get(name)?.unref ?: (parent as? PrototypeHolder)?.getPrototype(name)
/** /**
* Create or edit prototype node as a group * Create or edit prototype node as a group
*/ */
override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit { override fun prototypes(builder: VisionContainerBuilder<Solid>.() -> Unit): Unit {
(prototypes ?: Prototypes().also { (prototypes ?: SolidGroup().also {
prototypes = it prototypes = it
it.parent = this
}).run(builder) }).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 createGroup(): SolidGroup = SolidGroup()
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
@ -90,50 +87,3 @@ public fun VisionContainerBuilder<Vision>.group(
@VisionBuilder @VisionBuilder
public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup.() -> Unit = {}): SolidGroup = public fun VisionContainerBuilder<Vision>.group(name: String, action: SolidGroup.() -> Unit = {}): SolidGroup =
SolidGroup().apply(action).also { set(name, it) } SolidGroup().apply(action).also { set(name, it) }
/**
* A special class which works as a holder for prototypes
*/
internal class Prototypes(
children: MutableMap<NameToken, Vision> = 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<Solid>.() -> Unit) {
apply(builder)
}
override fun getPrototype(name: Name): Solid? = get(name) as? Solid
}
internal class PrototypeSerializer : KSerializer<MutableVisionGroup> {
private val mapSerializer: KSerializer<Map<NameToken, Vision>> =
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)
}
}

View File

@ -9,9 +9,9 @@ import space.kscience.visionforge.set
@Serializable @Serializable
@SerialName("solid.label") @SerialName("solid.label")
public class SolidLabel( public class SolidLabel(
public var text: String, public val text: String,
public var fontSize: Double, public val fontSize: Double,
public var fontFamily: String, public val fontFamily: String,
) : SolidBase(), Solid ) : SolidBase(), Solid
@VisionBuilder @VisionBuilder

View File

@ -11,25 +11,52 @@ import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.* import space.kscience.dataforge.names.*
import space.kscience.visionforge.* import space.kscience.visionforge.*
public interface SolidReference : Solid {
public interface SolidReference : VisionGroup {
/**
* The prototype for this reference. Always returns a "real" prototype, not a reference
*/
public val prototype: 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( private fun SolidReference.getRefProperty(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean,
includeStyles: Boolean, includeStyles: Boolean,
includeDefaults: Boolean, includeDefaults: Boolean,
): MetaItem? = buildList { ): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) {
add(getOwnProperty(name)) getOwnProperty(name)
if (includeStyles) { } else {
addAll(getStyleItems(name)) buildList {
} add(getOwnProperty(name))
add(prototype.getProperty(name, inherit, includeStyles, includeDefaults)) if (includeStyles) {
if (inherit) { addAll(getStyleItems(name))
add(parent?.getProperty(name, inherit)) }
} add(prototype.getProperty(name, inherit, includeStyles, includeDefaults))
}.merge() 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
/** /**
* A reference [Solid] to reuse a template object * A reference [Solid] to reuse a template object
@ -38,48 +65,24 @@ private fun SolidReference.getRefProperty(
@SerialName("solid.ref") @SerialName("solid.ref")
public class SolidReferenceGroup( public class SolidReferenceGroup(
public val refName: Name, public val refName: Name,
) : SolidBase(), SolidReference, VisionGroup { ) : VisionBase(), SolidReference, VisionGroup, Solid {
/** /**
* Recursively search for defined template in the parent * Recursively search for defined template in the parent
*/ */
override val prototype: Solid override val prototype: Solid by lazy {
get() { if (parent == null) error("No parent is present for SolidReferenceGroup")
if (parent == null) error("No parent is present for SolidReferenceGroup") if (parent !is PrototypeHolder) error("Parent does not hold prototypes")
if (parent !is SolidGroup) error("Reference parent is not a group") (parent as? PrototypeHolder)?.getPrototype(refName) ?: error("Prototype with name $refName not found")
return (parent as? SolidGroup)?.getPrototype(refName) }
?: error("Prototype with name $refName not found")
}
override val children: Map<NameToken, Vision> override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children get() = (prototype as? VisionGroup)?.children
?.filter { !it.key.toString().startsWith("@") } ?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN }
?.mapValues { ?.mapValues {
ReferenceChild(it.key.asName()) ReferenceChild(this, it.key.asName())
} ?: emptyMap() } ?: 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( override fun getProperty(
name: Name, name: Name,
inherit: Boolean, inherit: Boolean,
@ -94,37 +97,32 @@ public class SolidReferenceGroup(
* A ProxyChild is created temporarily only to interact with properties, it does not store any values * 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). * (properties are stored in external cache) and created and destroyed on-demand).
*/ */
private inner class ReferenceChild(private val childName: Name) : SolidReference, VisionGroup { private class ReferenceChild(
val owner: SolidReferenceGroup,
private val refName: Name
) : SolidReference, VisionGroup, Solid {
//TODO replace by properties override val prototype: Solid by lazy {
override var position: Point3D? if (refName.isEmpty()) owner.prototype else {
get() = prototype.position val proto = (owner.prototype as? VisionGroup)?.get(refName)
set(_) { ?: error("Prototype with name $refName not found in SolidReferenceGroup ${owner.refName}")
error("Can't set position of reference") proto.unref as? Solid
?: error("Prototype with name $refName is ${proto::class} but expected Solid")
} }
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<NameToken, Vision> override val children: Map<NameToken, Vision>
get() = (prototype as? VisionGroup)?.children get() = (prototype as? VisionGroup)?.children
?.filter { !it.key.toString().startsWith("@") } ?.filter { it.key != SolidGroup.PROTOTYPES_TOKEN }
?.mapValues { (key, _) -> ?.mapValues { (key, _) ->
ReferenceChild(childName + key.asName()) ReferenceChild(owner, refName + key.asName())
} ?: emptyMap() } ?: emptyMap()
override fun getOwnProperty(name: Name): MetaItem? = getChildProperty(childName, name) override fun getOwnProperty(name: Name): MetaItem? =
owner.getOwnProperty(childPropertyName(refName, name))
override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) { override fun setProperty(name: Name, item: MetaItem?, notify: Boolean) {
setChildProperty(childName, name, item, notify) owner.setProperty(childPropertyName(refName, name), item, notify)
} }
override fun getProperty( override fun getProperty(
@ -132,16 +130,12 @@ public class SolidReferenceGroup(
inherit: Boolean, inherit: Boolean,
includeStyles: Boolean, includeStyles: Boolean,
includeDefaults: Boolean, includeDefaults: Boolean,
): MetaItem? = if (!inherit && !includeStyles && !includeDefaults) { ): MetaItem? = getRefProperty(name, inherit, includeStyles, includeDefaults)
getOwnProperty(name)
} else {
getRefProperty(name, inherit, includeStyles, includeDefaults)
}
override var parent: VisionGroup? override var parent: VisionGroup?
get() { get() {
val parentName = childName.cutLast() val parentName = refName.cutLast()
return if (parentName.isEmpty()) this@SolidReferenceGroup else ReferenceChild(parentName) return if (parentName.isEmpty()) owner else ReferenceChild(owner, parentName)
} }
set(_) { set(_) {
error("Setting a parent for a reference child is not possible") error("Setting a parent for a reference child is not possible")
@ -149,8 +143,8 @@ public class SolidReferenceGroup(
@DFExperimental @DFExperimental
override val propertyChanges: Flow<Name> override val propertyChanges: Flow<Name>
get() = this@SolidReferenceGroup.propertyChanges.mapNotNull { name -> get() = owner.propertyChanges.mapNotNull { name ->
if (name.startsWith(childToken(childName))) { if (name.startsWith(childToken(refName))) {
name.cutFirst() name.cutFirst()
} else { } else {
null null
@ -158,7 +152,7 @@ public class SolidReferenceGroup(
} }
override fun invalidateProperty(propertyName: Name) { override fun invalidateProperty(propertyName: Name) {
this@SolidReferenceGroup.invalidateProperty(childPropertyName(childName, propertyName)) owner.invalidateProperty(childPropertyName(refName, propertyName))
} }
override fun update(change: VisionChange) { override fun update(change: VisionChange) {
@ -176,12 +170,6 @@ 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 * Create ref for existing prototype
*/ */

View File

@ -4,6 +4,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.visionforge.VisionBuilder import space.kscience.visionforge.VisionBuilder
import space.kscience.visionforge.VisionContainerBuilder import space.kscience.visionforge.VisionContainerBuilder
import space.kscience.visionforge.VisionPropertyContainer
import space.kscience.visionforge.set import space.kscience.visionforge.set
import kotlin.math.PI import kotlin.math.PI
import kotlin.math.cos import kotlin.math.cos
@ -12,12 +13,12 @@ import kotlin.math.sin
@Serializable @Serializable
@SerialName("solid.sphere") @SerialName("solid.sphere")
public class Sphere( public class Sphere(
public var radius: Float, public val radius: Float,
public var phiStart: Float = 0f, public val phiStart: Float = 0f,
public var phi: Float = PI2, public val phi: Float = PI2,
public var thetaStart: Float = 0f, public val thetaStart: Float = 0f,
public var theta: Float = PI.toFloat(), public val theta: Float = PI .toFloat(),
) : SolidBase(), GeometrySolid { ) : SolidBase(), GeometrySolid, VisionPropertyContainer<Sphere> {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>) {
fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D { fun point3DfromSphCoord(r: Float, theta: Float, phi: Float): Point3D {

View File

@ -15,12 +15,12 @@ import kotlin.math.sin
@Serializable @Serializable
@SerialName("solid.sphereLayer") @SerialName("solid.sphereLayer")
public class SphereLayer( public class SphereLayer(
public var outerRadius: Float, public val outerRadius: Float,
public var innerRadius: Float, public val innerRadius: Float,
public var phiStart: Float = 0f, public val phiStart: Float = 0f,
public var phi: Float = PI2, public val phi: Float = PI2,
public var thetaStart: Float = 0f, public val thetaStart: Float = 0f,
public var theta: Float = PI.toFloat(), public val theta: Float = PI.toFloat(),
) : SolidBase(), GeometrySolid { ) : SolidBase(), GeometrySolid {
override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>): Unit = geometryBuilder.run { override fun <T : Any> toGeometry(geometryBuilder: GeometryBuilder<T>): Unit = geometryBuilder.run {
@ -72,9 +72,17 @@ public class SphereLayer(
public inline fun VisionContainerBuilder<Solid>.sphereLayer( public inline fun VisionContainerBuilder<Solid>.sphereLayer(
outerRadius: Number, outerRadius: Number,
innerRadius: Number, innerRadius: Number,
phiStart: Number = 0f,
phi: Number = PI2,
thetaStart: Number = 0f,
theta: Number = PI.toFloat(),
name: String? = null, name: String? = null,
action: SphereLayer.() -> Unit = {}, action: SphereLayer.() -> Unit = {},
): SphereLayer = SphereLayer( ): SphereLayer = SphereLayer(
outerRadius.toFloat(), outerRadius.toFloat(),
innerRadius.toFloat(), innerRadius.toFloat(),
phiStart.toFloat(),
phi.toFloat(),
thetaStart.toFloat(),
theta.toFloat()
).apply(action).also { set(name, it) } ).apply(action).also { set(name, it) }

View File

@ -1,39 +1,68 @@
package space.kscience.visionforge.solid package space.kscience.visionforge.solid
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import space.kscience.dataforge.meta.Meta import kotlinx.serialization.descriptors.SerialDescriptor
import space.kscience.dataforge.meta.MetaBuilder import kotlinx.serialization.encoding.Decoder
import space.kscience.dataforge.meta.double import kotlinx.serialization.encoding.Encoder
import space.kscience.dataforge.meta.get 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 kotlin.math.PI import kotlin.math.PI
import kotlin.math.pow
import kotlin.math.sqrt
public const val PI2: Float = 2 * PI.toFloat() public const val PI2: Float = 2 * PI.toFloat()
@Serializable @Serializable
public data class Point2D(public var x: Double, public var y: Double) public data class Point2D(public var x: Float, public var y: Float)
public fun Point2D(x: Number, y: Number): Point2D = Point2D(x.toDouble(), y.toDouble()) public fun Point2D(x: Number, y: Number): Point2D = Point2D(x.toFloat(), y.toFloat())
public fun Point2D.toMeta(): Meta = Meta { public fun Point2D.toMeta(): Meta = Meta {
Solid.X_KEY put x X_KEY put x
Solid.Y_KEY put y Y_KEY put y
} }
internal fun Meta.point2D(): Point2D = Point2D(this["x"].double ?: 0.0, this["y"].double ?: 0.0) 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
@Serializable
public data class Point3D(
public var x: Double,
public var y: Double,
public var z: Double,
) {
public companion object { public companion object {
public val ZERO: Point3D = Point3D(0.0, 0.0, 0.0) public val ZERO: Point3D = Point3D(0.0, 0.0, 0.0)
public val ONE: Point3D = Point3D(1.0, 1.0, 1.0) public val ONE: Point3D = Point3D(1.0, 1.0, 1.0)
} }
} }
public fun Point3D(x: Number, y: Number, z: Number): Point3D = Point3D(x.toDouble(), y.toDouble(), z.toDouble()) @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<Point3D> {
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 operator fun Point3D.plus(other: Point3D): Point3D = Point3D( public operator fun Point3D.plus(other: Point3D): Point3D = Point3D(
this.x + other.x, this.x + other.x,
@ -41,10 +70,52 @@ public operator fun Point3D.plus(other: Point3D): Point3D = Point3D(
this.z + other.z this.z + other.z
) )
internal fun Meta.point3D() = Point3D(this["x"].double ?: 0.0, this["y"].double ?: 0.0, this["y"].double ?: 0.0) 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)
}
public fun Point3D.toMeta(): MetaBuilder = Meta { public fun Point3D.toMeta(): MetaBuilder = Meta {
Solid.X_KEY put x X_KEY put x
Solid.Y_KEY put y Y_KEY put y
Solid.Z_KEY put z 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 }
} }

View File

@ -18,7 +18,6 @@ public class Camera : Scheme() {
public var distance: Double by double(INITIAL_DISTANCE) public var distance: Double by double(INITIAL_DISTANCE)
public var azimuth: Double by double(INITIAL_AZIMUTH) public var azimuth: Double by double(INITIAL_AZIMUTH)
public var latitude: Double by double(INITIAL_LATITUDE) public var latitude: Double by double(INITIAL_LATITUDE)
public val zenith: Double get() = PI / 2 - latitude
public companion object : SchemeSpec<Camera>(::Camera) { public companion object : SchemeSpec<Camera>(::Camera) {
public const val INITIAL_DISTANCE: Double = 300.0 public const val INITIAL_DISTANCE: Double = 300.0
@ -51,4 +50,6 @@ public class Camera : Scheme() {
} }
} }
} }
} }
public val Camera.zenith: Double get() = PI / 2 - latitude

View File

@ -1,36 +1,39 @@
package space.kscience.visionforge.solid.transform package space.kscience.visionforge.solid.transform
import space.kscience.dataforge.meta.itemSequence
import space.kscience.dataforge.misc.DFExperimental import space.kscience.dataforge.misc.DFExperimental
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName
import space.kscience.visionforge.* import space.kscience.visionforge.MutableVisionGroup
import space.kscience.visionforge.Vision
import space.kscience.visionforge.VisionGroup
import space.kscience.visionforge.meta
import space.kscience.visionforge.solid.* 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 @DFExperimental
internal fun mergeChild(parent: VisionGroup, child: Vision): Vision { internal fun Vision.updateFrom(other: Vision): Vision {
return child.apply { if (this is Solid && other is Solid) {
x += other.x
configure(parent.meta) y += other.y
z += other.y
//parent.properties?.let { config.update(it) } rotationX += other.rotationX
rotationY += other.rotationY
if (this is Solid && parent is Solid) { rotationZ += other.rotationZ
position = (position ?: Point3D.ZERO) + (parent.position ?: Point3D.ZERO) scaleX *= other.scaleX
rotation = (parent.rotation ?: Point3D.ZERO) + (parent.rotation ?: Point3D.ZERO) scaleY *= other.scaleY
scale = when { scaleZ *= other.scaleZ
scale == null && parent.scale == null -> null other.meta.itemSequence().forEach { (name, item) ->
scale == null -> parent.scale if (getProperty(name) == null) {
parent.scale == null -> scale setProperty(name, item)
else -> Point3D(
scale!!.x * parent.scale!!.x,
scale!!.y * parent.scale!!.y,
scale!!.z * parent.scale!!.z
)
} }
} }
} }
return this
} }
@DFExperimental @DFExperimental
internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() { internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
@ -43,7 +46,7 @@ internal object RemoveSingleChild : VisualTreeTransform<SolidGroup>() {
} }
if (parent is VisionGroup && parent.children.size == 1) { if (parent is VisionGroup && parent.children.size == 1) {
val child = parent.children.values.first() val child = parent.children.values.first()
val newParent = mergeChild(parent, child) val newParent = child.updateFrom(parent)
newParent.parent = null newParent.parent = null
set(childName.asName(), newParent) set(childName.asName(), newParent)
} }

View File

@ -31,7 +31,7 @@ internal object UnRef : VisualTreeTransform<SolidGroup>() {
} }
children.filter { (it.value as? SolidReferenceGroup)?.refName == name }.forEach { (key, value) -> children.filter { (it.value as? SolidReferenceGroup)?.refName == name }.forEach { (key, value) ->
val reference = value as SolidReferenceGroup val reference = value as SolidReferenceGroup
val newChild = mergeChild(reference, reference.prototype) val newChild = reference.prototype.updateFrom(reference)
newChild.parent = null newChild.parent = null
set(key.asName(), newChild) // replace proxy with merged object set(key.asName(), newChild) // replace proxy with merged object
} }

View File

@ -4,6 +4,6 @@ plugins {
dependencies { dependencies {
api(project(":visionforge-solid")) api(project(":visionforge-solid"))
implementation(npm("three", "0.124.0")) implementation(npm("three", "0.130.1"))
implementation(npm("three-csg-ts", "2.2.2")) implementation(npm("three-csg-ts", "3.1.6"))
} }

View File

@ -24,6 +24,7 @@
@file:JsModule("three") @file:JsModule("three")
@file:JsNonModule @file:JsNonModule
@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING", "unused")
package info.laht.threekt package info.laht.threekt

View File

@ -1,3 +1,4 @@
@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE_WARNING", "unused")
@file:JsModule("three") @file:JsModule("three")
@file:JsNonModule @file:JsNonModule

View File

@ -44,6 +44,7 @@ abstract external class BufferAttribute protected constructor(
* Default is 0. Position at whcih to start update. * Default is 0. Position at whcih to start update.
*/ */
var offset: Int var offset: Int
/** /**
* Default is -1, which means don't use update ranges. * Default is -1, which means don't use update ranges.
@ -56,16 +57,19 @@ abstract external class BufferAttribute protected constructor(
* UUID of this object instance. This gets automatically assigned and this shouldn't be edited. * UUID of this object instance. This gets automatically assigned and this shouldn't be edited.
*/ */
val uuid: String val uuid: String
/** /**
* Optional name for this attribute instance. Default is an empty string. * Optional name for this attribute instance. Default is an empty string.
*/ */
var name: String var name: String
var array: dynamic var array: dynamic
/** /**
* The length of vectors that are being stored in the array. * The length of vectors that are being stored in the array.
*/ */
val itemSize: Int val itemSize: Int
/** /**
* Stores the array's length divided by the itemSize. * Stores the array's length divided by the itemSize.
* *
@ -73,6 +77,7 @@ abstract external class BufferAttribute protected constructor(
* then this will count the number of such vectors stored. * then this will count the number of such vectors stored.
*/ */
val count: Int 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. * Indicates how the underlying data in the buffer maps to the values in the GLSL shader code. See the constructor above for details.
*/ */
@ -86,6 +91,7 @@ abstract external class BufferAttribute protected constructor(
* This corresponds to the gl.DYNAMIC_DRAW flag. * This corresponds to the gl.DYNAMIC_DRAW flag.
*/ */
var dynamic: Boolean var dynamic: Boolean
/** /**
* This can be used to only update some components of stored vectors ( * This can be used to only update some components of stored vectors (
* for example, just the component related to color). * for example, just the component related to color).
@ -99,8 +105,16 @@ abstract external class BufferAttribute protected constructor(
*/ */
var needsUpdate: Boolean var needsUpdate: Boolean
/**
* A callback function that is executed after the Renderer has transferred the attribute array data to the GPU.
*/
var onUploadCallback: () -> Unit 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. * A version number, incremented every time the needsUpdate property is set to true.
*/ */
@ -119,6 +133,7 @@ abstract external class BufferAttribute protected constructor(
fun getW(index: Int): Number fun getW(index: Int): Number
fun copy(source: BufferAttribute): BufferAttribute fun copy(source: BufferAttribute): BufferAttribute
/** /**
* Copy a vector from bufferAttribute[index2] to array[index1]. * Copy a vector from bufferAttribute[index2] to array[index1].
*/ */
@ -205,3 +220,22 @@ abstract external class BufferAttribute protected constructor(
*/ */
fun setXYZW(index: Int, x: Number, y: Number, z: Number, w: Number) fun setXYZW(index: Int, x: Number, y: Number, z: Number, w: Number)
} }
external class Float32BufferAttribute(
array: Array<Float>,
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

View File

@ -85,14 +85,14 @@ open external class BufferGeometry {
fun clearGroups() fun clearGroups()
fun addGroup(start: Int, count: Int, materialIndex: Int = definedExternally) fun addGroup(start: Int, count: Int, materialIndex: Int = definedExternally)
fun addAttribute(name: String, attribute: BufferAttribute) fun setAttribute(name: String, attribute: BufferAttribute)
fun getAttribute(name: String): BufferAttribute fun getAttribute(name: String): BufferAttribute
fun removeAttribute(name: String): BufferGeometry fun deleteAttribute(name: String): BufferGeometry
fun setIndex(index: BufferAttribute) fun setIndex(index: BufferAttribute)
fun setIndex(index: Array<Short>)
fun setDrawRange(start: Int, count: Int) fun setDrawRange(start: Int, count: Int)
fun fromGeometry(geometry: Geometry)
fun setFromObject(`object`: Object3D): BufferGeometry fun setFromObject(`object`: Object3D): BufferGeometry
fun updateFromObject(`object`: Object3D): BufferGeometry fun updateFromObject(`object`: Object3D): BufferGeometry
fun setFromPoints(points: Array<Vector3>): BufferGeometry fun setFromPoints(points: Array<Vector3>): BufferGeometry

View File

@ -1,48 +0,0 @@
/*
* 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)
}

View File

@ -1,109 +0,0 @@
/*
* 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<Vector3>
}
external interface MorphNormal {
val name: String
val normals: Array<Vector3>
}
open external class Geometry {
val id: Int
var vertices: Array<Vector3>
var colors: Array<Color>
var faces: Array<Face3>
var faceVertexUvs: Array<Array<Vector2>>
var morphTargets: Array<MorphTarget>
var morphNormals: Array<MorphNormal>
var skinWeights: Array<Vector4>
var skinIndices: Array<Vector4>
var lineDistances: List<Double>
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<Vector3>): Geometry
fun sortFacesByMaterialIndex()
fun toJSON(): Any
open fun clone(): Geometry
fun copy(geometry: Geometry): Geometry
fun dispose()
}

View File

@ -4,7 +4,6 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class BoxGeometry( external class BoxGeometry(
@ -14,14 +13,4 @@ external class BoxGeometry(
widthSegments: Int = definedExternally, widthSegments: Int = definedExternally,
heightSegments: Int = definedExternally, heightSegments: Int = definedExternally,
depthSegments: 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 ) : BufferGeometry

View File

@ -4,7 +4,7 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class ConeGeometry( external class ConeGeometry(
radius: Number = definedExternally, radius: Number = definedExternally,
@ -14,14 +14,4 @@ external class ConeGeometry(
openEnded: Boolean = definedExternally, openEnded: Boolean = definedExternally,
thetaStart: Boolean = definedExternally, thetaStart: Boolean = definedExternally,
thetaLength: 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 ) : BufferGeometry

View File

@ -4,7 +4,6 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class CylinderGeometry( external class CylinderGeometry(
radiusTop: Number, radiusTop: Number,
@ -15,15 +14,4 @@ external class CylinderGeometry(
openEnded: Boolean = definedExternally, openEnded: Boolean = definedExternally,
thetaStart: Number = definedExternally, thetaStart: Number = definedExternally,
thetaLength: 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 ) : BufferGeometry

View File

@ -4,8 +4,5 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class EdgesGeometry(geometry: Geometry, thresholdAngle: Int = definedExternally) : BufferGeometry { public external class EdgesGeometry(geometry: BufferGeometry, thresholdAngle: Int = definedExternally) : BufferGeometry
constructor(geometry: BufferGeometry, thresholdAngle: Int = definedExternally)
}

View File

@ -11,7 +11,6 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.extras.core.Shape import info.laht.threekt.extras.core.Shape
import info.laht.threekt.math.Vector2 import info.laht.threekt.math.Vector2
@ -78,15 +77,3 @@ external open class ExtrudeBufferGeometry : BufferGeometry {
var WorldUVGenerator: UVGenerator var WorldUVGenerator: UVGenerator
} }
} }
external open class ExtrudeGeometry : Geometry {
constructor(shapes: Shape, options: ExtrudeGeometryOptions?)
constructor(shapes: Array<Shape>, options: ExtrudeGeometryOptions?)
open fun addShapeList(shapes: Array<Shape>, options: Any? = definedExternally)
open fun addShape(shape: Shape, options: Any? = definedExternally)
companion object {
var WorldUVGenerator: UVGenerator
}
}

View File

@ -4,7 +4,6 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class PlaneGeometry( external class PlaneGeometry(
@ -13,13 +12,4 @@ external class PlaneGeometry(
widthSegments: Int = definedExternally, widthSegments: Int = definedExternally,
heightSegments: Int = definedExternally heightSegments: Int = definedExternally
) : Geometry
external class PlaneBufferGeometry(
width: Number,
height: Number,
widthSegments: Int = definedExternally,
heightSegments: Int = definedExternally
) : BufferGeometry ) : BufferGeometry

View File

@ -4,7 +4,6 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class SphereGeometry( external class SphereGeometry(
radius: Number, radius: Number,
@ -14,14 +13,4 @@ external class SphereGeometry(
phiLength: Number = definedExternally, phiLength: Number = definedExternally,
thetaStart: Number = definedExternally, thetaStart: Number = definedExternally,
thetaLength: 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 ) : BufferGeometry

View File

@ -34,10 +34,7 @@ external interface TextGeometryParameters {
set(value) = definedExternally set(value) = definedExternally
} }
external class TextBufferGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) : ExtrudeBufferGeometry { external class TextBufferGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) :
val parameters: TextGeometryParameters ExtrudeBufferGeometry {
}
external class TextGeometry(text: String, parameters: TextGeometryParameters? = definedExternally) : ExtrudeGeometry {
val parameters: TextGeometryParameters val parameters: TextGeometryParameters
} }

View File

@ -4,7 +4,7 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
external class TorusGeometry( external class TorusGeometry(
radius: Number = definedExternally, radius: Number = definedExternally,
@ -12,12 +12,4 @@ external class TorusGeometry(
radialSegments: Int = definedExternally, radialSegments: Int = definedExternally,
tubularSegments: Int = definedExternally, tubularSegments: Int = definedExternally,
arc: Number = definedExternally arc: Number = definedExternally
) : Geometry
external class TorusBufferGeometry(
radius: Number = definedExternally,
tube: Number = definedExternally,
radialSegments: Int = definedExternally,
tubularSegments: Int = definedExternally,
arc: Number = definedExternally
) : BufferGeometry ) : BufferGeometry

View File

@ -1,10 +1,10 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.extras.core.Curve import info.laht.threekt.extras.core.Curve
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
/** /**
* Creates a tube that extrudes along a 3d curve. * Creates a tube that extrudes along a 3d curve.
*/ */
@ -16,25 +16,6 @@ external class TubeGeometry(
radiusSegments: Int = definedExternally, radiusSegments: Int = definedExternally,
closed: Boolean = definedExternally closed: Boolean = definedExternally
) : Geometry {
var tangents: Array<Vector3>
var normals: Array<Vector3>
var binormals: Array<Vector3>
}
/**
* Creates a tube that extrudes along a 3d curve.
*/
external class TubeBufferGeometry(
path: Curve<Vector3>,
tubularSegments: Int = definedExternally,
radius: Number = definedExternally,
radiusSegments: Int = definedExternally,
closed: Boolean = definedExternally
) : BufferGeometry { ) : BufferGeometry {
val parameters: dynamic val parameters: dynamic

View File

@ -4,14 +4,8 @@
package info.laht.threekt.geometries package info.laht.threekt.geometries
import info.laht.threekt.core.BufferGeometry 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. * This can be used as a helper object to view a Geometry object as a wireframe.
*/ */
external class WireframeGeometry : BufferGeometry { external class WireframeGeometry(geometry: BufferGeometry) : BufferGeometry
constructor(geometry: Geometry)
constructor(geometry: BufferGeometry)
}

View File

@ -28,12 +28,10 @@
package info.laht.threekt.objects package info.laht.threekt.objects
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Geometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.materials.Material import info.laht.threekt.materials.Material
open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D { open external class LineSegments(geometry: BufferGeometry, material: Material) : Object3D {
constructor(geometry: Geometry, material: Material)
var geometry: BufferGeometry var geometry: BufferGeometry
var material: Material var material: Material

View File

@ -27,16 +27,16 @@
package info.laht.threekt.objects package info.laht.threekt.objects
import info.laht.threekt.core.* 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.materials.Material import info.laht.threekt.materials.Material
open external class Mesh : Object3D { open external class Mesh(geometry: BufferGeometry?, material: Material?) : Object3D {
constructor(geometry: Geometry?, material: Material?) var geometry: BufferGeometry
constructor(geometry: BufferGeometry?, material: Material?)
var geometry: dynamic
var material: Material var material: Material
var drawMode: Int var drawMode: Int

View File

@ -0,0 +1,15 @@
@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<BufferGeometry>, useGroups: Boolean): BufferGeometry
}

View File

@ -30,16 +30,14 @@ public abstract class MeshThreeFactory<in T : Solid>(
override fun invoke(three: ThreePlugin, obj: T): Mesh { override fun invoke(three: ThreePlugin, obj: T): Mesh {
val geometry = buildGeometry(obj) 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 meshMeta: Meta = obj.properties[Material3D.MATERIAL_KEY]?.node ?: Meta.empty
val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply { val mesh = Mesh(geometry, ThreeMaterials.DEFAULT).apply {
matrixAutoUpdate = false matrixAutoUpdate = false
//set position for mesh //set position for mesh
updatePosition(obj) updatePosition(obj)
}.applyProperties(obj) applyProperties(obj)
}
//add listener to object properties //add listener to object properties
obj.onPropertyChange(three.updateScope) { name -> obj.onPropertyChange(three.updateScope) { name ->
@ -76,9 +74,9 @@ internal fun Mesh.applyProperties(obj: Solid): Mesh = apply {
updateMaterial(obj) updateMaterial(obj)
applyEdges(obj) applyEdges(obj)
//applyWireFrame(obj) //applyWireFrame(obj)
layers.enable(obj.layer) layers.set(obj.layer)
children.forEach { children.forEach {
it.layers.enable(obj.layer) it.layers.set(obj.layer)
} }
} }

View File

@ -1,12 +1,12 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import info.laht.threekt.geometries.BoxBufferGeometry import info.laht.threekt.geometries.BoxGeometry
import space.kscience.visionforge.solid.Box import space.kscience.visionforge.solid.Box
import space.kscience.visionforge.solid.detail import space.kscience.visionforge.solid.detail
public object ThreeBoxFactory : MeshThreeFactory<Box>(Box::class) { public object ThreeBoxFactory : MeshThreeFactory<Box>(Box::class) {
override fun buildGeometry(obj: Box): BoxBufferGeometry = override fun buildGeometry(obj: Box): BoxGeometry =
obj.detail?.let { detail -> obj.detail?.let { detail ->
BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize, detail, detail, detail) BoxGeometry(obj.xSize, obj.ySize, obj.zSize, detail, detail, detail)
} ?: BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize) } ?: BoxGeometry(obj.xSize, obj.ySize, obj.zSize)
} }

View File

@ -250,8 +250,11 @@ public class ThreeCanvas(
} }
public fun render(vision: Solid) { public fun render(vision: Solid) {
three.logger.info { "Replacing root node in three canvas" } if (root != null) {
scene.findChild("@root".asName())?.let { scene.remove(it) } three.logger.info { "Replacing root node in three canvas" }
scene.findChild("@root".asName())?.let { scene.remove(it) }
root?.dispose()
}
val object3D = three.buildObject3D(vision) val object3D = three.buildObject3D(vision)
object3D.name = "@root" object3D.name = "@root"

View File

@ -2,7 +2,7 @@ package space.kscience.visionforge.solid.three
import info.laht.threekt.DoubleSide import info.laht.threekt.DoubleSide
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.PlaneBufferGeometry import info.laht.threekt.geometries.PlaneGeometry
import info.laht.threekt.materials.MeshBasicMaterial import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture import info.laht.threekt.textures.Texture
@ -46,7 +46,7 @@ public object ThreeCanvasLabelFactory : ThreeFactory<SolidLabel> {
} }
val mesh = Mesh( val mesh = Mesh(
PlaneBufferGeometry(canvas.width, canvas.height), PlaneGeometry(canvas.width, canvas.height),
material material
) )

View File

@ -1,29 +1,60 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import CSG import CSG
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.Object3D
import info.laht.threekt.objects.Mesh 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.Composite
import space.kscience.visionforge.solid.CompositeType import space.kscience.visionforge.solid.CompositeType
import kotlin.reflect.KClass
/** /**
* This should be inner, because it uses object builder * This should be inner, because it uses object builder
*/ */
public class ThreeCompositeFactory(public val three: ThreePlugin) : MeshThreeFactory<Composite>(Composite::class) { public class ThreeCompositeFactory(public val three: ThreePlugin) : ThreeFactory<Composite> {
override fun buildGeometry(obj: Composite): BufferGeometry { // 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<in Composite> get() = Composite::class
override fun invoke(three: ThreePlugin, obj: Composite): Object3D {
val first = three.buildObject3D(obj.first) as? Mesh ?: error("First part of composite is not a mesh") 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") val second = three.buildObject3D(obj.second) as? Mesh ?: error("Second part of composite is not a mesh")
second.updateMatrix() return when (obj.compositeType) {
val firstCSG = CSG.fromMesh(first) CompositeType.SUM, CompositeType.UNION -> CSG.union(first, second)
val secondCSG = CSG.fromMesh(second) CompositeType.INTERSECT -> CSG.intersect(first, second)
val resultCSG = when (obj.compositeType) { CompositeType.SUBTRACT -> CSG.subtract(first, second)
CompositeType.UNION -> firstCSG.union(secondCSG) }.apply {
CompositeType.INTERSECT -> firstCSG.intersect(secondCSG) updatePosition(obj)
CompositeType.SUBTRACT -> firstCSG.subtract(secondCSG) 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)
}
}
} }
return resultCSG.toGeometry().toBufferGeometry()
} }
} }

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.geometries.CylinderBufferGeometry import info.laht.threekt.geometries.CylinderGeometry
import space.kscience.visionforge.solid.ConeSegment import space.kscience.visionforge.solid.ConeSegment
import space.kscience.visionforge.solid.detail import space.kscience.visionforge.solid.detail
import kotlin.math.PI import kotlin.math.PI
@ -11,7 +11,7 @@ public object ThreeConeFactory : MeshThreeFactory<ConeSegment>(ConeSegment::clas
override fun buildGeometry(obj: ConeSegment): BufferGeometry { override fun buildGeometry(obj: ConeSegment): BufferGeometry {
val cylinder = obj.detail?.let { val cylinder = obj.detail?.let {
val segments = it.toDouble().pow(0.5).toInt() val segments = it.toDouble().pow(0.5).toInt()
CylinderBufferGeometry( CylinderGeometry(
radiusTop = obj.topRadius, radiusTop = obj.topRadius,
radiusBottom = obj.bottomRadius, radiusBottom = obj.bottomRadius,
height = obj.height, height = obj.height,
@ -21,7 +21,7 @@ public object ThreeConeFactory : MeshThreeFactory<ConeSegment>(ConeSegment::clas
thetaStart = obj.startAngle, thetaStart = obj.startAngle,
thetaLength = obj.angle thetaLength = obj.angle
) )
} ?: CylinderBufferGeometry( } ?: CylinderGeometry(
radiusTop = obj.topRadius, radiusTop = obj.topRadius,
radiusBottom = obj.bottomRadius, radiusBottom = obj.bottomRadius,
height = obj.height, height = obj.height,

View File

@ -63,9 +63,7 @@ public fun Object3D.updateProperty(source: Vision, propertyName: Name) {
* Generic factory for elements which provide inside geometry builder * Generic factory for elements which provide inside geometry builder
*/ */
public object ThreeShapeFactory : MeshThreeFactory<GeometrySolid>(GeometrySolid::class) { public object ThreeShapeFactory : MeshThreeFactory<GeometrySolid>(GeometrySolid::class) {
override fun buildGeometry(obj: GeometrySolid): BufferGeometry { override fun buildGeometry(obj: GeometrySolid): BufferGeometry = ThreeGeometryBuilder().apply {
return obj.run { obj.toGeometry(this)
ThreeGeometryBuilder().apply { toGeometry(this) }.build() }.build()
}
}
} }

View File

@ -1,52 +1,63 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Face3 import info.laht.threekt.core.Float32BufferAttribute
import info.laht.threekt.core.Geometry
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
import space.kscience.dataforge.meta.Meta 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.GeometryBuilder
import space.kscience.visionforge.solid.Point3D 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 Point3D.toVector() = Vector3(x, y, z)
internal fun <T> MutableList<T>.add(vararg values: T) {
values.forEach {
add(it)
}
}
/** /**
* An implementation of geometry builder for Three.js [BufferGeometry] * An implementation of geometry builder for Three.js [BufferGeometry]
*/ */
public class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> { public class ThreeGeometryBuilder : GeometryBuilder<BufferGeometry> {
private val vertices = ArrayList<Vector3>() private val indices = ArrayList<Short>()
private val faces = ArrayList<Face3>() private val positions = ArrayList<Float>()
private val normals = ArrayList<Float>()
// private val colors = ArrayList<Float>()
private val vertexCache = HashMap<Point3D, Int>() private val vertexCache = HashMap<Point3D, Short>()
private var counter: Short = -1
private fun append(vertex: Point3D): Int { private fun vertex(vertex: Point3D, normal: Point3D): Short = vertexCache.getOrPut(vertex) {
val index = vertexCache[vertex] ?: -1//vertices.indexOf(vertex) //add vertex and update cache if needed
return if (index > 0) { positions.add(vertex.x, vertex.y, vertex.z)
index normals.add(normal.x, vertex.y, vertex.z)
} else { //colors.add(1f, 1f, 1f)
vertices.add(vertex.toVector()) counter++
vertexCache[vertex] = vertices.size - 1 counter
vertices.size - 1
}
} }
override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) { override fun face(vertex1: Point3D, vertex2: Point3D, vertex3: Point3D, normal: Point3D?, meta: Meta) {
val face = Face3(append(vertex1), append(vertex2), append(vertex3), normal?.toVector() ?: Vector3(0, 0, 0)) val actualNormal: Point3D = normal ?: (vertex3 - vertex2) cross (vertex1 - vertex2)
meta["materialIndex"].int?.let { face.materialIndex = it } indices.add(
meta["color"]?.getColor()?.let { face.color = it } vertex(vertex1, actualNormal),
faces.add(face) vertex(vertex2, actualNormal),
vertex(vertex3, actualNormal)
)
} }
override fun build(): BufferGeometry { override fun build(): BufferGeometry = BufferGeometry().apply {
return Geometry().apply { setIndex(indices.toTypedArray())
vertices = this@ThreeGeometryBuilder.vertices.toTypedArray() setAttribute("position", Float32BufferAttribute(positions.toTypedArray(), 3))
faces = this@ThreeGeometryBuilder.faces.toTypedArray() setAttribute("normal", Float32BufferAttribute(normals.toTypedArray(), 3))
computeBoundingSphere() //setAttribute("color", Float32BufferAttribute(colors.toFloatArray(), 3))
computeFaceNormals() //a temporary fix for CSG problem
}.toBufferGeometry() val uvsArray = Array<Float>((counter+1)*2){0f}
setAttribute("uv", Float32BufferAttribute(uvsArray, 2))
computeBoundingSphere()
} }
} }

View File

@ -6,6 +6,6 @@ import space.kscience.visionforge.solid.SolidBase
/** /**
* A custom visual object that has its own Three.js renderer * A custom visual object that has its own Three.js renderer
*/ */
public abstract class ThreeVision : SolidBase() { public abstract class ThreeJsVision : SolidBase() {
public abstract fun render(three: ThreePlugin): Object3D public abstract fun render(three: ThreePlugin): Object3D
} }

View File

@ -1,6 +1,6 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import info.laht.threekt.core.Geometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.Object3D import info.laht.threekt.core.Object3D
import info.laht.threekt.math.Color import info.laht.threekt.math.Color
import info.laht.threekt.objects.LineSegments import info.laht.threekt.objects.LineSegments
@ -16,8 +16,8 @@ public object ThreeLineFactory : ThreeFactory<PolyLine> {
override val type: KClass<PolyLine> get() = PolyLine::class override val type: KClass<PolyLine> get() = PolyLine::class
override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D { override fun invoke(three: ThreePlugin, obj: PolyLine): Object3D {
val geometry = Geometry().apply { val geometry = BufferGeometry().apply {
vertices = Array(obj.points.size) { obj.points[it].toVector() } setFromPoints(Array(obj.points.size) { obj.points[it].toVector() })
} }
val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true) val material = ThreeMaterials.getLineMaterial(obj.getProperty(MeshThreeFactory.EDGES_MATERIAL_KEY).node, true)

View File

@ -49,7 +49,7 @@ public class ThreePlugin : AbstractPlugin(), ElementVisionRenderer {
} }
public fun buildObject3D(obj: Solid): Object3D = when (obj) { public fun buildObject3D(obj: Solid): Object3D = when (obj) {
is ThreeVision -> obj.render(this) is ThreeJsVision -> obj.render(this)
is SolidReferenceGroup -> ThreeReferenceFactory(this, obj) is SolidReferenceGroup -> ThreeReferenceFactory(this, obj)
is SolidGroup -> { is SolidGroup -> {
val group = ThreeGroup() val group = ThreeGroup()

View File

@ -1,14 +1,14 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import info.laht.threekt.core.BufferGeometry import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.geometries.SphereBufferGeometry import info.laht.threekt.geometries.SphereGeometry
import space.kscience.visionforge.solid.Sphere import space.kscience.visionforge.solid.Sphere
import space.kscience.visionforge.solid.detail import space.kscience.visionforge.solid.detail
public object ThreeSphereFactory : MeshThreeFactory<Sphere>(Sphere::class) { public object ThreeSphereFactory : MeshThreeFactory<Sphere>(Sphere::class) {
override fun buildGeometry(obj: Sphere): BufferGeometry { override fun buildGeometry(obj: Sphere): BufferGeometry {
return obj.detail?.let {detail -> return obj.detail?.let {detail ->
SphereBufferGeometry( SphereGeometry(
radius = obj.radius, radius = obj.radius,
phiStart = obj.phiStart, phiStart = obj.phiStart,
phiLength = obj.phi, phiLength = obj.phi,
@ -17,7 +17,7 @@ public object ThreeSphereFactory : MeshThreeFactory<Sphere>(Sphere::class) {
widthSegments = detail, widthSegments = detail,
heightSegments = detail heightSegments = detail
) )
}?: SphereBufferGeometry( }?: SphereGeometry(
radius = obj.radius, radius = obj.radius,
phiStart = obj.phiStart, phiStart = obj.phiStart,
phiLength = obj.phi, phiLength = obj.phi,

View File

@ -1,49 +1,52 @@
@file:Suppress("INTERFACE_WITH_SUPERCLASS", @file:Suppress(
"INTERFACE_WITH_SUPERCLASS",
"OVERRIDING_FINAL_MEMBER", "OVERRIDING_FINAL_MEMBER",
"RETURN_TYPE_MISMATCH_ON_OVERRIDE", "RETURN_TYPE_MISMATCH_ON_OVERRIDE",
"CONFLICTING_OVERLOADS", "CONFLICTING_OVERLOADS",
"EXTERNAL_DELEGATION") "EXTERNAL_DELEGATION"
)
@file:JsModule("three-csg-ts") @file:JsModule("three-csg-ts")
@file:JsNonModule @file:JsNonModule
import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.math.Matrix4 import info.laht.threekt.math.Matrix4
import info.laht.threekt.math.Vector3 import info.laht.threekt.math.Vector3
import info.laht.threekt.objects.Mesh import info.laht.threekt.objects.Mesh
external open class CSG { public external class CSG {
open fun clone(): CSG public fun clone(): CSG
open fun toPolygons(): Array<Polygon> public fun toPolygons(): Array<Polygon>
open fun union(csg: CSG): CSG public fun toGeometry(toMatrix: Matrix4): BufferGeometry
open fun subtract(csg: CSG): CSG public fun union(csg: CSG): CSG
open fun intersect(csg: CSG): CSG public fun subtract(csg: CSG): CSG
open fun inverse(): CSG public fun intersect(csg: CSG): CSG
public fun inverse(): CSG
companion object {
public companion object {
fun fromPolygons(polygons: Array<Polygon>): CSG fun fromPolygons(polygons: Array<Polygon>): CSG
fun fromGeometry(geom: Any): CSG fun fromGeometry(geom: BufferGeometry, objectIndex: dynamic = definedExternally): CSG
fun fromMesh(mesh: Mesh): CSG fun fromMesh(mesh: Mesh, objectIndex: dynamic = definedExternally): CSG
fun toGeometry(csg: CSG, toMatrix: Matrix4): BufferGeometry
fun toMesh(csg: CSG, toMatrix: Matrix4): Mesh fun toMesh(csg: CSG, toMatrix: Matrix4): Mesh
fun iEval(tokens: Mesh, index: Number? = definedExternally) fun iEval(tokens: Mesh, index: Number? = definedExternally)
fun eval(tokens: Mesh, doRemove: Boolean): Mesh fun eval(tokens: Mesh, doRemove: Boolean): Mesh
var _tmpm3: Any fun union(meshA: Mesh, meshB: Mesh): Mesh
var doRemove: Any fun subtract(meshA: Mesh, meshB: Mesh): Mesh
var currentOp: Any fun intersect(meshA: Mesh, meshB: Mesh): Mesh
var currentPrim: Any
var nextPrim: Any
var sourceMesh: Any
} }
} }
external open class Vector(x: Number, y: Number, z: Number) : Vector3 { external class Vector(x: Number, y: Number, z: Number) : Vector3 {
open fun negated(): Vector fun negated(): Vector
open fun plus(a: Vector): Vector fun plus(a: Vector): Vector
open fun minus(a: Vector): Vector fun minus(a: Vector): Vector
open fun times(a: Number): Vector fun times(a: Number): Vector
open fun dividedBy(a: Number): Vector fun dividedBy(a: Number): Vector
open fun lerp(a: Vector, t: Number): Any fun lerp(a: Vector, t: Number): Any
open fun unit(): Vector fun unit(): Vector
open fun cross(a: Vector): Any fun cross(a: Vector): Any
} }
external interface IVector { external interface IVector {
@ -52,21 +55,21 @@ external interface IVector {
var z: Number var z: Number
} }
external open class Vertex(pos: IVector, normal: IVector, uv: IVector? = definedExternally) { external class Vertex(pos: IVector, normal: IVector, uv: IVector? = definedExternally) {
open var pos: Vector var pos: Vector
open var normal: Vector var normal: Vector
open var uv: Vector var uv: Vector
open fun clone(): Vertex fun clone(): Vertex
open fun flip() fun flip()
open fun interpolate(other: Vertex, t: Number): Vertex fun interpolate(other: Vertex, t: Number): Vertex
} }
external open class Plane(normal: Vector, w: Number) { external class Plane(normal: Vector, w: Number) {
open var normal: Vector var normal: Vector
open var w: Number var w: Number
open fun clone(): Plane fun clone(): Plane
open fun flip() fun flip()
open fun splitPolygon( fun splitPolygon(
polygon: Polygon, polygon: Polygon,
coplanarFront: Array<Polygon>, coplanarFront: Array<Polygon>,
coplanarBack: Array<Polygon>, coplanarBack: Array<Polygon>,
@ -80,10 +83,10 @@ external open class Plane(normal: Vector, w: Number) {
} }
} }
external open class Polygon(vertices: Array<Vertex>, shared: Any? = definedExternally) { external class Polygon(vertices: Array<Vertex>, shared: Any? = definedExternally) {
open var plane: Plane var plane: Plane
open var vertices: Array<Vertex> var vertices: Array<Vertex>
open var shared: Any var shared: Any
open fun clone(): Polygon fun clone(): Polygon
open fun flip() fun flip()
} }

View File

@ -1,7 +1,7 @@
package space.kscience.visionforge.solid.three package space.kscience.visionforge.solid.three
import CSG import info.laht.threekt.core.BufferGeometry
import info.laht.threekt.core.* import info.laht.threekt.core.Layers
import info.laht.threekt.external.controls.OrbitControls import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.materials.Material import info.laht.threekt.materials.Material
import info.laht.threekt.math.Euler import info.laht.threekt.math.Euler
@ -19,53 +19,13 @@ 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 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 internal fun Double.toRadians() = this * PI / 180
public fun CSG.toGeometry(): Geometry {
val geom = Geometry()
val vertices = ArrayList<Vector3>()
val faces = ArrayList<Face3>()
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() { internal fun Any.dispose() {
when (this) { when (this) {
is Geometry -> dispose()
is BufferGeometry -> dispose() is BufferGeometry -> dispose()
is DirectGeometry -> dispose()
is Material -> dispose() is Material -> dispose()
is Mesh -> { is Mesh -> {
geometry.dispose() geometry.dispose()