commit 4db090c24017e92a01328cde77cc6b67a501f8c4 Author: Alexander Nozik Date: Fri Mar 8 11:55:01 2019 +0300 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..89cc712a --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ + +.idea/ +*.iws +out/ +.gradle +build/ + + +!gradle-wrapper.jar +gradle.properties \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 00000000..c3b3cadc --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,145 @@ +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +buildscript { + val kotlinVersion: String by rootProject.extra("1.3.21") + val ioVersion: String by rootProject.extra("0.1.5") + val coroutinesVersion: String by rootProject.extra("1.1.1") + val atomicfuVersion: String by rootProject.extra("0.12.1") + val dokkaVersion: String by rootProject.extra("0.9.17") + val serializationVersion: String by rootProject.extra("0.10.0") + + val dataforgeVersion: String by rootProject.extra("0.1.1-dev-5") + + repositories { + jcenter() + maven("https://dl.bintray.com/kotlin/kotlin-eap") + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + classpath("org.jfrog.buildinfo:build-info-extractor-gradle:4+") + classpath("com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4") + classpath("org.jetbrains.dokka:dokka-gradle-plugin:$dokkaVersion") + classpath("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") + classpath("org.openjfx:javafx-plugin:0.0.7") + } +} + +plugins { + id("com.jfrog.artifactory") version "4.8.1" apply false +// id("org.jetbrains.kotlin.multiplatform") apply false +} + +allprojects { + apply(plugin = "maven") + apply(plugin = "maven-publish") + apply(plugin = "com.jfrog.artifactory") + + repositories { + jcenter() + maven("https://kotlin.bintray.com/kotlinx") + maven("http://npm.mipt.ru:8081/artifactory/gradle-dev") + } + + group = "hep.dataforge" + version = "0.0.1-dev-1" + + // apply bintray configuration + apply(from = "${rootProject.rootDir}/gradle/bintray.gradle") + + //apply artifactory configuration + apply(from = "${rootProject.rootDir}/gradle/artifactory.gradle") + +} + +subprojects { + + // dokka { +// outputFormat = "html" +// outputDirectory = javadoc.destinationDir +// } +// +// task dokkaJar (type: Jar, dependsOn: dokka) { +// from javadoc . destinationDir +// classifier = "javadoc" +// } + + // Create empty jar for sources classifier to satisfy maven requirements + val stubSources by tasks.registering(Jar::class) { + archiveClassifier.set("sources") + //from(sourceSets.main.get().allSource) + } + + // Create empty jar for javadoc classifier to satisfy maven requirements + val stubJavadoc by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") + } + + tasks.withType { + kotlinOptions { + jvmTarget = "1.8" + } + } + + + afterEvaluate { + extensions.findByType()?.apply { + jvm { + compilations.all { + kotlinOptions { + jvmTarget = "1.8" + } + } + } + + js { + compilations.all { + 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 + } + } + + configure { + + publications.filterIsInstance().forEach { publication -> + if (publication.name == "kotlinMultiplatform") { + // for our root metadata publication, set artifactId with a package and project name + publication.artifactId = project.name + } else { + // for targets, set artifactId with a package, project name and target name (e.g. iosX64) + publication.artifactId = "${project.name}-${publication.name}" + } + } + + targets.all { + val publication = publications.findByName(name) as MavenPublication + + // Patch publications with fake javadoc + publication.artifact(stubJavadoc.get()) + } + } + } + } + +} \ No newline at end of file diff --git a/dataforge-vis-common/build.gradle.kts b/dataforge-vis-common/build.gradle.kts new file mode 100644 index 00000000..d0b33bb3 --- /dev/null +++ b/dataforge-vis-common/build.gradle.kts @@ -0,0 +1,30 @@ +plugins { + kotlin("multiplatform") +} + +val dataforgeVersion: String by rootProject.extra + +kotlin { + jvm() + js() + + sourceSets { + val commonMain by getting { + dependencies { + api("hep.dataforge:dataforge-io:$dataforgeVersion") + api("hep.dataforge:dataforge-io-metadata:$dataforgeVersion") + } + } + val jvmMain by getting { + dependencies { + api("hep.dataforge:dataforge-io-jvm:$dataforgeVersion") + //api("no.tornado:tornadofx:1.7.18") + } + } + val jsMain by getting { + dependencies { + api("hep.dataforge:dataforge-io-js:$dataforgeVersion") + } + } + } +} \ No newline at end of file diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObject.kt new file mode 100644 index 00000000..504871f4 --- /dev/null +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObject.kt @@ -0,0 +1,150 @@ +package hep.dataforge.vis + +import hep.dataforge.meta.* +import hep.dataforge.names.Name +import hep.dataforge.names.toName +import hep.dataforge.vis.DisplayObject.Companion.DEFAULT_TYPE +import hep.dataforge.vis.DisplayObject.Companion.META_KEY +import hep.dataforge.vis.DisplayObject.Companion.TAGS_KEY + +/** + * A root type for display hierarchy + */ +interface DisplayObject { + + /** + * The parent object of this one. If null, this one is a root. + */ + val parent: DisplayObject? + + /** + * The type of this object. Uses `.` notation. Empty type means untyped group + */ + val type: String + + val properties: Styled + + companion object { + const val DEFAULT_TYPE = "" + const val TYPE_KEY = "@type" + const val CHILDREN_KEY = "@children" + const val META_KEY = "@meta" + const val TAGS_KEY = "@tags" + } +} + +interface DisplayGroup : DisplayObject { + + val children: List + + /** + * Add a child object and notify listeners + */ + fun addChild(obj: DisplayObject) + + /** + * Remove a specific child and notify listeners + */ + fun removeChild(obj: DisplayObject) + + /** + * Add listener for children change + * TODO add detailed information into change listener + */ + fun onChildrenChange(owner: Any? = null, action: () -> Unit) + + /** + * Remove children change listener + */ + fun removeChildrenChangeListener(owner: Any? = null) +} + +/** + * Get the property of this display object of parent's if not found + */ +tailrec operator fun DisplayObject.get(name: Name): MetaItem<*>? = properties[name] ?: parent?.get(name) + +operator fun DisplayObject.get(name: String) = get(name.toName()) + +/** + * A change listener for [DisplayObject] configuration. + */ +fun DisplayObject.onChange(owner: Any?, action: (Name, before: MetaItem<*>?, after: MetaItem<*>?) -> Unit) { + properties.style.onChange(owner, action) + parent?.onChange(owner, action) +} + +/** + * Remove all meta listeners with matching owners + */ +fun DisplayObject.removeChangeListener(owner: Any?) { + properties.style.removeListener(owner) + parent?.removeChangeListener(owner) +} + + +/** + * Additional meta not relevant to display + */ +val DisplayObject.meta: Meta get() = properties[META_KEY]?.node ?: EmptyMeta + +val DisplayObject.tags: List get() = properties[TAGS_KEY].stringList + +internal data class ObjectListener( + val owner: Any?, + val action: () -> Unit +) + +/** + * Basic group of display objects + */ +open class DisplayNode( + override val parent: DisplayObject? = null, + override val type: String = DEFAULT_TYPE, + meta: Meta = EmptyMeta +) : DisplayGroup { + + private val _children = ArrayList() + override val children: List get() = _children + override val properties = Styled(meta) + private val listeners = HashSet() + + override fun addChild(obj: DisplayObject) { +// val before = _children[name] +// if (obj == null) { +// _children.remove(name) +// } else { +// _children[name] = obj +// } +// listeners.forEach { it.action(name, before, obj) } + _children.add(obj) + listeners.forEach { it.action() } + } + + override fun removeChild(obj: DisplayObject) { + if (_children.remove(obj)) { + listeners.forEach { it.action } + } + } + + override fun onChildrenChange(owner: Any?, action: () -> Unit) { + listeners.add(ObjectListener(owner, action)) + } + + + override fun removeChildrenChangeListener(owner: Any?) { + listeners.removeAll { it.owner === owner } + } +} + +/** + * Basic [DisplayObject] leaf element + */ +open class DisplayLeaf( + override val parent: DisplayObject?, + override val type: String, + meta: Meta = EmptyMeta +) : DisplayObject { + final override val properties = Styled(meta) +} + diff --git a/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt new file mode 100644 index 00000000..14663acc --- /dev/null +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/DisplayObjectDelegates.kt @@ -0,0 +1,120 @@ +package hep.dataforge.vis + +import hep.dataforge.meta.* +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 + +/** + * A delegate for display object properties + */ +class DisplayObjectDelegate( + val key: Name?, + val default: MetaItem<*>?, + val inherited: Boolean +) : ReadWriteProperty?> { + override fun getValue(thisRef: DisplayObject, property: KProperty<*>): MetaItem<*>? { + val name = key ?: property.name.toName() + return if (inherited) { + thisRef[name] + } else { + thisRef.properties[name] + } ?: default + } + + override fun setValue(thisRef: DisplayObject, property: KProperty<*>, value: MetaItem<*>?) { + val name = key ?: property.name.toName() + thisRef.properties.style[name] = value + } +} + +class DisplayObjectDelegateWrapper( + val key: Name?, + val default: T, + val inherited: Boolean, + val write: Config.(name: Name, value: T) -> Unit = { name, value -> set(name, value) }, + val read: (MetaItem<*>?) -> T? +) : ReadWriteProperty { + override fun getValue(thisRef: DisplayObject, property: KProperty<*>): T { + val name = key ?: property.name.toName() + return if (inherited) { + read(thisRef[name]) + } else { + read(thisRef.properties[name]) + } ?: default + } + + override fun setValue(thisRef: DisplayObject, property: KProperty<*>, value: T) { + val name = key ?: property.name.toName() + thisRef.properties.style.write(name, value) + } +} + + +fun DisplayObject.value(default: Value? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.value } + +fun DisplayObject.string(default: String? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.string } + +fun DisplayObject.boolean(default: Boolean? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.boolean } + +fun DisplayObject.number(default: Number? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.number } + +fun DisplayObject.double(default: Double? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.double } + +fun DisplayObject.int(default: Int? = null, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.int } + + +fun DisplayObject.node(key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), null, inherited) { it.node } + +//fun Configurable.spec(spec: Specification, key: String? = null) = ChildConfigDelegate(key) { spec.wrap(this) } + +@JvmName("safeString") +fun DisplayObject.string(default: String, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.string } + +@JvmName("safeBoolean") +fun DisplayObject.boolean(default: Boolean, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.boolean } + +@JvmName("safeNumber") +fun DisplayObject.number(default: Number, key: String? = null, inherited: Boolean = true) = + DisplayObjectDelegateWrapper(key?.toName(), default, inherited) { it.number } + +@JvmName("safeDouble") +fun DisplayObject.double(default: Double, key: String? = null, inherited: Boolean = true) = + 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) } } + +//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/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/NamedObject.kt b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/NamedObject.kt new file mode 100644 index 00000000..bc15d9e6 --- /dev/null +++ b/dataforge-vis-common/src/commonMain/kotlin/hep/dataforge/vis/NamedObject.kt @@ -0,0 +1,40 @@ +package hep.dataforge.vis + +import hep.dataforge.names.Name +import hep.dataforge.names.NameToken + +interface NamedObject : DisplayObject { + val name: String + + operator fun get(nameToken: NameToken): DisplayGroup? + + operator fun set(nameToken: NameToken, group: DisplayGroup) +} + +/** + * Recursively get a child + */ +tailrec operator fun NamedObject.get(name: Name): DisplayObject? = when (name.length) { + 0 -> this + 1 -> this[name[0]] + else -> name.first()?.let { this[it] as? NamedObject }?.get(name.cutFirst()) +} + + +/** + * Set given object creating intermediate empty groups if needed + * @param name - the full name of a child + * @param objFactory - a function that creates child object from parent (to avoid mutable parent parameter) + */ +fun NamedObject.set(name: Name, objFactory: (parent: DisplayObject) -> DisplayGroup): Unit = when (name.length) { + 0 -> error("Can't set object with empty name") + 1 -> set(name[0], objFactory(this)) + else -> (this[name.first()!!] ?: DisplayNode(this)) + .run { + if (this is NamedObject) { + this.set(name.cutFirst(), objFactory) + } else { + error("Can't assign child to a leaf element $this") + } + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial-fx/build.gradle.kts b/dataforge-vis-spatial-fx/build.gradle.kts new file mode 100644 index 00000000..5ed73278 --- /dev/null +++ b/dataforge-vis-spatial-fx/build.gradle.kts @@ -0,0 +1,16 @@ +import org.openjfx.gradle.JavaFXOptions + +plugins { + kotlin("jvm") + id("org.openjfx.javafxplugin") +} + +dependencies { + api(project(":dataforge-vis-spatial")) + api("no.tornado:tornadofx:1.7.18") + implementation("org.fxyz3d:fxyz3d:0.4.0") +} + +configure { + modules("javafx.controls") +} \ No newline at end of file diff --git a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectFXListener.kt b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectFXListener.kt new file mode 100644 index 00000000..a66ad8ca --- /dev/null +++ b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/DisplayObjectFXListener.kt @@ -0,0 +1,41 @@ +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.* + +/** + * A caching binding collection for [DisplayObject] properties + */ +class DisplayObjectFXListener(val obj: DisplayObject) { + private val binndings = HashMap?>>() + + init { + obj.onChange(this) { name, _, _ -> + binndings[name]?.invalidate() + } + } + + operator fun get(key: Name): ObjectBinding?> { + return binndings.getOrPut(key) { + object : ObjectBinding?>() { + override fun computeValue(): MetaItem<*>? = obj.get(key) + } + } + } + + operator fun get(key: String) = get(key.toName()) +} + +fun ObjectBinding?>.value() = this.objectBinding { it.value } +fun ObjectBinding?>.string() = this.stringBinding { it.string } +fun ObjectBinding?>.number() = this.objectBinding { it.number } +fun ObjectBinding?>.double() = this.objectBinding { it.double } +fun ObjectBinding?>.float() = this.objectBinding { it.number?.toFloat() } +fun ObjectBinding?>.int() = this.objectBinding { it.int } +fun ObjectBinding?>.long() = this.objectBinding { it.long } +fun ObjectBinding?>.node() = this.objectBinding { it.node } + +fun ObjectBinding?>.transform(transform: (MetaItem<*>) -> T) = this.objectBinding { it?.let(transform) } diff --git a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt new file mode 100644 index 00000000..59f9045d --- /dev/null +++ b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Canvas3D.kt @@ -0,0 +1,166 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.vis.spatial.World.CAMERA_FAR_CLIP +import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_DISTANCE +import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_X_ANGLE +import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Y_ANGLE +import hep.dataforge.vis.spatial.World.CAMERA_INITIAL_Z_ANGLE +import hep.dataforge.vis.spatial.World.CAMERA_NEAR_CLIP +import javafx.event.EventHandler +import javafx.scene.* +import javafx.scene.input.KeyCode +import javafx.scene.input.KeyEvent +import javafx.scene.input.MouseEvent +import javafx.scene.input.ScrollEvent +import javafx.scene.paint.Color +import org.fxyz3d.utils.CameraTransformer +import tornadofx.* + +class Canvas3D : Fragment() { + val world: Group = Group() + + private val camera = PerspectiveCamera().apply { + nearClip = CAMERA_NEAR_CLIP + farClip = CAMERA_FAR_CLIP + translateZ = CAMERA_INITIAL_DISTANCE + } + + private val cameraShift = CameraTransformer().apply { + val cameraFlip = CameraTransformer() + cameraFlip.children.add(camera) + cameraFlip.setRotateZ(180.0) + children.add(cameraFlip) + } + + val translationXProperty get() = cameraShift.t.xProperty() + var translateX by translationXProperty + val translationYProperty get() = cameraShift.t.yProperty() + var translateY by translationYProperty + val translationZProperty get() = cameraShift.t.zProperty() + var translateZ by translationZProperty + + private val cameraRotation = CameraTransformer().apply { + children.add(cameraShift) + ry.angle = CAMERA_INITIAL_Y_ANGLE + rx.angle = CAMERA_INITIAL_X_ANGLE + rz.angle = CAMERA_INITIAL_Z_ANGLE + } + + val rotationXProperty get() = cameraRotation.rx.angleProperty() + var angleX by rotationXProperty + val rotationYProperty get() = cameraRotation.ry.angleProperty() + var angleY by rotationYProperty + val rotationZProperty get() = cameraRotation.rz.angleProperty() + var angleZ by rotationZProperty + + + override val root =borderpane { + center = SubScene( + Group(world, cameraRotation).apply { DepthTest.ENABLE }, + 1024.0, + 768.0, + true, + SceneAntialiasing.BALANCED + ).apply { + fill = Color.GREY + this.camera = this@Canvas3D.camera + id = "canvas" + handleKeyboard(this) + handleMouse(this) + } + } + + + private fun handleKeyboard(scene: SubScene) { + scene.onKeyPressed = EventHandler { event -> + if (event.isControlDown) { + when (event.code) { + KeyCode.Z -> { + cameraShift.t.x = 0.0 + cameraShift.t.y = 0.0 + camera.translateZ = CAMERA_INITIAL_DISTANCE + cameraRotation.ry.angle = CAMERA_INITIAL_Y_ANGLE + cameraRotation.rx.angle = CAMERA_INITIAL_X_ANGLE + } +// KeyCode.X -> axisGroup.isVisible = !axisGroup.isVisible +// KeyCode.S -> snapshot() +// KeyCode.DIGIT1 -> pixelMap.filterKeys { it.getLayerNumber() == 1 }.values.forEach { +// toggleTransparency( +// it +// ) +// } +// KeyCode.DIGIT2 -> pixelMap.filterKeys { it.getLayerNumber() == 2 }.values.forEach { +// toggleTransparency( +// it +// ) +// } +// KeyCode.DIGIT3 -> pixelMap.filterKeys { it.getLayerNumber() == 3 }.values.forEach { +// toggleTransparency( +// it +// ) +// } + else -> { + }//do nothing + } + } + } + } + + private fun handleMouse(scene: SubScene) { + + var mousePosX: Double = 0.0 + var mousePosY: Double = 0.0 + var mouseOldX: Double = 0.0 + var mouseOldY: Double = 0.0 + var mouseDeltaX: Double = 0.0 + var mouseDeltaY: Double = 0.0 + + scene.onMousePressed = EventHandler { me -> + mousePosX = me.sceneX + mousePosY = me.sceneY + mouseOldX = me.sceneX + mouseOldY = me.sceneY + } + + scene.onMouseDragged = EventHandler { me -> + mouseOldX = mousePosX + mouseOldY = mousePosY + mousePosX = me.sceneX + mousePosY = me.sceneY + mouseDeltaX = mousePosX - mouseOldX + mouseDeltaY = mousePosY - mouseOldY + + val modifier = when { + me.isControlDown -> CONTROL_MULTIPLIER + me.isShiftDown -> SHIFT_MULTIPLIER + else -> 1.0 + } + + if (me.isPrimaryButtonDown) { + cameraRotation.rz.angle = + cameraRotation.rz.angle + mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED + cameraRotation.rx.angle = + cameraRotation.rx.angle + mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED + } else if (me.isSecondaryButtonDown) { + cameraShift.t.x = cameraShift.t.x + mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED + cameraShift.t.y = cameraShift.t.y + mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED + } + } + scene.onScroll = EventHandler { event -> + val z = camera.translateZ + val newZ = z + MOUSE_SPEED * event.deltaY * RESIZE_SPEED + camera.translateZ = newZ + } + } + + companion object { + private const val AXIS_LENGTH = 2000.0 + private const val CONTROL_MULTIPLIER = 0.1 + private const val SHIFT_MULTIPLIER = 10.0 + private const val MOUSE_SPEED = 0.1 + private const val ROTATION_SPEED = 2.0 + private const val TRACK_SPEED = 6.0 + private const val RESIZE_SPEED = 50.0 + private const val LINE_WIDTH = 3.0 + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt new file mode 100644 index 00000000..cd2d84ba --- /dev/null +++ b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/FX3DOutput.kt @@ -0,0 +1,48 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.context.Context +import hep.dataforge.io.Output +import hep.dataforge.meta.Meta +import hep.dataforge.vis.* +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: DisplayObject): Node? { + val listener = DisplayObjectFXListener(obj) + val x = listener["pos.x"].float() + val y = listener["pos.y"].float() + val z = listener["pos.z"].float() + val center = objectBinding(x, y, z) { + org.fxyz3d.geometry.Point3D(x.value ?: 0f, y.value ?: 0f, z.value ?: 0f) + } + return when (obj) { + is DisplayGroup -> 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 + } + } + } + + override fun render(obj: DisplayObject, meta: Meta) { + buildNode(obj)?.let { canvas.world.children.add(it) } + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt new file mode 100644 index 00000000..3816f9f6 --- /dev/null +++ b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt @@ -0,0 +1,69 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.double +import hep.dataforge.meta.get +import hep.dataforge.meta.int +import hep.dataforge.values.ValueType +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) { + null -> Materials.GREY + is MetaItem.ValueItem -> PhongMaterial(color()) + is MetaItem.NodeItem -> PhongMaterial().apply { + (node["color"]?: this@material).let { diffuseColor = it.color() } + node["specularColor"]?.let { specularColor = it.color() } + } + } +} + diff --git a/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt new file mode 100644 index 00000000..7185f113 --- /dev/null +++ b/dataforge-vis-spatial-fx/src/main/kotlin/hep/dataforge/vis/spatial/RendererDemoApp.kt @@ -0,0 +1,64 @@ +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 +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import tornadofx.* +import kotlin.random.Random + + +class RendererDemoApp : App(RendererDemoView::class) + + +class RendererDemoView : View() { + val renderer = FX3DOutput(Global) + override val root: Parent = borderpane { + center = renderer.canvas.root + } + + lateinit var group: DisplayGroup + + init { + + 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) + } + } + + renderer.canvas.apply { + angleY = -30.0 + angleX = -15.0 + } + } +} + + +fun main() { + launch() +} \ No newline at end of file diff --git a/dataforge-vis-spatial-js/build.gradle.kts b/dataforge-vis-spatial-js/build.gradle.kts new file mode 100644 index 00000000..d183766b --- /dev/null +++ b/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-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" + } + } +} \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/HMR.kt new file mode 100644 index 00000000..a77b9cf6 --- /dev/null +++ b/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-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/main.kt new file mode 100644 index 00000000..f4359987 --- /dev/null +++ b/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-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt new file mode 100644 index 00000000..f06744a5 --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/Materials.kt @@ -0,0 +1,59 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.double +import hep.dataforge.meta.get +import hep.dataforge.meta.int +import hep.dataforge.values.ValueType +import info.laht.threekt.materials.Material +import info.laht.threekt.materials.MeshPhongMaterial +import info.laht.threekt.math.Color +import info.laht.threekt.math.ColorConstants + +object Materials { + val DEFAULT = MeshPhongMaterial().apply { + this.color.set(ColorConstants.darkgreen) + } +} + +/** + * Infer color based on meta item + */ +fun MetaItem<*>.color(): Color { + return when (this) { + is MetaItem.ValueItem -> if (this.value.type == ValueType.STRING) { + Color(this.value.string) + } else { + val int = value.number.toInt() +// val red = int and 0x00ff0000 shr 16 +// val green = int and 0x0000ff00 shr 8 +// val blue = int and 0x000000ff + Color(int) + } + is MetaItem.NodeItem -> { + Color( + node["red"]?.int ?: 0, + node["green"]?.int ?: 0, + node["blue"]?.int ?: 0 + ) + } + } +} + +/** + * Infer FX material based on meta item + */ +fun MetaItem<*>?.material(): Material { + return when (this) { + null -> Materials.DEFAULT + is MetaItem.ValueItem -> MeshPhongMaterial().apply { + color = this@material.color() + } + is MetaItem.NodeItem -> MeshPhongMaterial().apply { + (node["color"] ?: this@material).let { color = it.color() } + opacity = node["opacity"]?.double ?: 1.0 + node["specularColor"]?.let { specular = it.color() } + } + } +} + diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt new file mode 100644 index 00000000..2e46f0db --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeDemoApp.kt @@ -0,0 +1,66 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.context.Global +import hep.dataforge.meta.number +import hep.dataforge.meta.set +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) { + val renderer = ThreeOutput(Global) + renderer.start(document.getElementById("canvas")!!) + println("started") + + 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 + properties.style["color"] = 1530 + } + } + } + + 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-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt new file mode 100644 index 00000000..52156552 --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/ThreeOutput.kt @@ -0,0 +1,141 @@ +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 hep.dataforge.vis.DisplayObject +import hep.dataforge.vis.get +import hep.dataforge.vis.onChange +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.math.ColorConstants +import info.laht.threekt.objects.Mesh +import info.laht.threekt.scenes.Scene +import org.w3c.dom.Element +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) + } + + val scene: Scene = Scene().apply { + add(AmbientLight()) + } + + val camera = PerspectiveCamera( + 75, + window.innerWidth.toDouble() / window.innerHeight, + World.CAMERA_NEAR_CLIP, + World.CAMERA_FAR_CLIP + ).apply { + position.setZ(World.CAMERA_INITIAL_DISTANCE) + rotation.set(World.CAMERA_INITIAL_X_ANGLE, World.CAMERA_INITIAL_Y_ANGLE, World.CAMERA_INITIAL_Z_ANGLE) + } + + val controls: OrbitControls = OrbitControls(camera, renderer.domElement) + + val root get() = renderer.domElement + + private fun animate() { + window.requestAnimationFrame { + animate() + } + renderer.render(scene, camera) + } + + fun start(element: Element) { + window.addEventListener("resize", { + camera.aspect = window.innerWidth.toDouble() / window.innerHeight; + camera.updateProjectionMatrix(); + + renderer.setSize(window.innerWidth, window.innerHeight) + }, false) + element.appendChild(root) + animate() + } + + + private fun buildNode(obj: DisplayObject): Object3D? { + + // general properties updater + val updateProperties: Object3D.(DisplayObject) -> Unit = { + position.set(obj.x, obj.y, obj.z) + setRotationFromEuler(obj.euler) + scale.set(obj.scaleX, obj.scaleY, obj.scaleZ) + visible = obj.visible + } + + return when (obj) { + is DisplayGroup -> Group(obj.children.mapNotNull { buildNode(it) }) + is Box -> { + val geometry = BoxBufferGeometry(obj.xSize, obj.ySize, obj.zSize) + val update: Mesh.(Box) -> Unit = { box -> + this.geometry = BoxBufferGeometry(box.xSize, box.ySize, box.zSize) + material = box["color"].material() + } + Mesh(geometry, obj["color"].material()).also { mesh -> + obj.onChange(this) { _, _, _ -> + mesh.updateProperties(obj) + mesh.update(obj) + } + } + } + else -> { + logger.error { "No renderer defined for ${obj::class}" } + return null + } + }.apply { + updateProperties(obj) + } + } + + override fun render(obj: DisplayObject, 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) +// } + +} \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three.kt b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three.kt new file mode 100644 index 00000000..987f2f0c --- /dev/null +++ b/dataforge-vis-spatial-js/src/main/kotlin/hep/dataforge/vis/spatial/three.kt @@ -0,0 +1,24 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.MetaItem +import hep.dataforge.meta.float +import hep.dataforge.meta.get +import hep.dataforge.meta.node +import hep.dataforge.vis.DisplayObject +import info.laht.threekt.core.Object3D +import info.laht.threekt.math.Euler +import info.laht.threekt.math.Vector3 + +/** + * Utility methods for three.kt. + * TODO move to three project + */ + +@Suppress("FunctionName") +fun Group(children: Collection) = info.laht.threekt.objects.Group().apply { + children.forEach { this.add(it) } +} + +val DisplayObject.euler get() = Euler(rotationX, rotationY, rotationZ, rotationOrder.name) + +val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?: 0f, node["z"].float ?: 0f) \ No newline at end of file diff --git a/dataforge-vis-spatial-js/src/main/web/index.html b/dataforge-vis-spatial-js/src/main/web/index.html new file mode 100644 index 00000000..27a1aae4 --- /dev/null +++ b/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-spatial/build.gradle.kts b/dataforge-vis-spatial/build.gradle.kts new file mode 100644 index 00000000..5eb5a54c --- /dev/null +++ b/dataforge-vis-spatial/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + kotlin("multiplatform") +} + +kotlin { + jvm() + js() + + sourceSets { + val commonMain by getting { + dependencies { + api(project(":dataforge-vis-common")) + } + } + val jvmMain by getting { + dependencies { + + } + } + val jsMain by getting { + dependencies { + + } + } + } +} + diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt new file mode 100644 index 00000000..6707dfd1 --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Box.kt @@ -0,0 +1,23 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.EmptyMeta +import hep.dataforge.meta.Meta +import hep.dataforge.vis.DisplayGroup +import hep.dataforge.vis.DisplayLeaf +import hep.dataforge.vis.DisplayObject +import hep.dataforge.vis.double + +class Box(parent: DisplayObject?, meta: Meta) : DisplayLeaf(parent, TYPE, meta) { + var xSize by double(1.0) + var ySize by double(1.0) + var zSize by double(1.0) + + //TODO add helper for color configuration + + companion object { + const val TYPE = "geometry.spatial.box" + } +} + +fun DisplayGroup.box(meta: Meta = EmptyMeta, action: Box.() -> Unit = {}) = + Box(this, meta).apply(action).also { addChild(it) } \ No newline at end of file diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt new file mode 100644 index 00000000..86ae35a0 --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Extruded.kt @@ -0,0 +1,18 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.meta.EmptyMeta +import hep.dataforge.meta.Meta +import hep.dataforge.vis.DisplayGroup +import hep.dataforge.vis.DisplayLeaf +import hep.dataforge.vis.DisplayObject + +class Extruded(parent: DisplayObject?, meta: Meta) : DisplayLeaf(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-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Geometry.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Geometry.kt new file mode 100644 index 00000000..b5664343 --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/Geometry.kt @@ -0,0 +1 @@ +package hep.dataforge.vis.spatial diff --git a/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/displayObject3D.kt b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/displayObject3D.kt new file mode 100644 index 00000000..33e8261a --- /dev/null +++ b/dataforge-vis-spatial/src/commonMain/kotlin/hep/dataforge/vis/spatial/displayObject3D.kt @@ -0,0 +1,109 @@ +package hep.dataforge.vis.spatial + +import hep.dataforge.io.Output +import hep.dataforge.meta.* +import hep.dataforge.vis.DisplayGroup +import hep.dataforge.vis.DisplayNode +import hep.dataforge.vis.DisplayObject +import hep.dataforge.vis.DisplayObject.Companion.DEFAULT_TYPE + +fun DisplayGroup.group(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit = {}) = + DisplayNode(this, DEFAULT_TYPE, meta).apply(action).also { addChild(it) } + + +fun Output.render(meta: Meta = EmptyMeta, action: DisplayGroup.() -> Unit) = + render(DisplayNode(null, DEFAULT_TYPE, EmptyMeta).apply(action), meta) + +//TODO replace properties by containers? + +// Common properties + +var DisplayObject.visible + get() = properties["visible"].boolean ?: true + set(value) { + properties.style["visible"] = value + } + +// 3D Object position + +var DisplayObject.x + get() = properties["pos.x"].number ?: 0.0 + set(value) { + properties.style["pos.x"] = value + } + +var DisplayObject.y + get() = properties["pos.y"].number ?: 0.0 + set(value) { + properties.style["pos.y"] = value + } + +var DisplayObject.z + get() = properties["pos.z"].number ?: 0.0 + set(value) { + properties.style["pos.z"] = value + } + +// 3D Object rotation + +var DisplayObject.rotationX + get() = properties["rotation.x"].number ?: 0.0 + set(value) { + properties.style["rotation.x"] = value + } + +var DisplayObject.rotationY + get() = properties["rotation.y"].number ?: 0.0 + set(value) { + properties.style["rotation.y"] = value + } + +var DisplayObject.rotationZ + get() = properties["rotation.z"].number ?: 0.0 + set(value) { + properties.style["rotation.z"] = value + } + +enum class RotationOrder { + XYZ, + YZX, + ZXY, + XZY, + YXZ, + ZYX +} + +var DisplayObject.rotationOrder: RotationOrder + get() = properties["rotation.order"].enum() ?: RotationOrder.XYZ + set(value) { + properties.style["rotation.order"] = value + } + +// 3D object scale + +var DisplayObject.scaleX + get() = properties["scale.x"].number ?: 1.0 + set(value) { + properties.style["scale.x"] = value + } + +var DisplayObject.scaleY + get() = properties["scale.y"].number ?: 1.0 + set(value) { + properties.style["scale.y"] = value + } + +var DisplayObject.scaleZ + get() = properties["scale.z"].number ?: 1.0 + set(value) { + properties.style["scale.z"] = value + } + +object World { + const val CAMERA_INITIAL_DISTANCE = -500.0 + const val CAMERA_INITIAL_X_ANGLE = -50.0 + const val CAMERA_INITIAL_Y_ANGLE = 0.0 + const val CAMERA_INITIAL_Z_ANGLE = -210.0 + const val CAMERA_NEAR_CLIP = 0.1 + const val CAMERA_FAR_CLIP = 10000.0 +} \ No newline at end of file diff --git a/gradle/artifactory.gradle b/gradle/artifactory.gradle new file mode 100644 index 00000000..12e59642 --- /dev/null +++ b/gradle/artifactory.gradle @@ -0,0 +1,31 @@ +apply plugin: "com.jfrog.artifactory" + +artifactory { + def artifactory_user = project.hasProperty('artifactoryUser') ? project.property('artifactoryUser') : "" + def artifactory_password = project.hasProperty('artifactoryPassword') ? project.property('artifactoryPassword') : "" + def artifactory_contextUrl = 'http://npm.mipt.ru:8081/artifactory' + + contextUrl = artifactory_contextUrl //The base Artifactory URL if not overridden by the publisher/resolver + publish { + repository { + repoKey = 'gradle-dev-local' + username = artifactory_user + password = artifactory_password + } + + defaults { + publications('jvm', 'js', 'kotlinMultiplatform', 'metadata') + publishBuildInfo = false + publishArtifacts = true + publishPom = true + publishIvy = false + } + } + resolve { + repository { + repoKey = 'gradle-dev' + username = artifactory_user + password = artifactory_password + } + } +} \ No newline at end of file diff --git a/gradle/bintray.gradle b/gradle/bintray.gradle new file mode 100644 index 00000000..8da83c86 --- /dev/null +++ b/gradle/bintray.gradle @@ -0,0 +1,85 @@ +apply plugin: 'com.jfrog.bintray' + +def vcs = "https://github.com/mipt-npm/kmath" + +def pomConfig = { + licenses { + license { + name "The Apache Software License, Version 2.0" + url "http://www.apache.org/licenses/LICENSE-2.0.txt" + distribution "repo" + } + } + developers { + developer { + id "MIPT-NPM" + name "MIPT nuclear physics methods laboratory" + organization "MIPT" + organizationUrl "http://npm.mipt.ru" + } + } + scm { + url vcs + } +} + +project.ext.configureMavenCentralMetadata = { pom -> + def root = asNode() + root.appendNode('name', project.name) + root.appendNode('description', project.description) + root.appendNode('url', vcs) + root.children().last() + pomConfig +} + +project.ext.configurePom = pomConfig + + +// Configure publishing +publishing { + repositories { + maven { + url = "https://bintray.com/mipt-npm/scientifik" + } + } + + // Process each publication we have in this project + publications.all { publication -> + // apply changes to pom.xml files, see pom.gradle + pom.withXml(configureMavenCentralMetadata) + + + } +} + +bintray { + user = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') + key = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') + publish = true + override = true // for multi-platform Kotlin/Native publishing + + pkg { + userOrg = "mipt-npm" + repo = "scientifik" + name = "scientifik.kmath" + issueTrackerUrl = "https://github.com/mipt-npm/kmath/issues" + licenses = ['Apache-2.0'] + vcsUrl = vcs + version { + name = project.version + vcsTag = project.version + released = new Date() + } + } +} + +bintrayUpload.dependsOn publishToMavenLocal + +// This is for easier debugging of bintray uploading problems +bintrayUpload.doFirst { + publications = project.publishing.publications.findAll { + !it.name.contains('-test') && it.name != 'kotlinMultiplatform' + }.collect { + println("Uploading artifact '$it.groupId:$it.artifactId:$it.version' from publication '$it.name'") + it.name//https://github.com/bintray/gradle-bintray-plugin/issues/256 + } +} \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..87b738cb Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..558870da --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..af6708ff --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..6d57edc7 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 00000000..d1e3c70c --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,27 @@ +pluginManagement { + repositories { + jcenter() + gradlePluginPortal() + 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}") + "kotlin2js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}") + "org.jetbrains.kotlin.frontend" -> useModule("org.jetbrains.kotlin:kotlin-frontend-plugin:0.0.45") + } + } + } +} + +enableFeaturePreview("GRADLE_METADATA") + +rootProject.name = "dataforge-vis" +include( + ":dataforge-vis-common", + ":dataforge-vis-spatial", + ":dataforge-vis-spatial-fx", + ":dataforge-vis-spatial-js" +) \ No newline at end of file