From ec01b0d7a813ec6eb09d053d66733a5e767a8156 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Wed, 6 Mar 2019 18:15:30 +0300 Subject: [PATCH] JS 3d Visualization --- build.gradle.kts | 26 +++- dataforge-meta-io/build.gradle | 59 -------- dataforge-meta-io/build.gradle.kts | 57 +++++++ dataforge-vis/build.gradle.kts | 8 + .../kotlin/hep/dataforge/vis/FXProperties.kt | 23 --- .../vis/spatial/FXSpatialRenderer.kt | 48 ------ .../dataforge-vis-js/build.gradle.kts | 42 ------ .../build.gradle.kts | 2 +- .../vis/DisplayObjectPropertyListener.kt | 3 - .../hep/dataforge/vis/spatial/Canvas3D.kt | 23 ++- .../hep/dataforge/vis/spatial/FX3DOutput.kt | 54 +++++++ .../hep/dataforge/vis/spatial/Materials.kt | 63 ++++++++ .../dataforge/vis/spatial/RendererDemoApp.kt | 11 +- .../dataforge-vis-spatial-js/build.gradle.kts | 59 ++++++++ .../package.json.d/package.info | 3 + .../src/main/kotlin/hep/dataforge/vis/HMR.kt | 19 +++ .../src/main/kotlin/hep/dataforge/vis/main.kt | 50 +++++++ .../hep/dataforge/vis/spatial/ThreeDemoApp.kt | 64 ++++++++ .../hep/dataforge/vis/spatial/ThreeOutput.kt | 140 ++++++++++++++++++ .../kotlin/hep/dataforge/vis/spatial/three.kt | 13 ++ .../src/main/web/index.html | 19 +++ .../webpack.config.d/css.js | 1 + .../dataforge-vis-spatial/build.gradle.kts | 24 ++- .../kotlin/hep/dataforge/vis/spatial/Box.kt | 2 +- .../dataforge/vis/spatial/DisplayObject3D.kt | 24 ++- .../hep/dataforge/vis/spatial/Extruded.kt | 17 +++ .../dataforge/vis/DisplayObjectDelegates.kt | 24 ++- settings.gradle.kts | 11 +- 28 files changed, 682 insertions(+), 207 deletions(-) delete mode 100644 dataforge-meta-io/build.gradle create mode 100644 dataforge-meta-io/build.gradle.kts delete mode 100644 dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/FXProperties.kt delete mode 100644 dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt delete mode 100644 dataforge-vis/dataforge-vis-js/build.gradle.kts rename dataforge-vis/{dataforge-vis-fx => dataforge-vis-spatial-fx}/build.gradle.kts (89%) rename dataforge-vis/{dataforge-vis-fx => dataforge-vis-spatial-fx}/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt (93%) rename dataforge-vis/{dataforge-vis-fx => dataforge-vis-spatial-fx}/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt (87%) create mode 100644 dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt create mode 100644 dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt rename dataforge-vis/{dataforge-vis-fx => dataforge-vis-spatial-fx}/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt (82%) create mode 100644 dataforge-vis/dataforge-vis-spatial-js/build.gradle.kts create mode 100644 dataforge-vis/dataforge-vis-spatial-js/package.json.d/package.info create mode 100644 dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt create mode 100644 dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt create mode 100644 dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt create mode 100644 dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt create mode 100644 dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three.kt create mode 100644 dataforge-vis/dataforge-vis-spatial-js/src/main/web/index.html create mode 100644 dataforge-vis/dataforge-vis-spatial-js/webpack.config.d/css.js create mode 100644 dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt diff --git a/build.gradle.kts b/build.gradle.kts index 7b8168f8..f172e46d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -58,6 +58,28 @@ subprojects { } } } + + js{ + configure(listOf(compilations["main"], compilations["test"])) { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + metaInfo = true + sourceMap = true + sourceMapEmbedSources = "always" + moduleKind = "umd" + } + } + } + + configure(listOf(compilations["main"])) { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + main = "call" + } + } + } + } + targets.all { sourceSets.all { languageSettings.progressiveMode = true @@ -119,6 +141,4 @@ subprojects { } } -} - - +} \ No newline at end of file diff --git a/dataforge-meta-io/build.gradle b/dataforge-meta-io/build.gradle deleted file mode 100644 index 2b1b3ffa..00000000 --- a/dataforge-meta-io/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -plugins { - id "org.jetbrains.kotlin.multiplatform" -} - -description = "IO for meta" - -kotlin { - targets { - fromPreset(presets.jvm, 'jvm') - fromPreset(presets.js, 'js') - // For ARM, preset should be changed to presets.iosArm32 or presets.iosArm64 - // For Linux, preset should be changed to e.g. presets.linuxX64 - // For MacOS, preset should be changed to e.g. presets.macosX64 - //fromPreset(presets.iosX64, 'ios') - } - sourceSets { - commonMain { - dependencies { - api project(":dataforge-meta") - //implementation 'org.jetbrains.kotlin:kotlin-reflect' - api "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serializationVersion" - api "org.jetbrains.kotlinx:kotlinx-io:$ioVersion" - } - } - commonTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test-common' - implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common' - } - } - jvmMain { - dependencies { - api "org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion" - api "org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion" - } - } - jvmTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test' - implementation 'org.jetbrains.kotlin:kotlin-test-junit' - } - } - jsMain { - dependencies { - api "org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serializationVersion" - api "org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion" - } - } - jsTest { - dependencies { - implementation 'org.jetbrains.kotlin:kotlin-test-js' - } - } -// iosMain { -// } -// iosTest { -// } - } -} \ No newline at end of file diff --git a/dataforge-meta-io/build.gradle.kts b/dataforge-meta-io/build.gradle.kts new file mode 100644 index 00000000..1b57774d --- /dev/null +++ b/dataforge-meta-io/build.gradle.kts @@ -0,0 +1,57 @@ +plugins { + kotlin("multiplatform") +} + +description = "IO for meta" + + +val ioVersion: String by rootProject.extra +val serializationVersion: String by rootProject.extra + +kotlin { + jvm() + js() + sourceSets { + val commonMain by getting{ + dependencies { + api(project(":dataforge-meta")) + //implementation 'org.jetbrains.kotlin:kotlin-reflect' + api("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$serializationVersion") + api("org.jetbrains.kotlinx:kotlinx-io:$ioVersion") + } + } + val commonTest by getting { + dependencies { + implementation("org.jetbrains.kotlin:kotlin-test-common") + implementation("org.jetbrains.kotlin:kotlin-test-annotations-common") + } + } + val jvmMain by getting { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion") + api("org.jetbrains.kotlinx:kotlinx-io-jvm:$ioVersion") + } + } + val jvmTest by getting { + dependencies { + implementation("org.jetbrains.kotlin:kotlin-test") + implementation("org.jetbrains.kotlin:kotlin-test-junit") + } + } + val jsMain by getting { + dependencies { + api("org.jetbrains.kotlinx:kotlinx-serialization-runtime-js:$serializationVersion") + api("org.jetbrains.kotlinx:kotlinx-io-js:$ioVersion") + } + } + val jsTest by getting { + dependencies { + implementation("org.jetbrains.kotlin:kotlin-test-js") + } + } +// iosMain { +// } +// iosTest { +// } + } +} \ No newline at end of file diff --git a/dataforge-vis/build.gradle.kts b/dataforge-vis/build.gradle.kts index 9ef8235b..8b93e57c 100644 --- a/dataforge-vis/build.gradle.kts +++ b/dataforge-vis/build.gradle.kts @@ -1,5 +1,8 @@ +import org.openjfx.gradle.JavaFXOptions + plugins { kotlin("multiplatform") + id("org.openjfx.javafxplugin") } kotlin { @@ -14,6 +17,7 @@ kotlin { } val jvmMain by getting { dependencies { + //api("no.tornado:tornadofx:1.7.18") } } val jsMain by getting { @@ -21,4 +25,8 @@ kotlin { } } } +} + +configure{ + modules("javafx.controls") } \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/FXProperties.kt b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/FXProperties.kt deleted file mode 100644 index 79ea2d55..00000000 --- a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/FXProperties.kt +++ /dev/null @@ -1,23 +0,0 @@ -package hep.dataforge.vis - -import javafx.scene.paint.Color -import javafx.scene.paint.PhongMaterial - -object Materials{ - val RED = PhongMaterial().apply { - diffuseColor = Color.DARKRED - specularColor = Color.RED - } - - val WHITE = PhongMaterial().apply { - diffuseColor = Color.WHITE - specularColor = Color.LIGHTBLUE - } - - val GREY = PhongMaterial().apply { - diffuseColor = Color.DARKGREY - specularColor = Color.GREY - } - - val BLUE = PhongMaterial(Color.BLUE) -} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt b/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt deleted file mode 100644 index 4456cd76..00000000 --- a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/FXSpatialRenderer.kt +++ /dev/null @@ -1,48 +0,0 @@ -package hep.dataforge.vis.spatial - -import hep.dataforge.context.Context -import hep.dataforge.io.Output -import hep.dataforge.meta.Meta -import hep.dataforge.meta.int -import hep.dataforge.vis.DisplayGroup -import hep.dataforge.vis.DisplayObject -import hep.dataforge.vis.DisplayObjectPropertyListener -import hep.dataforge.vis.transform -import javafx.scene.Group -import javafx.scene.Node -import javafx.scene.paint.Color -import org.fxyz3d.geometry.Point3D -import org.fxyz3d.shapes.primitives.CuboidMesh - - -/** - * https://github.com/miho/JCSG for operations - * - */ -class FXSpatialRenderer(override val context: Context) : Output { - - val canvas by lazy { Canvas3D() } - - private fun buildObject(obj: DisplayObject): Node { - return when (obj) { - is DisplayGroup -> Group(obj.children.map { buildObject(it) }) - is Box -> CuboidMesh(obj.xSize, obj.ySize, obj.zSize).apply { - val listener = DisplayObjectPropertyListener(obj) - this.center = Point3D(obj.x.toFloat(), obj.y.toFloat(), obj.z.toFloat()) - this.diffuseColorProperty().bind(listener["color"].transform { - //TODO Move to extension - val int = it.int ?: 0 - val red = int and 0x00ff0000 shr 16 - val green = int and 0x0000ff00 shr 8 - val blue = int and 0x000000ff - return@transform Color.rgb(red, green, blue) - }) - } - else -> TODO() - } - } - - override fun render(obj: DisplayObject, meta: Meta) { - canvas.world.children.add(buildObject(obj)) - } -} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-js/build.gradle.kts b/dataforge-vis/dataforge-vis-js/build.gradle.kts deleted file mode 100644 index f5e385a6..00000000 --- a/dataforge-vis/dataforge-vis-js/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -plugins{ - kotlin("js") - id("kotlin") -} - -// configure(listOf(compilations.main, compilations.test)) { -// tasks.getByName(compileKotlinTaskName).kotlinOptions { -// sourceMap = true -// moduleKind = "umd" -// metaInfo = true -// } -// } -// -// configure(compilations.main) { -// tasks.getByName(compileKotlinTaskName).kotlinOptions { -// main = "call" -// } -// } - -dependencies { - implementation("info.laht.threekt:threejs-wrapper:0.88-npm-1") -} - -extensions.findByType()?.apply { - extensions.findByType()?.apply { - dependency("three") - dependency("three-orbitcontrols") - devDependency("karma") - - } - - sourceMaps = true - - bundle("webpack") { - this as WebPackExtension - bundleName = "main" - proxyUrl = "http://localhost:8080" - contentPath = file("src/main/web") - sourceMapEnabled = true - mode = "development" - } -} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-fx/build.gradle.kts b/dataforge-vis/dataforge-vis-spatial-fx/build.gradle.kts similarity index 89% rename from dataforge-vis/dataforge-vis-fx/build.gradle.kts rename to dataforge-vis/dataforge-vis-spatial-fx/build.gradle.kts index 260b8622..3b57a761 100644 --- a/dataforge-vis/dataforge-vis-fx/build.gradle.kts +++ b/dataforge-vis/dataforge-vis-spatial-fx/build.gradle.kts @@ -12,7 +12,7 @@ dependencies{ implementation("org.fxyz3d:fxyz3d:0.4.0") } -extensions.findByType()?.apply { +configure { modules("javafx.controls") } diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt similarity index 93% rename from dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt rename to dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt index 63eb25c5..44214460 100644 --- a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt +++ b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectPropertyListener.kt @@ -1,8 +1,5 @@ package hep.dataforge.vis -import hep.dataforge.meta.* -import hep.dataforge.names.Name -import hep.dataforge.names.toName import javafx.beans.binding.ObjectBinding import tornadofx.* diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt similarity index 87% rename from dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt rename to dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt index 60b6b6dd..bca2ab76 100644 --- a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt +++ b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt @@ -19,21 +19,34 @@ class Canvas3D : Fragment() { translateZ = CAMERA_INITIAL_DISTANCE } - //TODO move up - val cameraShift = CameraTransformer().apply { + private val cameraShift = CameraTransformer().apply { val cameraFlip = CameraTransformer() cameraFlip.children.add(camera) cameraFlip.setRotateZ(180.0) children.add(cameraFlip) } - val cameraRotation = CameraTransformer().apply { + val translationXProperty get() = cameraShift.t.xProperty() + var translateX by translationXProperty + val translationYProperty get() = cameraShift.t.yProperty() + var translateY by translationYProperty + val translationZProperty get() = cameraShift.t.zProperty() + var translateZ by translationZProperty + + private val cameraRotation = CameraTransformer().apply { children.add(cameraShift) ry.angle = CAMERA_INITIAL_Y_ANGLE rx.angle = CAMERA_INITIAL_X_ANGLE rz.angle = CAMERA_INITIAL_Z_ANGLE } + val rotationXProperty get() = cameraRotation.rx.angleProperty() + var angleX by rotationXProperty + val rotationYProperty get() = cameraRotation.ry.angleProperty() + var angleY by rotationYProperty + val rotationZProperty get() = cameraRotation.rz.angleProperty() + var angleZ by rotationZProperty + override val root =borderpane { center = SubScene( @@ -122,10 +135,6 @@ class Canvas3D : Fragment() { cameraRotation.rz.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED cameraRotation.rx.angle = cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED - // } else if (me.isSecondaryButtonDown()) { - // double z = camera.getTranslateZ(); - // double newZ = z + mouseDeltaX * MOUSE_SPEED * modifier*100; - // camera.setTranslateZ(newZ); } else if (me.isSecondaryButtonDown) { cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED diff --git a/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt new file mode 100644 index 00000000..2b19bd45 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt @@ -0,0 +1,54 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.vis.DisplayObjectPropertyListener +import javafx.scene.Group +import javafx.scene.Node +import org.fxyz3d.shapes.primitives.CuboidMesh +import tornadofx.* + +/** + * https://github.com/miho/JCSG for operations + * + */ +class FX3DOutput(override val context: Context) : Output { + val canvas by lazy { Canvas3D() } + + + private fun buildNode(obj: Any): Node? { + return when (obj) { + is DisplayShape3D -> { + val listener = DisplayObjectPropertyListener(obj) + val x = listener["x"].float() + val y = listener["y"].float() + val z = listener["z"].float() + val center = objectBinding(x, y, z) { + Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f) + } + when (obj) { + is DisplayGroup3D -> Group(obj.children.map { buildNode(it) }).apply { + this.translateXProperty().bind(x) + this.translateYProperty().bind(y) + this.translateZProperty().bind(z) + } + is Box -> CuboidMesh(obj.xSize, obj.ySize, obj.zSize).apply { + this.centerProperty().bind(center) + this.materialProperty().bind(listener["color"].transform { it.material() }) + } + else -> { + logger.error { "No renderer defined for ${obj::class}" } + null + } + } + } + is DisplayGroup -> Group(obj.children.map { buildNode(it) }) // a logical group + else -> { + logger.error { "No renderer defined for ${obj::class}" } + null + } + } + } + + override fun render(obj: Any, meta: Meta) { + buildNode(obj)?.let { canvas.world.children.add(it) } + } +} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt new file mode 100644 index 00000000..911bb503 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt @@ -0,0 +1,63 @@ +package hep.dataforge.vis.spatial + +import javafx.scene.paint.Color +import javafx.scene.paint.Material +import javafx.scene.paint.PhongMaterial + +object Materials { + val RED = PhongMaterial().apply { + diffuseColor = Color.DARKRED + specularColor = Color.RED + } + + val WHITE = PhongMaterial().apply { + diffuseColor = Color.WHITE + specularColor = Color.LIGHTBLUE + } + + val GREY = PhongMaterial().apply { + diffuseColor = Color.DARKGREY + specularColor = Color.GREY + } + + val BLUE = PhongMaterial(Color.BLUE) +} + +/** + * Infer color based on meta item + */ +fun MetaItem<*>.color(): Color { + return when (this) { + is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) { + Color.web(this.value.string) + } else { + val int = value.number.toInt() + val red = int and 0x00ff0000 shr 16 + val green = int and 0x0000ff00 shr 8 + val blue = int and 0x000000ff + Color.rgb(red, green, blue) + } + is MetaItem.NodeItem -> { + Color.rgb( + node["red"]?.int ?: 0, + node["green"]?.int ?: 0, + node["blue"]?.int ?: 0, + node["opacity"]?.double ?: 1.0 + ) + } + } +} + +/** + * Infer FX material based on meta item + */ +fun MetaItem<*>.material(): Material { + return when (this) { + is MetaItem.ValueItem -> PhongMaterial(color()) + is MetaItem.NodeItem -> PhongMaterial().apply { + node["color"]?.let { diffuseColor = it.color() } + node["specularColor"]?.let { specularColor = it.color() } + } + } +} + diff --git a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt similarity index 82% rename from dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt rename to dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt index 26a02d09..b339bd9e 100644 --- a/dataforge-vis/dataforge-vis-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt +++ b/dataforge-vis/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt @@ -1,8 +1,5 @@ package hep.dataforge.vis.spatial -import hep.dataforge.context.Global -import hep.dataforge.meta.number -import hep.dataforge.vis.DisplayGroup import javafx.scene.Parent import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay @@ -16,7 +13,7 @@ class RendererDemoApp : App(RendererDemoView::class) class RendererDemoView : View() { - val renderer = FXSpatialRenderer(Global) + val renderer = FX3DOutput(Global) override val root: Parent = borderpane { center = renderer.canvas.root } @@ -51,9 +48,9 @@ class RendererDemoView : View() { } } - renderer.canvas.cameraRotation.apply { - ry.angle = -30.0 - rx.angle = -15.0 + renderer.canvas.apply { + angleY = -30.0 + angleX = -15.0 } } } diff --git a/dataforge-vis/dataforge-vis-spatial-js/build.gradle.kts b/dataforge-vis/dataforge-vis-spatial-js/build.gradle.kts new file mode 100644 index 00000000..1336ce78 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/build.gradle.kts @@ -0,0 +1,59 @@ +import org.jetbrains.kotlin.gradle.frontend.KotlinFrontendExtension +import org.jetbrains.kotlin.gradle.frontend.npm.NpmExtension +import org.jetbrains.kotlin.gradle.frontend.webpack.WebPackExtension + +plugins { + id("kotlin2js") + id("kotlin-dce-js") + id("org.jetbrains.kotlin.frontend") +} + + +dependencies { + api(project(":dataforge-vis:dataforge-vis-spatial")) + implementation("info.laht.threekt:threejs-wrapper:0.88-npm-1") +} + +configure { + downloadNodeJsVersion = "latest" + + configure { + dependency("three") + dependency("three-orbitcontrols") + dependency("style-loader") + devDependency("karma") + } + + sourceMaps = true + + bundle("webpack") { + this as WebPackExtension + bundleName = "main" + proxyUrl = "http://localhost:8080" + contentPath = file("src/main/web") + sourceMapEnabled = true + //mode = "production" + mode = "development" + } +} + +tasks{ + compileKotlin2Js{ + kotlinOptions{ + metaInfo = true + outputFile = "${project.buildDir.path}/js/${project.name}.js" + sourceMap = true + moduleKind = "umd" + main = "call" + } + } + + compileTestKotlin2Js{ + kotlinOptions{ + metaInfo = true + outputFile = "${project.buildDir.path}/js/${project.name}-test.js" + sourceMap = true + moduleKind = "umd" + } + } +} diff --git a/dataforge-vis/dataforge-vis-spatial-js/package.json.d/package.info b/dataforge-vis/dataforge-vis-spatial-js/package.json.d/package.info new file mode 100644 index 00000000..e2d5f174 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/package.json.d/package.info @@ -0,0 +1,3 @@ +{ + "description": "A demo project for particle visualization in JS" +} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt new file mode 100644 index 00000000..a77b9cf6 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt @@ -0,0 +1,19 @@ +package hep.dataforge.vis.hmr + +external val module: Module + +external interface Module { + val hot: Hot? +} + +external interface Hot { + val data: dynamic + + fun accept() + fun accept(dependency: String, callback: () -> Unit) + fun accept(dependencies: Array, callback: (updated: Array) -> Unit) + + fun dispose(callback: (data: dynamic) -> Unit) +} + +external fun require(name: String): dynamic \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt new file mode 100644 index 00000000..f4359987 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt @@ -0,0 +1,50 @@ +package hep.dataforge.vis + +import hep.dataforge.vis.hmr.module +import hep.dataforge.vis.spatial.ThreeDemoApp +import kotlin.browser.document +import kotlin.dom.hasClass + + +abstract class ApplicationBase { + abstract val stateKeys: List + + abstract fun start(state: Map) + abstract fun dispose(): Map +} + + +fun main() { + var application: ApplicationBase? = null + + val state: dynamic = module.hot?.let { hot -> + hot.accept() + + hot.dispose { data -> + data.appState = application?.dispose() + application = null + } + + hot.data + } + + if (document.body != null) { + application = start(state) + } else { + application = null + document.addEventListener("DOMContentLoaded", { application = start(state) }) + } +} + +fun start(state: dynamic): ApplicationBase? { + return if (document.body?.hasClass("testApp") == true) { + val application = ThreeDemoApp() + + @Suppress("UnsafeCastFromDynamic") + application.start(state?.appState ?: emptyMap()) + + application + } else { + null + } +} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt new file mode 100644 index 00000000..5cab86a2 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt @@ -0,0 +1,64 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.context.Global +import hep.dataforge.meta.number +import hep.dataforge.vis.ApplicationBase +import hep.dataforge.vis.DisplayGroup +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlin.browser.document +import kotlin.random.Random + + +class ThreeDemoApp : ApplicationBase() { + + override val stateKeys: List = emptyList() + + override fun start(state: Map) { + println("started") + val renderer = ThreeOutput(Global) + document.getElementById("canvas")?.appendChild(renderer.root) + + lateinit var group: DisplayGroup + + renderer.render { + group = group { + box { + xSize = 100.0 + ySize = 100.0 + zSize = 100.0 + } + box { + x = 110.0 + xSize = 100.0 + ySize = 100.0 + zSize = 100.0 + } + } + } + + var color by group.properties.number(1530).int + + GlobalScope.launch { + val random = Random(111) + while (isActive) { + delay(1000) + color = random.nextInt(0, Int.MAX_VALUE) + } + } + +// view.animate() + +// view = WebLinesView(document.getElementById("lines")!!, document.getElementById("addForm")!!) +// presenter = LinesPresenter(view) +// +// state["lines"]?.let { linesState -> +// @Suppress("UNCHECKED_CAST") +// presenter.restore(linesState as Array) +// } + } + + override fun dispose() = emptyMap()//mapOf("lines" to presenter.dispose()) +} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt new file mode 100644 index 00000000..4afc32cc --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt @@ -0,0 +1,140 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.context.Context +import hep.dataforge.io.Output +import hep.dataforge.meta.Meta +import hep.dataforge.vis.DisplayGroup +import info.laht.threekt.WebGLRenderer +import info.laht.threekt.cameras.PerspectiveCamera +import info.laht.threekt.core.Object3D +import info.laht.threekt.external.controls.OrbitControls +import info.laht.threekt.geometries.BoxBufferGeometry +import info.laht.threekt.lights.AmbientLight +import info.laht.threekt.materials.MeshPhongMaterial +import info.laht.threekt.math.ColorConstants +import info.laht.threekt.objects.Mesh +import info.laht.threekt.scenes.Scene +import kotlin.browser.window + +class ThreeOutput(override val context: Context) : Output { + + private val renderer = WebGLRenderer { antialias = true }.apply { + setClearColor(ColorConstants.skyblue, 1) + setSize(window.innerWidth, window.innerHeight) + } + + private val scene: Scene = Scene().apply { + add(AmbientLight()) + } + private val camera = PerspectiveCamera( + 75, + window.innerWidth.toDouble() / window.innerHeight, + 0.1, + 10000 + ).apply { + position.z = 4500.0 + } + + private val controls: OrbitControls = OrbitControls(camera, renderer.domElement) + + val root by lazy { + window.addEventListener("resize", { + camera.aspect = window.innerWidth.toDouble() / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight) + }, false) + renderer.domElement + } + + + private fun buildNode(obj: Any): Object3D? { + return when (obj) { + is DisplayShape3D -> { +// val listener = DisplayObjectPropertyListener(obj) +// val x = listener["x"].float() +// val y = listener["y"].float() +// val z = listener["z"].float() +// val center = objectBinding(x, y, z) { +// Vector3(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f) +// } + when (obj) { + is DisplayGroup3D -> Group(obj.children.mapNotNull { buildNode(it) }).apply { + this.translateX(obj.x) + this.translateY(obj.y) + this.translateZ(obj.z) + } + is Box -> { + val geometry = BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize) + .translate(obj.x, obj.y, obj.z) + val material = MeshPhongMaterial().apply { + this.color.set(ColorConstants.darkgreen) + } + Mesh(geometry, material) + } + else -> { + logger.error { "No renderer defined for ${obj::class}" } + null + } + } + } + is DisplayGroup -> Group(obj.children.mapNotNull { buildNode(it) }) // a logical group + else -> { + logger.error { "No renderer defined for ${obj::class}" } + null + } + } + } + + override fun render(obj: Any, meta: Meta) { + buildNode(obj)?.let { scene.add(it) } + } + + // init { +// val cube: Mesh + +// cube = Mesh( +// BoxBufferGeometry(1, 1, 1), +// MeshPhongMaterial().apply { +// this.color.set(ColorConstants.darkgreen) +// } +// ).also(scene::add) +// +// Mesh(cube.geometry as BufferGeometry, +// MeshBasicMaterial().apply { +// this.wireframe = true +// this.color.set(ColorConstants.black) +// } +// ).also(cube::add) +// +// val points = CatmullRomCurve3( +// arrayOf( +// Vector3(-10, 0, 10), +// Vector3(-5, 5, 5), +// Vector3(0, 0, 0), +// Vector3(5, -5, 5), +// Vector3(10, 0, 10) +// ) +// ).getPoints(50) +// +// val geometry = BufferGeometry().setFromPoints(points) +// +// val material = LineBasicMaterial().apply { +// color.set(0xff0000) +// } +// +// // Create the final object to add to the scene +// Line(geometry, material).apply(scene::add) + +// } + +// fun animate() { +// window.requestAnimationFrame { +// cube.rotation.x += 0.01 +// cube.rotation.y += 0.01 +// animate() +// } +// renderer.render(scene, camera) +// } + +} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three.kt b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three.kt new file mode 100644 index 00000000..f35d92c4 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three.kt @@ -0,0 +1,13 @@ +package hep.dataforge.vis.spatial + +import info.laht.threekt.core.Object3D + +/** + * Utility methods for three.kt. + * TODO move to three project + */ + +@Suppress("FunctionName") +fun Group(children: Collection) = info.laht.threekt.objects.Group().apply { + children.forEach { this.add(it) } +} \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-js/src/main/web/index.html b/dataforge-vis/dataforge-vis-spatial-js/src/main/web/index.html new file mode 100644 index 00000000..27a1aae4 --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/src/main/web/index.html @@ -0,0 +1,19 @@ + + + + Three js demo for particle physics + + + + + + +

Demo canvas

+
+ + \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial-js/webpack.config.d/css.js b/dataforge-vis/dataforge-vis-spatial-js/webpack.config.d/css.js new file mode 100644 index 00000000..f15a9f5e --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial-js/webpack.config.d/css.js @@ -0,0 +1 @@ +config.module.rules.push({ test: /\.css$/, loader: "style!css" }); \ No newline at end of file diff --git a/dataforge-vis/dataforge-vis-spatial/build.gradle.kts b/dataforge-vis/dataforge-vis-spatial/build.gradle.kts index c76b3f57..f1d101ae 100644 --- a/dataforge-vis/dataforge-vis-spatial/build.gradle.kts +++ b/dataforge-vis/dataforge-vis-spatial/build.gradle.kts @@ -4,7 +4,26 @@ plugins { kotlin { jvm() - js() + js { + configure(listOf(compilations["main"], compilations["test"])) { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + metaInfo = true + sourceMap = true + sourceMapEmbedSources = "always" + moduleKind = "umd" + } + } + } + + configure(listOf(compilations["main"])) { + tasks.getByName(compileKotlinTaskName) { + kotlinOptions { + main = "call" + } + } + } + } sourceSets { val commonMain by getting { @@ -23,4 +42,5 @@ kotlin { } } } -} \ No newline at end of file +} + diff --git a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt index 5c0db659..c46b11e6 100644 --- a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt +++ b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt @@ -6,7 +6,7 @@ import hep.dataforge.vis.DisplayGroup import hep.dataforge.vis.DisplayObject import hep.dataforge.vis.double -class Box(parent: DisplayObject?, meta: Meta) : DisplayObject3D(parent, TYPE, meta) { +class Box(parent: DisplayObject?, meta: Meta) : DisplayShape3D(parent, TYPE, meta) { var xSize by double(1.0) var ySize by double(1.0) var zSize by double(1.0) diff --git a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt index be72fbd8..29043c88 100644 --- a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt +++ b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/DisplayObject3D.kt @@ -7,18 +7,32 @@ import hep.dataforge.vis.* import hep.dataforge.vis.DisplayObject.Companion.DEFAULT_TYPE -open class DisplayObject3D(parent: DisplayObject?, type: String, meta: Meta) : DisplayLeaf(parent, type, meta) { - var x by double(0.0) - var y by double(0.0) - var z by double(0.0) +interface DisplayObject3D : DisplayObject { + val x: Double + val y: Double + val z: Double companion object { const val TYPE = "geometry.spatial" } } +open class DisplayShape3D(parent: DisplayObject?, type: String, meta: Meta) : + DisplayLeaf(parent, type, meta), DisplayObject3D { + override var x by double(0.0, inherited = false) + override var y by double(0.0, inherited = false) + override var z by double(0.0, inherited = false) +} + +class DisplayGroup3D(parent: DisplayObject?, type: String, meta: Meta) : DisplayNode(parent, type, meta), + DisplayObject3D { + override var x by double(0.0, inherited = false) + override var y by double(0.0, inherited = false) + override var z by double(0.0, inherited = false) +} + fun DisplayGroup.group(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit = {}) = - DisplayNode(this, DEFAULT_TYPE, meta).apply(action).also{addChild(it)} + DisplayNode(this, DEFAULT_TYPE, meta).apply(action).also { addChild(it) } fun Output.render(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit) = diff --git a/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt new file mode 100644 index 00000000..1c693bfc --- /dev/null +++ b/dataforge-vis/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt @@ -0,0 +1,17 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.EmptyMeta +import hep.dataforge.meta.Meta +import hep.dataforge.vis.DisplayGroup +import hep.dataforge.vis.DisplayObject + +class Extruded(parent: DisplayObject?, meta: Meta) : DisplayShape3D(parent, TYPE, meta) { + + + companion object { + const val TYPE = "geometry.spatial.extruded" + } +} + +fun DisplayGroup.extrude(meta: Meta = EmptyMeta, action: Extruded.() -> Unit = {}) = + Extruded(this, meta).apply(action).also { addChild(it) } \ No newline at end of file diff --git a/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt b/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt index d99ad547..0c0fdb36 100644 --- a/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt +++ b/dataforge-vis/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt @@ -5,6 +5,7 @@ import hep.dataforge.names.Name import hep.dataforge.names.toName import hep.dataforge.values.Value import kotlin.jvm.JvmName +import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -95,4 +96,25 @@ fun DisplayObject.double(default: Double, key: String? = null, inherited: Boolea DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.double } inline fun > DisplayObject.enum(default: E, key: String? = null, inherited: Boolean = true) = - DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { item -> item.string?.let { enumValueOf(it) } } \ No newline at end of file + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { item -> item.string?.let { enumValueOf(it) } } + +//merge properties + +fun DisplayObject.merge( + key: String? = null, + transformer: (Sequence>) -> T +): ReadOnlyProperty { + return object : ReadOnlyProperty { + override fun getValue(thisRef: DisplayObject, property: KProperty<*>): T { + val name = key?.toName() ?: property.name.toName() + val sequence = sequence> { + var thisObj: DisplayObject? = thisRef + while (thisObj != null) { + thisObj.properties[name]?.let { yield(it) } + thisObj = thisObj.parent + } + } + return transformer(sequence) + } + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 662388fe..c5175c2f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,16 +1,16 @@ pluginManagement { repositories { - mavenCentral() jcenter() gradlePluginPortal() - maven("https://dl.bintray.com/kotlin/kotlin-eap/") + maven("https://dl.bintray.com/kotlin/kotlin-eap") } resolutionStrategy { eachPlugin { when (requested.id.id) { "kotlinx-atomicfu" -> useModule("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${requested.version}") "kotlin-multiplatform" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") - "org.jetbrains.kotlin.frontend" -> useModule("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") + "kotlin2js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") + "org.jetbrains.kotlin.frontend" -> useModule("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") } } } @@ -29,5 +29,6 @@ include( ":dataforge-scripting", ":dataforge-vis", ":dataforge-vis:dataforge-vis-spatial", - ":dataforge-vis:dataforge-vis-fx" -) + ":dataforge-vis:dataforge-vis-spatial-fx", + ":dataforge-vis:dataforge-vis-spatial-js" +) \ No newline at end of file