From ace68179108ac8664621e59620b0641769f68d07 Mon Sep 17 00:00:00 2001 From: Samuel Pantze <83579186+smlpt@users.noreply.github.com> Date: Mon, 26 Feb 2024 15:39:31 +0100 Subject: [PATCH 1/6] Camera: adds OrientationOverlay class and basic toggle logic --- src/main/kotlin/graphics/scenery/Camera.kt | 57 ++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/main/kotlin/graphics/scenery/Camera.kt b/src/main/kotlin/graphics/scenery/Camera.kt index 191316195..07f75403d 100644 --- a/src/main/kotlin/graphics/scenery/Camera.kt +++ b/src/main/kotlin/graphics/scenery/Camera.kt @@ -6,6 +6,7 @@ import graphics.scenery.attribute.renderable.HasRenderable import graphics.scenery.attribute.spatial.DefaultSpatial import graphics.scenery.attribute.spatial.HasCustomSpatial import graphics.scenery.net.Networkable +import graphics.scenery.primitives.Cylinder import graphics.scenery.utils.extensions.minus import graphics.scenery.utils.extensions.plus import graphics.scenery.utils.extensions.times @@ -492,6 +493,62 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom } + enum class OverlayPosition { + CENTER, TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT + } + + private var orientationOverlayVisible = false + + private inner class OrientationOverlay( + camera: Camera = this, + radius: Float = 0.1f, + length: Float = 1f, + pos: OverlayPosition + ) : DefaultNode("Orientation Overlay") { + + val axesRadius: Float = radius + val axesLength: Float = length + val axesParent: Node = Group() + + init { + axesParent.name = "Orientation Axes" + axesParent.spatialOrNull()?.position = Vector3f(0f, 0f, -5f) + + var c = Cylinder(axesRadius / 2.0f, axesLength, 12) + c.name = "X axis" + c.material().diffuse = Vector3f(1f, 0f, 0f) + val halfPI = Math.PI.toFloat() / 2.0f + c.spatial().rotation = Quaternionf().rotateLocalZ(-halfPI) + axesParent.addChild(c) + // + c = Cylinder(axesRadius / 2.0f, axesLength, 12) + c.name = "Y axis" + c.material().diffuse = Vector3f(0f, 1f, 0f) + c.spatial().rotation = Quaternionf().rotateLocalZ(Math.PI.toFloat()) + axesParent.addChild(c) + // + c = Cylinder(axesRadius / 2.0f, axesLength, 12) + c.name = "Z axis" + c.material().diffuse = Vector3f(0f, 0f, 1f) + c.spatial().rotation = Quaternionf().rotateLocalX(-halfPI) + axesParent.addChild(c) + camera.addChild(axesParent) + } + } + + fun toggleOrientationOverlay(pos: OverlayPosition = OverlayPosition.TOP_RIGHT) { + + if (!orientationOverlayVisible) { + val overlay = OrientationOverlay(pos = pos) + } else { + this.removeChild("Orientation Overlay") + } + + orientationOverlayVisible = !orientationOverlayVisible + } + + + @Deprecated(message = "", replaceWith = ReplaceWith("spatial().viewportToWorld(vector)")) fun viewportToWorld(vector: Vector2f): Vector3f { return spatial().viewportToWorld(vector) From d6deab5ed8232bd0b8685be611d4f38d8eb0810b Mon Sep 17 00:00:00 2001 From: Samuel Pantze <83579186+smlpt@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:31:40 +0100 Subject: [PATCH 2/6] AtmosphereExample: adds orientation overlay to the example --- .../scenery/tests/examples/basic/AtmosphereExample.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/AtmosphereExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/AtmosphereExample.kt index b5d634dca..05e80ace4 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/AtmosphereExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/AtmosphereExample.kt @@ -42,7 +42,7 @@ class AtmosphereExample : SceneryBase("Atmosphere Example", renderer = hub.add( SceneryElement.Renderer, Renderer.createRenderer(hub, applicationName, scene, windowWidth, windowHeight)) - + renderer?.pushMode = true if (useVR) { hmd = OpenVRHMD(useCompositor = true) hub.add(SceneryElement.HMDInput, hmd) @@ -80,7 +80,8 @@ class AtmosphereExample : SceneryBase("Atmosphere Example", spatial { position = Vector3f(0.0f, 0.0f, 5.0f) } - perspectiveCamera(70.0f, 512, 768) + perspectiveCamera(70.0f, 768, 512) + if (!useVR) toggleOrientationOverlay() scene.addChild(this) } From e886fd2e6eb5900f3345106feab1f46dc3cd3107 Mon Sep 17 00:00:00 2001 From: Samuel Pantze <83579186+smlpt@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:32:32 +0100 Subject: [PATCH 3/6] Camera: adds documentation, moves position logic into the getRelativePosition method --- src/main/kotlin/graphics/scenery/Camera.kt | 89 +++++++++++++++++----- 1 file changed, 72 insertions(+), 17 deletions(-) diff --git a/src/main/kotlin/graphics/scenery/Camera.kt b/src/main/kotlin/graphics/scenery/Camera.kt index 07f75403d..b413b9246 100644 --- a/src/main/kotlin/graphics/scenery/Camera.kt +++ b/src/main/kotlin/graphics/scenery/Camera.kt @@ -223,7 +223,7 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom } /** - * Returns the starting position and direction of a ray starting at the the screen space position + * Returns the starting position and direction of a ray starting at the screen space position * indicated by [x] and [y] targeting away from the camera. * * Returns (worldPos, worldDir) @@ -493,27 +493,38 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom } - enum class OverlayPosition { + enum class OverlayAlignment { CENTER, TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT } private var orientationOverlayVisible = false + /** + * Class to create an orientation overlay, consisting of three axes pointing towards positive X/Y/Z. + * Can be placed in the center or in any of the screen corners. + * @param camera the camera to attach the overlay to + * @param radius radius for the axes + * @param length length of the axes + * @param align four corners or the center of type [OverlayAlignment] + * @param margin how far from the corners the overlay should be + */ private inner class OrientationOverlay( camera: Camera = this, - radius: Float = 0.1f, - length: Float = 1f, - pos: OverlayPosition + radius: Float, + length: Float, + align: OverlayAlignment, + margin: Float ) : DefaultNode("Orientation Overlay") { - val axesRadius: Float = radius - val axesLength: Float = length + val axesRadius: Float = radius / 10 + val axesLength: Float = length / 10 val axesParent: Node = Group() init { axesParent.name = "Orientation Axes" - axesParent.spatialOrNull()?.position = Vector3f(0f, 0f, -5f) - + axesParent.ifSpatial { + position = getRelativePosition(margin, align) + } var c = Cylinder(axesRadius / 2.0f, axesLength, 12) c.name = "X axis" c.material().diffuse = Vector3f(1f, 0f, 0f) @@ -524,27 +535,71 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom c = Cylinder(axesRadius / 2.0f, axesLength, 12) c.name = "Y axis" c.material().diffuse = Vector3f(0f, 1f, 0f) - c.spatial().rotation = Quaternionf().rotateLocalZ(Math.PI.toFloat()) +// c.spatial().rotation = Quaternionf().rotateLocalZ(Math.PI.toFloat()) axesParent.addChild(c) // c = Cylinder(axesRadius / 2.0f, axesLength, 12) c.name = "Z axis" c.material().diffuse = Vector3f(0f, 0f, 1f) - c.spatial().rotation = Quaternionf().rotateLocalX(-halfPI) + c.spatial().rotation = Quaternionf().rotateLocalX(halfPI) axesParent.addChild(c) camera.addChild(axesParent) + + // workaround: continuously update the position. + // TODO Should be done with camera callback instead + thread { + while (true) { + axesParent.ifSpatial { + position = getRelativePosition(margin, align) + } + Thread.sleep(1000) + } + } + } + + fun getRelativePosition(margin: Float, align: OverlayAlignment): Vector3f { + // position normalized in range -1 to 1 + var pos = when (align) { + OverlayAlignment.CENTER -> Vector2f(0f, 0f) + OverlayAlignment.TOP_LEFT -> Vector2f(margin, margin) + OverlayAlignment.TOP_RIGHT -> Vector2f(1f - margin, 1f - margin) + OverlayAlignment.BOTTOM_RIGHT -> Vector2f(1f - margin, 1f - margin) + OverlayAlignment.BOTTOM_LEFT -> Vector2f(margin, 1f - margin) + } + return spatial().viewportToView(pos)//.add(Vector3f(0f, 0f, 0.0f)) } - } - fun toggleOrientationOverlay(pos: OverlayPosition = OverlayPosition.TOP_RIGHT) { + fun updateRotation() { + axesParent.ifSpatial { + // NB: camera rotation is already inverse, no need to conjugate + rotation = this@Camera.spatial().rotation + } + } + } - if (!orientationOverlayVisible) { - val overlay = OrientationOverlay(pos = pos) + /** + * Call this method to attach an orientation overlay to the camera, + * consisting of three axes pointing towards positive X/Y/Z. + * @param align where to spawn the overlay, defaults to [OverlayAlignment.TOP_RIGHT]. + * @param margin how far from the screen corners to spawn the overlay. Defaults to a factor of 0.1. + * @param radius radius for the axes + * @param length length of the axes + */ + fun toggleOrientationOverlay( + align: OverlayAlignment = OverlayAlignment.TOP_RIGHT, + margin: Float = 0.1f, + radius: Float = 0.05f, + length: Float = 0.3f + ) { + orientationOverlayVisible = !orientationOverlayVisible + if (orientationOverlayVisible) { + val overlay = OrientationOverlay(this, radius, length, align, margin) + postUpdate.add { + overlay.updateRotation() + } } else { this.removeChild("Orientation Overlay") } - - orientationOverlayVisible = !orientationOverlayVisible } From fe6996ce9482c6dfff21d5d2f22ee5ce9cd0b33a Mon Sep 17 00:00:00 2001 From: Samuel Pantze <83579186+smlpt@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:44:49 +0100 Subject: [PATCH 4/6] Camera: removed some comments --- src/main/kotlin/graphics/scenery/Camera.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/kotlin/graphics/scenery/Camera.kt b/src/main/kotlin/graphics/scenery/Camera.kt index b413b9246..e133e28d2 100644 --- a/src/main/kotlin/graphics/scenery/Camera.kt +++ b/src/main/kotlin/graphics/scenery/Camera.kt @@ -535,7 +535,6 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom c = Cylinder(axesRadius / 2.0f, axesLength, 12) c.name = "Y axis" c.material().diffuse = Vector3f(0f, 1f, 0f) -// c.spatial().rotation = Quaternionf().rotateLocalZ(Math.PI.toFloat()) axesParent.addChild(c) // c = Cylinder(axesRadius / 2.0f, axesLength, 12) @@ -566,7 +565,7 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom OverlayAlignment.BOTTOM_RIGHT -> Vector2f(1f - margin, 1f - margin) OverlayAlignment.BOTTOM_LEFT -> Vector2f(margin, 1f - margin) } - return spatial().viewportToView(pos)//.add(Vector3f(0f, 0f, 0.0f)) + return spatial().viewportToView(pos) } fun updateRotation() { From 9b37b964d8d4d631793fae943d1a68a0dd1b68bd Mon Sep 17 00:00:00 2001 From: Samuel Pantze <83579186+smlpt@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:51:52 +0100 Subject: [PATCH 5/6] Camera: adds documentation for OverlayAlignment --- src/main/kotlin/graphics/scenery/Camera.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/kotlin/graphics/scenery/Camera.kt b/src/main/kotlin/graphics/scenery/Camera.kt index e133e28d2..1caaa220b 100644 --- a/src/main/kotlin/graphics/scenery/Camera.kt +++ b/src/main/kotlin/graphics/scenery/Camera.kt @@ -493,6 +493,9 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom } + /** + * This class provides possible position states for the orientation overlay. + */ enum class OverlayAlignment { CENTER, TOP_LEFT, TOP_RIGHT, BOTTOM_RIGHT, BOTTOM_LEFT } From c679602c813cd529e0b9ad7df0a6107e4ccd370b Mon Sep 17 00:00:00 2001 From: Samuel Pantze <83579186+smlpt@users.noreply.github.com> Date: Fri, 1 Mar 2024 13:24:35 +0100 Subject: [PATCH 6/6] Camera: adds emissive material property --- src/main/kotlin/graphics/scenery/Camera.kt | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/graphics/scenery/Camera.kt b/src/main/kotlin/graphics/scenery/Camera.kt index 1caaa220b..ccb13d929 100644 --- a/src/main/kotlin/graphics/scenery/Camera.kt +++ b/src/main/kotlin/graphics/scenery/Camera.kt @@ -529,20 +529,30 @@ open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustom position = getRelativePosition(margin, align) } var c = Cylinder(axesRadius / 2.0f, axesLength, 12) + c.name = "X axis" - c.material().diffuse = Vector3f(1f, 0f, 0f) + c.material { + diffuse = Vector3f(1f, 0f, 0f) + emissive = Vector4f(1f, 0f, 0f, 3f) + } val halfPI = Math.PI.toFloat() / 2.0f c.spatial().rotation = Quaternionf().rotateLocalZ(-halfPI) axesParent.addChild(c) - // + c = Cylinder(axesRadius / 2.0f, axesLength, 12) c.name = "Y axis" - c.material().diffuse = Vector3f(0f, 1f, 0f) + c.material { + diffuse = Vector3f(0f, 1f, 0f) + emissive = Vector4f(0f, 1f, 0f, 3f) + } axesParent.addChild(c) - // + c = Cylinder(axesRadius / 2.0f, axesLength, 12) c.name = "Z axis" - c.material().diffuse = Vector3f(0f, 0f, 1f) + c.material { + diffuse = Vector3f(0f, 0f, 1f) + emissive = Vector4f(0f, 0f, 1f, 3f) + } c.spatial().rotation = Quaternionf().rotateLocalX(halfPI) axesParent.addChild(c) camera.addChild(axesParent)