Almost working OrbitControls for FX

This commit is contained in:
Alexander Nozik 2019-12-28 22:11:43 +03:00
parent 26948a88d7
commit 459d256ebd
31 changed files with 451 additions and 319 deletions

View File

@ -1,6 +1,6 @@
import scientifik.useSerialization
val dataforgeVersion by extra("0.1.5-dev-5")
val dataforgeVersion by extra("0.1.5-dev-6")
plugins {
val kotlinVersion = "1.3.61"

View File

@ -35,6 +35,7 @@ kotlin {
api("hep.dataforge:dataforge-output-html:$dataforgeVersion")
api(npm("bootstrap","4.4.1"))
implementation(npm("jsoneditor"))
implementation(npm("file-saver"))
}
}
}

View File

@ -1,10 +1,9 @@
package hep.dataforge.js
package hep.dataforge.vis.js.editor
import hep.dataforge.names.NameToken
import hep.dataforge.vis.common.VisualGroup
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.isEmpty
import hep.dataforge.vis.js.editor.card
import kotlinx.html.TagConsumer
import kotlinx.html.dom.append
import kotlinx.html.js.*

View File

@ -5,13 +5,9 @@ import hep.dataforge.meta.MetaBuilder
import hep.dataforge.meta.buildMeta
import hep.dataforge.names.Name
import hep.dataforge.names.toName
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.common.useStyle
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.RotationOrder
import hep.dataforge.vis.spatial.VisualGroup3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.rotationOrder
import scientifik.gdml.*
import kotlin.random.Random
@ -37,7 +33,14 @@ class GDMLTransformer(val root: GDML) {
var volumeAction: (GDMLGroup) -> Action = { Action.CACHE }
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { _, _ -> }
var solidConfiguration: VisualObject3D.(parent: GDMLVolume, solid: GDMLSolid) -> Unit = { parent, solid ->
lUnit = LUnit.CM
if (parent.physVolumes.isNotEmpty()) {
useStyle("opaque") {
Material3D.MATERIAL_OPACITY_KEY put 0.3
}
}
}
fun VisualObject3D.useStyle(name: String, builder: MetaBuilder.() -> Unit) {
styleCache.getOrPut(name.toName()) {

View File

@ -6,8 +6,8 @@ import hep.dataforge.names.asName
import hep.dataforge.names.plus
import hep.dataforge.vis.common.get
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.GeometryConstants.one
import hep.dataforge.vis.spatial.GeometryConstants.zero
import hep.dataforge.vis.spatial.World.ONE
import hep.dataforge.vis.spatial.World.ZERO
import scientifik.gdml.*
import kotlin.math.cos
import kotlin.math.sin
@ -21,20 +21,20 @@ private fun VisualObject3D.withPosition(
): VisualObject3D = apply {
newPos?.let {
val point = Point3D(it.x(lUnit), it.y(lUnit), it.z(lUnit))
if (position != null || point != zero) {
if (position != null || point != ZERO) {
position = point
}
}
newRotation?.let {
val point = Point3D(it.x(), it.y(), it.z())
if (rotation != null || point != zero) {
if (rotation != null || point != ZERO) {
rotation = point
}
//this@withPosition.rotationOrder = RotationOrder.ZXY
}
newScale?.let {
val point = Point3D(it.x, it.y, it.z)
if (scale != null || point != one) {
if (scale != null || point != ONE) {
scale = point
}
}

View File

@ -38,7 +38,7 @@ class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
contextual(Point2DSerializer)
contextual(NameSerializer)
contextual(NameTokenSerializer)
contextual(Meta::class, MetaSerializer)
contextual(MetaSerializer)
contextual(ConfigSerializer)
polymorphic(VisualObject::class, VisualObject3D::class) {
@ -48,7 +48,8 @@ class Visual3DPlugin(meta: Meta) : AbstractPlugin(meta) {
Tube::class with Tube.serializer()
Box::class with Box.serializer()
Convex::class with Convex.serializer()
addSubclass(Extruded.serializer())
Extruded::class with Extruded.serializer()
addSubclass(Label3D.serializer())
}
}

View File

@ -3,12 +3,8 @@ package hep.dataforge.vis.spatial
import kotlin.math.PI
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
val ZERO = Point3D(0.0, 0.0, 0.0)
val ONE = Point3D(1.0, 1.0, 1.0)
}
const val PI2: Float = 2 * PI.toFloat()

View File

@ -39,8 +39,3 @@ fun Point3D.toMeta() = buildMeta {
VisualObject3D.y put y
VisualObject3D.z put z
}
object GeometryConstants {
val zero = Point3D(0.0, 0.0, 0.0)
val one = Point3D(1.0, 1.0, 1.0)
}

View File

@ -0,0 +1,17 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.*
class AxesSpec(override val config: Config) : Specific {
var visible by boolean(!config.isEmpty())
var size by double(AXIS_SIZE)
var width by double(AXIS_WIDTH)
companion object : Specification<AxesSpec> {
override fun wrap(config: Config): AxesSpec = AxesSpec(config)
const val AXIS_SIZE = 1000.0
const val AXIS_WIDTH = 3.0
}
}

View File

@ -0,0 +1,26 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.*
import kotlin.math.PI
class CameraSpec(override val config: Config) : Specific {
var fov by int(FIELD_OF_VIEW)
//var aspect by double(1.0)
var nearClip by double(NEAR_CLIP)
var farClip by double(FAR_CLIP)
var distance by double(INITIAL_DISTANCE)
var azimuth by double(INITIAL_AZIMUTH)
var latitude by double(INITIAL_LATITUDE)
val zenith: Double get() = PI / 2 - latitude
companion object : Specification<CameraSpec> {
override fun wrap(config: Config): CameraSpec = CameraSpec(config)
const val INITIAL_DISTANCE = 300.0
const val INITIAL_AZIMUTH = 0.0
const val INITIAL_LATITUDE = PI/6
const val NEAR_CLIP = 0.1
const val FAR_CLIP = 10000.0
const val FIELD_OF_VIEW = 75
}
}

View File

@ -0,0 +1,15 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.*
class CanvasSpec(override val config: Config) : Specific {
var axes by spec(AxesSpec)
var camera by spec(CameraSpec)
var controls by spec(ControlsSpec)
var minSize by int(300)
companion object: Specification<CanvasSpec>{
override fun wrap(config: Config): CanvasSpec = CanvasSpec(config)
}
}

View File

@ -0,0 +1,11 @@
package hep.dataforge.vis.spatial.specifications
import hep.dataforge.meta.Config
import hep.dataforge.meta.Specific
import hep.dataforge.meta.Specification
class ControlsSpec(override val config: Config) : Specific {
companion object : Specification<ControlsSpec> {
override fun wrap(config: Config): ControlsSpec = ControlsSpec(config)
}
}

View File

@ -15,8 +15,8 @@ internal fun mergeChild(parent: VisualGroup, child: VisualObject): VisualObject
//parent.properties?.let { config.update(it) }
if (this is VisualObject3D && parent is VisualObject3D) {
position = (position ?: GeometryConstants.zero) + (parent.position ?: GeometryConstants.zero)
rotation = (parent.rotation ?: GeometryConstants.zero) + (parent.rotation ?: GeometryConstants.zero)
position = (position ?: World.ZERO) + (parent.position ?: World.ZERO)
rotation = (parent.rotation ?: World.ZERO) + (parent.rotation ?: World.ZERO)
scale = when {
scale == null && parent.scale == null -> null
scale == null -> parent.scale

View File

@ -1,59 +1,61 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.context.Context
import hep.dataforge.meta.*
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.string
import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.spatial.Point3D
import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.World
import hep.dataforge.vis.spatial.specifications.AxesSpec
import hep.dataforge.vis.spatial.specifications.CameraSpec
import hep.dataforge.vis.spatial.specifications.CanvasSpec
import hep.dataforge.vis.spatial.specifications.ControlsSpec
import info.laht.threekt.WebGLRenderer
import info.laht.threekt.cameras.PerspectiveCamera
import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.external.controls.TrackballControls
import info.laht.threekt.helpers.AxesHelper
import info.laht.threekt.lights.AmbientLight
import info.laht.threekt.scenes.Scene
import org.w3c.dom.HTMLElement
import org.w3c.dom.Node
import kotlin.browser.window
import kotlin.dom.clear
import kotlin.math.cos
import kotlin.math.max
import kotlin.math.sin
class ThreeCanvas(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Renderer<VisualObject3D> {
class ThreeCanvas(val three: ThreePlugin, val spec: CanvasSpec) : Renderer<VisualObject3D> {
override val context: Context get() = three.context
var data: VisualObject3D? = null
var content: VisualObject3D? = null
private set
val axes = AxesHelper(meta["axes.size"].int ?: 50).apply { visible = false }
val axes = AxesHelper(spec.axes.size.toInt()).apply {
visible = spec.axes.visible
}
val scene: Scene = Scene().apply {
add(AmbientLight())
if (meta["axes.visible"].boolean == true) {
axes.visible = true
}
add(axes)
}
private fun buildCamera(meta: Meta) = PerspectiveCamera(
meta["fov"].int ?: 75,
meta["aspect"].double ?: 1.0,
meta["nearClip"].double ?: World.CAMERA_NEAR_CLIP,
meta["farClip"].double ?: World.CAMERA_FAR_CLIP
val camera = buildCamera(spec.camera)
private fun buildCamera(spec: CameraSpec) = PerspectiveCamera(
spec.fov,
1.0,
spec.nearClip,
spec.farClip
).apply {
position.setZ(World.CAMERA_INITIAL_DISTANCE)
rotation.set(
World.CAMERA_INITIAL_X_ANGLE,
World.CAMERA_INITIAL_Y_ANGLE,
World.CAMERA_INITIAL_Z_ANGLE
)
translateX(spec.distance* sin(spec.zenith) * sin(spec.azimuth))
translateY(spec.distance* cos(spec.zenith))
translateZ(spec.distance * sin(spec.zenith) * cos(spec.azimuth))
}
val camera = buildCamera(meta["camera"].node ?: EmptyMeta)
private fun addControls(element: Node, meta: Meta) {
when (meta["type"].string) {
private fun addControls(element: Node, controlsSpec: ControlsSpec) {
when (controlsSpec["type"].string) {
"trackball" -> TrackballControls(camera, element)
else -> OrbitControls(camera, element)
}
@ -69,7 +71,7 @@ class ThreeCanvas(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Renderer
}
addControls(renderer.domElement, meta["controls"].node ?: EmptyMeta)
addControls(renderer.domElement, spec.controls ?: ControlsSpec.empty())
fun animate() {
window.requestAnimationFrame {
@ -80,9 +82,7 @@ class ThreeCanvas(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Renderer
element.appendChild(renderer.domElement)
val minSize by meta.number(0).int
renderer.setSize(max(minSize, element.offsetWidth), max(minSize, element.offsetWidth))
renderer.setSize(max(spec.minSize, element.offsetWidth), max(spec.minSize, element.offsetWidth))
element.onresize = {
renderer.setSize(element.offsetWidth, element.offsetWidth)
@ -93,13 +93,13 @@ class ThreeCanvas(val three: ThreePlugin, val meta: Meta = EmptyMeta) : Renderer
}
override fun render(obj: VisualObject3D, meta: Meta) {
data = obj
content = obj
scene.add(three.buildObject3D(obj))
}
}
fun ThreePlugin.output(element: HTMLElement? = null, meta: Meta = EmptyMeta, override: MetaBuilder.() -> Unit = {}) =
ThreeCanvas(this, buildMeta(meta, override)).apply {
fun ThreePlugin.output(element: HTMLElement? = null, spec: CanvasSpec = CanvasSpec.empty()): ThreeCanvas =
ThreeCanvas(this, spec).apply {
if (element != null) {
attach(element)
}

View File

@ -1,29 +1,35 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.vis.spatial.Label3D
import hep.dataforge.vis.spatial.color
import info.laht.threekt.DoubleSide
import info.laht.threekt.core.Object3D
import info.laht.threekt.geometries.PlaneGeometry
import info.laht.threekt.geometries.PlaneBufferGeometry
import info.laht.threekt.materials.MeshBasicMaterial
import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture
import org.w3c.dom.CanvasRenderingContext2D
import org.w3c.dom.HTMLCanvasElement
import org.w3c.dom.*
import kotlin.browser.document
import kotlin.math.PI
import kotlin.reflect.KClass
/**
* Using example from http://stemkoski.github.io/Three.js/Texture-From-Canvas.html
*/
object ThreeTextFactory : ThreeFactory<Label3D> {
object ThreeLabelFactory : ThreeFactory<Label3D> {
override val type: KClass<in Label3D> get() = Label3D::class
override fun invoke(obj: Label3D): Object3D {
val canvas = document.createElement("canvas") as HTMLCanvasElement
val context = canvas.getContext("2d") as CanvasRenderingContext2D
context.font = "${obj.fontSize}pt ${obj.fontFamily}"
context.fillStyle = "rgba(255,0,0,0.95)"//obj.material?.color ?: "black"
context.fillText(obj.text, 0.0, 0.0)
context.font = "Bold ${obj.fontSize}pt ${obj.fontFamily}"
context.fillStyle = obj.color ?: "black"
context.textAlign = CanvasTextAlign.CENTER
//context.textBaseline = CanvasTextBaseline.MIDDLE
val metrics = context.measureText(obj.text)
context.fillText(obj.text, 0.5*metrics.width, 0.5*metrics.width)
// canvas contents will be used for a texture
val texture = Texture(canvas)
@ -32,14 +38,16 @@ object ThreeTextFactory : ThreeFactory<Label3D> {
val material = MeshBasicMaterial().apply {
map = texture
side = DoubleSide
transparent = true
}
material.transparent = true;
val mesh = Mesh(
PlaneGeometry(canvas.clientWidth, canvas.clientHeight),
PlaneBufferGeometry(canvas.width, canvas.height),
material
)
//mesh.rotateX(PI)
mesh.updatePosition(obj)
return mesh

View File

@ -27,7 +27,7 @@ class ThreePlugin : AbstractPlugin() {
objectFactories[Sphere::class] = ThreeSphereFactory
objectFactories[ConeSegment::class] = ThreeCylinderFactory
objectFactories[PolyLine::class] = ThreeLineFactory
objectFactories[Label3D::class] = ThreeTextFactory
objectFactories[Label3D::class] = ThreeLabelFactory
}
@Suppress("UNCHECKED_CAST")

View File

@ -1,51 +1,67 @@
package hep.dataforge.vis.spatial.three
import hep.dataforge.js.requireJS
import hep.dataforge.vis.js.editor.card
import hep.dataforge.vis.spatial.Visual3DPlugin
import hep.dataforge.vis.spatial.VisualGroup3D
import kotlinx.html.InputType
import kotlinx.html.TagConsumer
import kotlinx.html.button
import kotlinx.html.dom.append
import kotlinx.html.js.*
import org.w3c.dom.Element
import org.w3c.dom.HTMLElement
import org.w3c.dom.events.Event
import org.w3c.files.Blob
import org.w3c.files.BlobPropertyBag
import kotlin.dom.clear
//private fun download(filename: String, text: String) {
// var element = document.createElement("a");
// element.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(text));
// element.setAttribute("download", filename);
//
// element.style.display = 'none';
// document.body.appendChild(element);
//
// element.click();
//
// document.body.removeChild(element);
//}
private fun saveData(event: Event, fileName: String, mimeType: String = "text/plain", dataBuilder: () -> String) {
event.stopPropagation();
event.preventDefault();
fun Element.threeOutputConfig(canvas: ThreeCanvas) {
val fileSaver = requireJS("file-saver")
val blob = Blob(arrayOf(dataBuilder()), BlobPropertyBag("$mimeType;charset=utf-8"))
fileSaver.saveAs(blob, fileName)
}
fun Element.threeSettings(canvas: ThreeCanvas, block: TagConsumer<HTMLElement>.() -> Unit = {}) {
clear()
append {
card("Settings"){
div("row"){
div("col-1") {
label { +"Axes" }
card("Settings") {
div("row") {
div("col-2") {
label("checkbox-inline") {
input(type = InputType.checkBox).apply {
checked = canvas.axes.visible
onChangeFunction = {
canvas.axes.visible = checked
}
}
+"Axes"
}
}
div("col-1") {
button {
+"Export"
onClickFunction = {
val json = (canvas.content as? VisualGroup3D)?.let { group ->
Visual3DPlugin.json.stringify(
VisualGroup3D.serializer(),
group
)
}
if (json != null) {
saveData(it, "object.json", "text/json"){
json
}
}
}
}
}
card("Layers"){
}
}
card("Layers") {
div("row") {
(0..11).forEach { layer ->
div("col-1") {
@ -66,5 +82,6 @@ fun Element.threeOutputConfig(canvas: ThreeCanvas) {
}
}
}
block()
}
}

View File

@ -5,7 +5,6 @@ import hep.dataforge.meta.float
import hep.dataforge.meta.get
import hep.dataforge.meta.node
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.GeometryConstants.zero
import info.laht.threekt.core.*
import info.laht.threekt.external.controls.OrbitControls
import info.laht.threekt.materials.Material
@ -13,6 +12,7 @@ import info.laht.threekt.math.Euler
import info.laht.threekt.math.Vector3
import info.laht.threekt.objects.Mesh
import info.laht.threekt.textures.Texture
import kotlin.math.PI
/**
* Utility methods for three.kt.
@ -30,6 +30,8 @@ val MetaItem<*>.vector get() = Vector3(node["x"].float ?: 0f, node["y"].float ?:
fun Geometry.toBufferGeometry(): BufferGeometry = BufferGeometry().apply { fromGeometry(this@toBufferGeometry) }
internal fun Double.toRadians() = this * PI / 180
fun CSG.toGeometry(): Geometry {
val geom = Geometry()
@ -45,7 +47,7 @@ fun CSG.toGeometry(): Geometry {
}
for (j in 3..polygon.vertices.size) {
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, zero)
val fc = Face3(v0, v0 + j - 2, v0 + j - 1, World.ZERO)
fc.vertexNormals = arrayOf(
Vector3().copy(pvs[0].normal),
Vector3().copy(pvs[j - 2].normal),

View File

@ -2,29 +2,20 @@ package hep.dataforge.vis.spatial.fx
import hep.dataforge.context.Context
import hep.dataforge.context.ContextAware
import hep.dataforge.meta.*
import hep.dataforge.meta.Meta
import hep.dataforge.output.Renderer
import hep.dataforge.vis.spatial.VisualObject3D
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_NEAR_CLIP
import hep.dataforge.vis.spatial.specifications.CanvasSpec
import javafx.application.Platform
import javafx.beans.property.ObjectProperty
import javafx.beans.property.SimpleObjectProperty
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.layout.BorderPane
import javafx.scene.paint.Color
import org.fxyz3d.scene.Axes
import org.fxyz3d.utils.CameraTransformer
import tornadofx.*
class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
class FXCanvas3D(val plugin: FX3DPlugin, val spec: CanvasSpec) :
Fragment(), Renderer<VisualObject3D>, ContextAware {
override val context: Context get() = plugin.context
@ -32,41 +23,23 @@ class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
val world = Group()
val axes = Axes().also {
it.setHeight(meta["axis.size"].double ?: AXIS_LENGTH)
it.setRadius(meta["axis.width"].double ?: LINE_WIDTH)
it.isVisible = meta["axis.visible"].boolean ?: (meta["axis"] != null)
it.setHeight(spec.axes.size)
it.setRadius(spec.axes.width)
it.isVisible = spec.axes.visible
world.add(it)
}
val light = AmbientLight()
private val camera = PerspectiveCamera().apply {
nearClip = CAMERA_NEAR_CLIP
farClip = CAMERA_FAR_CLIP
translateZ = CAMERA_INITIAL_DISTANCE
nearClip = spec.camera.nearClip
farClip = spec.camera.farClip
fieldOfView = spec.camera.fov.toDouble()
this.add(light)
}
val cameraTransform = CameraTransformer().also {
it.add(camera)
}
val translationXProperty get() = cameraTransform.t.xProperty()
var translateX by translationXProperty
val translationYProperty get() = cameraTransform.t.yProperty()
var translateY by translationYProperty
val translationZProperty get() = cameraTransform.t.zProperty()
var translateZ by translationZProperty
val rotationXProperty get() = cameraTransform.rx.angleProperty()
var angleX by rotationXProperty
val rotationYProperty get() = cameraTransform.ry.angleProperty()
var angleY by rotationYProperty
val rotationZProperty get() = cameraTransform.rz.angleProperty()
var angleZ by rotationZProperty
private val canvas = SubScene(
Group(world, cameraTransform).apply { DepthTest.ENABLE },
Group(world, camera).apply { DepthTest.ENABLE },
400.0,
400.0,
true,
@ -74,15 +47,16 @@ class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
).also { scene ->
scene.fill = Color.GREY
scene.camera = camera
//id = "canvas"
handleKeyboard(scene)
handleMouse(scene)
}
override val root = borderpane {
center = canvas
}
val controls = camera.orbitControls(canvas, spec.camera).also {
world.add(it.centerMarker)
}
val rootObjectProperty: ObjectProperty<VisualObject3D> = SimpleObjectProperty()
var rootObject: VisualObject3D? by rootObjectProperty
@ -105,99 +79,7 @@ class FXCanvas3D(val plugin: FX3DPlugin, meta: Meta = EmptyMeta) :
}
}
private fun handleKeyboard(scene: SubScene) {
scene.onKeyPressed = EventHandler<KeyEvent> { event ->
if (event.isControlDown) {
when (event.code) {
KeyCode.Z -> {
translateX = 0.0
translateY = 0.0
camera.translateZ = CAMERA_INITIAL_DISTANCE
angleY = CAMERA_INITIAL_Y_ANGLE
angleX = CAMERA_INITIAL_X_ANGLE
}
KeyCode.X -> axes.isVisible = !axes.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<MouseEvent> { me ->
mousePosX = me.sceneX
mousePosY = me.sceneY
mouseOldX = me.sceneX
mouseOldY = me.sceneY
}
scene.onMouseDragged = EventHandler<MouseEvent> { 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) {
angleY += mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
angleX += mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
} else if (me.isSecondaryButtonDown) {
translateX -= mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
translateY -= mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
}
}
scene.onScroll = EventHandler<ScrollEvent> { event ->
val z = camera.translateZ
val newZ = z + MOUSE_SPEED * event.deltaY * RESIZE_SPEED
camera.translateZ = newZ
}
}
override fun render(obj: VisualObject3D, meta: Meta) {
rootObject = obj
}
companion object {
private const val AXIS_LENGTH = 400.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 = 1.0
}
}

View File

@ -0,0 +1,174 @@
package hep.dataforge.vis.spatial.fx
import hep.dataforge.vis.spatial.specifications.CameraSpec
import javafx.beans.InvalidationListener
import javafx.beans.property.SimpleDoubleProperty
import javafx.event.EventHandler
import javafx.geometry.Point3D
import javafx.scene.Camera
import javafx.scene.Node
import javafx.scene.SubScene
import javafx.scene.input.MouseEvent
import javafx.scene.input.ScrollEvent
import javafx.scene.shape.Sphere
import javafx.scene.transform.Rotate
import javafx.scene.transform.Translate
import tornadofx.*
import kotlin.math.*
class OrbitControls internal constructor(camera: Camera, canvas: SubScene, spec: CameraSpec) {
val distanceProperty = SimpleDoubleProperty(spec.distance)
var distance by distanceProperty
val azimuthProperty = SimpleDoubleProperty(spec.azimuth)
var azimuth by azimuthProperty
val zenithProperty = SimpleDoubleProperty(PI/2 - spec.latitude)
var zenith by zenithProperty
val latitudeProperty = zenithProperty.unaryMinus().plus(PI/2)
val latitude by latitudeProperty
val baseXProperty = SimpleDoubleProperty(0.0)
var x by baseXProperty
val baseYProperty = SimpleDoubleProperty(0.0)
var y by baseYProperty
val baseZProperty = SimpleDoubleProperty(0.0)
var z by baseZProperty
// val basePositionProperty: ObjectBinding<Point3D> =
// nonNullObjectBinding(baseXProperty, baseYProperty, baseZProperty) {
// Point3D(x, y, z)
// }
//
// val basePosition by basePositionProperty
val centerMarker by lazy {
Sphere(10.0).also {
it.translateXProperty().bind(baseXProperty)
it.translateYProperty().bind(baseYProperty)
it.translateZProperty().bind(baseZProperty)
}
}
private val rx = Rotate(0.0, Rotate.X_AXIS)
private val ry = Rotate(0.0, Rotate.Y_AXIS)
private val translate = Translate()
private val rz = Rotate(180.0, Rotate.Z_AXIS)
init {
camera.transforms.setAll(ry, rx, translate,rz)
update()
val listener = InvalidationListener {
update()
}
distanceProperty.addListener(listener)
azimuthProperty.addListener(listener)
zenithProperty.addListener(listener)
baseXProperty.addListener(listener)
baseYProperty.addListener(listener)
baseZProperty.addListener(listener)
canvas.apply {
camera.translateXProperty().bind(widthProperty().divide(2))
camera.translateZProperty().bind(heightProperty().divide(2))
handleMouse()
}
// coordinateContainer?.vbox {
// label(distanceProperty.asString())
// label(azimuthProperty.asString())
// label(zenithProperty.asString())
// }
}
private fun update() {
val spherePosition = Point3D(
sin(zenith) * sin(azimuth),
cos(zenith),
sin(zenith) * cos(azimuth)
).times(distance)
val basePosition = Point3D(x, y, z)
//Create direction vector
val cameraPosition = basePosition + spherePosition
val camDirection: Point3D = (-spherePosition).normalize()
val xRotation = Math.toDegrees(asin(-camDirection.y))
val yRotation = Math.toDegrees(atan2(camDirection.x, camDirection.z))
rx.pivotX = cameraPosition.x
rx.pivotY = cameraPosition.y
rx.pivotZ = cameraPosition.z
rx.angle = xRotation
ry.pivotX = cameraPosition.x
ry.pivotY = cameraPosition.y
ry.pivotZ = cameraPosition.z
ry.angle = yRotation
translate.x = cameraPosition.x
translate.y = cameraPosition.y
translate.z = cameraPosition.z
}
private fun Node.handleMouse() {
var mousePosX = 0.0
var mousePosY = 0.0
var mouseOldX: Double
var mouseOldY: Double
var mouseDeltaX: Double
var mouseDeltaY: Double
onMousePressed = EventHandler<MouseEvent> { me ->
mousePosX = me.sceneX
mousePosY = me.sceneY
mouseOldX = me.sceneX
mouseOldY = me.sceneY
}
onMouseDragged = EventHandler<MouseEvent> { 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) {
azimuth -= mouseDeltaX * MOUSE_SPEED * modifier * ROTATION_SPEED
zenith -= mouseDeltaY * MOUSE_SPEED * modifier * ROTATION_SPEED
} else if (me.isSecondaryButtonDown) {
x += mouseDeltaX * MOUSE_SPEED * modifier * TRACK_SPEED
z -= mouseDeltaY * MOUSE_SPEED * modifier * TRACK_SPEED
}
}
onScroll = EventHandler<ScrollEvent> { event ->
distance = max(1.0, distance - MOUSE_SPEED * event.deltaY * RESIZE_SPEED)
}
}
companion object {
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 = 0.02
private const val TRACK_SPEED = 6.0
private const val RESIZE_SPEED = 10.0
}
}
fun Camera.orbitControls(canvas: SubScene, spec: CameraSpec) =
OrbitControls(this, canvas, spec)

View File

@ -2,7 +2,7 @@ package hep.dataforge.vis.spatial.gdml.demo
import hep.dataforge.context.Global
import hep.dataforge.js.Application
import hep.dataforge.js.objectTree
import hep.dataforge.vis.js.editor.objectTree
import hep.dataforge.js.startApplication
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.withBottom
@ -20,7 +20,7 @@ import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.toVisual
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.spatial.three.threeOutputConfig
import hep.dataforge.vis.spatial.three.threeSettings
import hep.dataforge.vis.spatial.visible
import kotlinx.html.dom.append
import kotlinx.html.js.p
@ -157,7 +157,7 @@ private class GDMLDemoApp : Application {
val output = three.output(canvasElement as HTMLElement)
output.camera.layers.set(0)
configElement.threeOutputConfig(output)
configElement.threeSettings(output)
//tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.objectTree(NameToken("World"), visual) {
editorElement.propertyEditor(it) { item ->

View File

@ -6,9 +6,9 @@
<title>Three js demo for particle physics</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<link rel="stylesheet" href="css/ru.mipt.npm.muon.monitor.main.css">
<link rel="stylesheet" href="css/main.css">
<link rel="stylesheet" href="css/jsoneditor.min.css">
<script type="text/javascript" src="ru.mipt.npm.muon.monitor.main.bundle.js"></script>
<script type="text/javascript" src="main.bundle.js"></script>
</head>
<body class="testApp">
<div class="container" id="drop_zone" data-toggle="tooltip" data-placement="right"

View File

@ -20,7 +20,7 @@ kotlin {
js {
browser {
webpackTask {
sourceMaps = false
sourceMaps = true
}
}
}

View File

@ -2,11 +2,11 @@ package ru.mipt.npm.muon.monitor
import hep.dataforge.context.Global
import hep.dataforge.js.Application
import hep.dataforge.js.objectTree
import hep.dataforge.js.startApplication
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.withBottom
import hep.dataforge.names.NameToken
import hep.dataforge.vis.js.editor.objectTree
import hep.dataforge.vis.js.editor.propertyEditor
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_COLOR_KEY
import hep.dataforge.vis.spatial.Material3D.Companion.MATERIAL_OPACITY_KEY
@ -15,11 +15,12 @@ import hep.dataforge.vis.spatial.VisualObject3D
import hep.dataforge.vis.spatial.VisualObject3D.Companion.VISIBLE_KEY
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
import hep.dataforge.vis.spatial.three.threeOutputConfig
import hep.dataforge.vis.spatial.three.threeSettings
import hep.dataforge.vis.spatial.visible
import org.w3c.dom.HTMLElement
import kotlin.browser.document
import kotlin.dom.clear
import kotlin.math.PI
private class GDMLDemoApp : Application {
// /**
@ -59,7 +60,8 @@ private class GDMLDemoApp : Application {
//val url = URL("https://drive.google.com/open?id=1w5e7fILMN83JGgB8WANJUYm8OW2s0WVO")
val canvasElement = document.getElementById("canvas") ?: error("Element with id 'canvas' not found on page")
val configElement = document.getElementById("layers") ?: error("Element with id 'layers' not found on page")
val settingsElement =
document.getElementById("settings") ?: error("Element with id 'settings' not found on page")
val treeElement = document.getElementById("tree") ?: error("Element with id 'tree' not found on page")
val editorElement = document.getElementById("editor") ?: error("Element with id 'editor' not found on page")
canvasElement.clear()
@ -71,7 +73,9 @@ private class GDMLDemoApp : Application {
val output = three.output(canvasElement as HTMLElement)
output.camera.layers.set(0)
configElement.threeOutputConfig(output)
output.camera.position.z = -2000.0
output.camera.position.y = 500.0
settingsElement.threeSettings(output)
//tree.visualObjectTree(visual, editor::propertyEditor)
treeElement.objectTree(NameToken("World"), visual) {
editorElement.propertyEditor(it) { item ->

View File

@ -27,8 +27,8 @@
<div class="row">
<div class="col-lg-3" id="tree"></div>
<div class="col-lg-6">
<div class="row" id="layers"></div>
<div class="row container" id="canvas"></div>
<div class="row" id="settings"></div>
<div class="row" id="canvas"></div>
</div>
<div class="col-lg-3" id="editor"></div>
</div>

View File

@ -1,11 +1,13 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.meta.buildMeta
import hep.dataforge.meta.invoke
import hep.dataforge.names.toName
import hep.dataforge.output.OutputManager
import hep.dataforge.vis.common.Colors
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.*
import hep.dataforge.vis.spatial.specifications.CanvasSpec
import kotlinx.coroutines.*
import kotlin.math.PI
import kotlin.math.cos
@ -21,10 +23,22 @@ fun OutputManager.demo(name: String, title: String = name, block: VisualGroup3D.
output.render(action = block)
}
val canvasOptions = CanvasSpec {
minSize = 500
axes {
size = 500.0
visible = true
}
camera {
distance = 600.0
latitude = PI/6
}
}
fun OutputManager.showcase() {
demo("shapes", "Basic shapes") {
box(100.0, 100.0, 100.0) {
z = 110.0
z = -110.0
}
sphere(50.0) {
x = 110
@ -88,6 +102,7 @@ fun OutputManager.showcase() {
layer(i * 5, 20 * sin(2 * PI / 100 * i), 20 * cos(2 * PI / 100 * i))
}
color(Colors.teal)
rotationX = -PI / 2
}
}
@ -111,13 +126,13 @@ fun OutputManager.showcase() {
box(100, 100, 50) {
opacity = 0.3
}
label("Hello, world!",fontSize = 15) {
z = -26
label("Hello, world!", fontSize = 12) {
z = 26
}
}
}
fun OutputManager.showcaseCSG(){
fun OutputManager.showcaseCSG() {
demo("CSG.simple", "CSG operations") {
composite(CompositeType.UNION) {
box(100, 100, 100) {

View File

@ -22,27 +22,27 @@ private class ThreeDemoApp : Application {
ThreeDemoGrid(element).run {
showcase()
showcaseCSG()
demo("dynamicBox", "Dancing boxes") {
val boxes = (-10..10).flatMap { i ->
(-10..10).map { j ->
varBox(10, 10, 0, name = "cell_${i}_${j}") {
x = i * 10
y = j * 10
value = 128
setProperty(EDGES_ENABLED_KEY, false)
setProperty(WIREFRAME_ENABLED_KEY, false)
}
}
}
GlobalScope.launch {
while (isActive) {
delay(500)
boxes.forEach { box ->
box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255)
}
}
}
}
// demo("dynamicBox", "Dancing boxes") {
// val boxes = (-10..10).flatMap { i ->
// (-10..10).map { j ->
// varBox(10, 10, 0, name = "cell_${i}_${j}") {
// x = i * 10
// y = j * 10
// value = 128
// setProperty(EDGES_ENABLED_KEY, false)
// setProperty(WIREFRAME_ENABLED_KEY, false)
// }
// }
// }
// GlobalScope.launch {
// while (isActive) {
// delay(500)
// boxes.forEach { box ->
// box.value = (box.value + Random.nextInt(-15, 15)).coerceIn(0..255)
// }
// }
// }
// }
}

View File

@ -3,11 +3,15 @@ package hep.dataforge.vis.spatial.demo
import hep.dataforge.context.Global
import hep.dataforge.meta.Meta
import hep.dataforge.meta.get
import hep.dataforge.meta.invoke
import hep.dataforge.meta.string
import hep.dataforge.names.Name
import hep.dataforge.output.OutputManager
import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.specifications.AxesSpec
import hep.dataforge.vis.spatial.specifications.CameraSpec
import hep.dataforge.vis.spatial.specifications.CanvasSpec
import hep.dataforge.vis.spatial.three.ThreeCanvas
import hep.dataforge.vis.spatial.three.ThreePlugin
import hep.dataforge.vis.spatial.three.output
@ -40,18 +44,14 @@ class ThreeDemoGrid(element: Element, meta: Meta = Meta.EMPTY) : OutputManager {
return outputs.getOrPut(name) {
if (type != VisualObject::class) error("Supports only DisplayObject")
val output = three.output(meta = meta) {
"minSize" put 500
"axis" put {
"size" put 500
}
}
lateinit var output: ThreeCanvas
//TODO calculate cell width here using jquery
gridRoot.append {
span("border") {
div("col-6") {
div { id = "output-$name" }.also {
output.attach(it)
output = three.output(it, canvasOptions)
//output.attach(it)
}
hr()
h2 { +(meta["title"].string ?: name.toString()) }

View File

@ -4,32 +4,14 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Three js demo for particle physics</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<script type="text/javascript" src="ru.mipt.npm.muon.monitor.main.bundle.js"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<script type="text/javascript" src="main.bundle.js"></script>
</head>
<body class="testApp">
<!--
<div class="container" id="drop_zone" data-toggle="tooltip" data-placement="right"
title="Для загрузки данных в текстовом формате, надо перетащить файл сюда">
Загрузить данные
<br/>
(перетащить файл сюда)
</div>
-->
<div class="container">
<h1>Demo grid</h1>
</div>
<div class="container" id="canvas"></div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js"
integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"
integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1"
crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
crossorigin="anonymous"></script>
</body>
</html>

View File

@ -1,7 +1,5 @@
package hep.dataforge.vis.spatial.demo
import hep.dataforge.vis.spatial.Material3D
import hep.dataforge.vis.spatial.gdml.LUnit
import hep.dataforge.vis.spatial.gdml.gdml
import javafx.stage.Stage
import tornadofx.*
@ -14,22 +12,12 @@ class FXDemoApp : App(FXDemoGrid::class) {
override fun start(stage: Stage) {
super.start(stage)
stage.width = 400.0
stage.height = 400.0
stage.width = 600.0
stage.height = 600.0
//view.showcase()
view.demo("gdml", "gdml") {
gdml(Paths.get("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml")) {
lUnit = LUnit.CM
solidConfiguration = { parent, solid ->
if (parent.physVolumes.isNotEmpty()) {
useStyle("opaque") {
Material3D.MATERIAL_OPACITY_KEY put 0.3
}
}
}
}
view.showcase()
view.demo("gdml", "gdml-cubes") {
gdml(Paths.get("D:\\Work\\Projects\\gdml.kt\\gdml-source\\cubes.gdml"))
//setProperty(Material3D.MATERIAL_WIREFRAME_KEY, true)
}
}

View File

@ -9,13 +9,15 @@ import hep.dataforge.output.Renderer
import hep.dataforge.vis.common.VisualObject
import hep.dataforge.vis.spatial.fx.FX3DPlugin
import hep.dataforge.vis.spatial.fx.FXCanvas3D
import hep.dataforge.vis.spatial.specifications.AxesSpec
import hep.dataforge.vis.spatial.specifications.CanvasSpec
import javafx.collections.FXCollections
import javafx.scene.Parent
import javafx.scene.control.Tab
import tornadofx.*
import kotlin.reflect.KClass
class FXDemoGrid : View(), OutputManager {
class FXDemoGrid : View(title = "DataForge-vis FX demo"), OutputManager {
private val outputs = FXCollections.observableHashMap<Name, FXCanvas3D>()
override val root: Parent = borderpane {
@ -32,13 +34,7 @@ class FXDemoGrid : View(), OutputManager {
override fun <T : Any> get(type: KClass<out T>, name: Name, stage: Name, meta: Meta): Renderer<T> {
return outputs.getOrPut(name) {
if (type != VisualObject::class) kotlin.error("Supports only DisplayObject")
val customMeta = buildMeta(meta) {
"minSize" put 500
"axis" put {
"size" put 500
}
}
val output = FXCanvas3D(fx3d, customMeta)
val output = FXCanvas3D(fx3d, canvasOptions)
output
} as Renderer<T>