From 108ba6affef799633400ffb3e6080f685fdf8b75 Mon Sep 17 00:00:00 2001 From: frauzufall Date: Fri, 21 May 2021 21:09:19 +0200 Subject: [PATCH] Node: refactoring to support adding attributes to a node --- .../kotlin/graphics/scenery/BoundingGrid.kt | 103 +- src/main/kotlin/graphics/scenery/Box.kt | 206 +-- src/main/kotlin/graphics/scenery/Camera.kt | 307 ++-- .../kotlin/graphics/scenery/DefaultNode.kt | 188 +++ .../graphics/scenery/DetachedHeadCamera.kt | 77 +- .../graphics/scenery/DirectionalLight.kt | 83 +- .../graphics/scenery/FullscreenObject.kt | 83 +- src/main/kotlin/graphics/scenery/Group.kt | 12 +- src/main/kotlin/graphics/scenery/Icosphere.kt | 79 +- .../kotlin/graphics/scenery/InstancedNode.kt | 81 ++ src/main/kotlin/graphics/scenery/Light.kt | 4 +- src/main/kotlin/graphics/scenery/Mesh.kt | 131 +- src/main/kotlin/graphics/scenery/Node.kt | 1269 ++++++----------- .../graphics/scenery/OrientedBoundingBox.kt | 25 +- .../kotlin/graphics/scenery/PointLight.kt | 61 +- .../kotlin/graphics/scenery/Renderable.kt | 70 - src/main/kotlin/graphics/scenery/RichNode.kt | 13 + src/main/kotlin/graphics/scenery/Scene.kt | 26 +- .../kotlin/graphics/scenery/SceneryBase.kt | 4 +- .../kotlin/graphics/scenery/ShaderMaterial.kt | 3 +- src/main/kotlin/graphics/scenery/Sphere.kt | 8 +- .../kotlin/graphics/scenery/VolumeMeasurer.kt | 4 +- .../scenery/attribute/AttributesMap.kt | 7 + .../scenery/attribute/DefaultAttributesMap.kt | 16 + .../scenery/attribute/DelegatesProperties.kt | 5 + .../DelegationType.kt} | 11 +- .../attribute/geometry/DefaultGeometry.kt | 69 + .../attribute/geometry/DelegatesGeometry.kt | 22 + .../geometry/Geometry.kt} | 15 +- .../attribute/geometry/HasCustomGeometry.kt | 34 + .../scenery/attribute/geometry/HasGeometry.kt | 7 + .../attribute/material/DefaultMaterial.kt | 32 + .../attribute/material/DelegatesMaterial.kt | 29 + .../attribute/material/HasCustomMaterial.kt | 34 + .../scenery/attribute/material/HasMaterial.kt | 8 + .../{ => attribute/material}/Material.kt | 45 +- .../attribute/renderable/DefaultRenderable.kt | 16 + .../renderable/DelegatesRenderable.kt | 29 + .../renderable/HasCustomRenderable.kt | 34 + .../attribute/renderable/HasRenderable.kt | 8 + .../attribute/renderable/Renderable.kt | 41 + .../attribute/spatial/DefaultSpatial.kt | 370 +++++ .../attribute/spatial/DelegatesSpatial.kt | 22 + .../attribute/spatial/HasCustomSpatial.kt | 34 + .../scenery/attribute/spatial/HasSpatial.kt | 8 + .../scenery/attribute/spatial/Spatial.kt | 109 ++ .../scenery/backends/opengl/OpenGLRenderer.kt | 421 +++--- .../scenery/backends/opengl/OpenGLUBO.kt | 3 +- .../backends/vulkan/VulkanNodeHelpers.kt | 105 +- .../backends/vulkan/VulkanObjectState.kt | 15 +- .../backends/vulkan/VulkanPostprocessPass.kt | 2 +- .../scenery/backends/vulkan/VulkanRenderer.kt | 219 +-- .../backends/vulkan/VulkanRenderpass.kt | 23 +- .../backends/vulkan/VulkanScenePass.kt | 72 +- .../scenery/backends/vulkan/VulkanUBO.kt | 4 +- .../graphics/scenery/compute/EdgeBundler.kt | 45 +- .../graphics/scenery/controls/OpenVRHMD.kt | 26 +- .../graphics/scenery/controls/ScreenConfig.kt | 4 +- .../behaviours/ArcballCameraControl.kt | 16 +- .../controls/behaviours/ControllerDrag.kt | 12 +- .../controls/behaviours/FPSCameraControl.kt | 22 +- .../behaviours/GamepadCameraControl.kt | 9 +- .../behaviours/GamepadMovementControl.kt | 52 +- .../scenery/controls/behaviours/MeshAdder.kt | 6 +- .../controls/behaviours/MouseDragPlane.kt | 18 +- .../controls/behaviours/MouseDragSphere.kt | 8 +- .../controls/behaviours/MouseRotate.kt | 16 +- .../controls/behaviours/MovementCommand.kt | 18 +- .../behaviours/RollingBallCameraControl.kt | 113 +- .../scenery/controls/behaviours/Ruler.kt | 10 +- ...cleScreenSpaceCalibrationPointGenerator.kt | 2 +- ...tedScreenSpaceCalibrationPointGenerator.kt | 2 +- .../controls/eyetracking/PupilEyeTracker.kt | 14 +- .../effectors/LineRestrictionEffector.kt | 33 +- .../scenery/effectors/VolumeEffector.kt | 14 +- .../graphics/scenery/fonts/SDFFontAtlas.kt | 54 +- .../kotlin/graphics/scenery/geometry/Curve.kt | 18 +- .../graphics/scenery/net/NodeSubscriber.kt | 18 +- .../graphics/scenery/primitives/Arrow.kt | 121 +- .../graphics/scenery/primitives/Cone.kt | 118 +- .../graphics/scenery/primitives/Cylinder.kt | 80 +- .../scenery/primitives/InfinitePlane.kt | 18 +- .../graphics/scenery/primitives/Line.kt | 190 ++- .../graphics/scenery/primitives/LinePair.kt | 204 +-- .../graphics/scenery/primitives/Plane.kt | 109 +- .../graphics/scenery/primitives/PointCloud.kt | 138 +- .../graphics/scenery/primitives/Skybox.kt | 2 +- .../graphics/scenery/primitives/TextBoard.kt | 112 +- .../graphics/scenery/proteins/Rainbow.kt | 4 +- .../graphics/scenery/volumes/Colormap.kt | 2 +- .../graphics/scenery/volumes/OrthoView.kt | 56 +- .../graphics/scenery/volumes/RAIVolume.kt | 42 +- .../scenery/volumes/SceneryContext.kt | 16 +- .../graphics/scenery/volumes/SlicingPlane.kt | 10 +- .../TransformedBufferedSimpleStack3D.kt | 2 +- .../TransformedMultiResolutionStack3D.kt | 2 +- .../volumes/TransformedSimpleStack3D.kt | 2 +- .../kotlin/graphics/scenery/volumes/Volume.kt | 130 +- .../graphics/scenery/volumes/VolumeManager.kt | 221 +-- .../basic/TexturedCubeJavaExample.java | 23 +- .../tests/examples/advanced/ARExample.kt | 28 +- .../examples/advanced/AttributesExample.kt | 138 ++ .../examples/advanced/BetaStrandExample.kt | 25 +- .../examples/advanced/BloodCellsExample.kt | 111 +- .../advanced/CycleRenderQualityExample.kt | 57 +- .../examples/advanced/EyeTrackingExample.kt | 81 +- .../tests/examples/advanced/HelixExample.kt | 23 +- .../examples/advanced/LocalisationExample.kt | 23 +- .../examples/advanced/MouseInputExample.kt | 31 +- .../examples/advanced/MultiBoxExample.kt | 37 +- .../advanced/MultiBoxInstancedExample.kt | 74 +- .../tests/examples/advanced/PBLExample.kt | 57 +- .../advanced/ProceduralTextureExample.kt | 34 +- .../examples/advanced/RainbowRibbonExample.kt | 31 +- .../RibbonExampleSecondaryStructures.kt | 37 +- .../examples/advanced/VRControllerExample.kt | 37 +- .../examples/advanced/VertexUpdateExample.kt | 122 +- .../examples/advanced/VideoDecodingExample.kt | 14 +- .../advanced/VideoRecordingExample.kt | 22 +- .../tests/examples/basic/ArcballExample.kt | 31 +- .../tests/examples/basic/ArrowExample.kt | 32 +- .../examples/basic/CurveCatmullRomExample.kt | 25 +- .../basic/CurveDifferentBaseShapes.kt | 25 +- .../basic/CurveUniformBSplineExample.kt | 17 +- .../examples/basic/DistanceMeasurement.kt | 86 +- .../examples/basic/EdgeBundlerExample.kt | 21 +- .../examples/basic/FontRenderingExample.kt | 19 +- .../examples/basic/IntersectionExample.kt | 26 +- .../tests/examples/basic/LineExample.kt | 33 +- .../tests/examples/basic/MeshAddingExample.kt | 13 +- .../tests/examples/basic/PointCloudExample.kt | 23 +- .../tests/examples/basic/ReaderExample.kt | 44 +- .../tests/examples/basic/RulerExample.kt | 13 +- .../tests/examples/basic/SponzaExample.kt | 49 +- .../basic/SwingTexturedCubeExample.kt | 20 +- .../examples/basic/TexturedCubeExample.kt | 20 +- .../tests/examples/cluster/BileExample.kt | 31 +- .../tests/examples/cluster/ClusterExample.kt | 32 +- .../tests/examples/cluster/DemoReelExample.kt | 55 +- .../cluster/ScreenConfigVisualizerExample.kt | 41 +- .../examples/compute/ComputeShaderExample.kt | 31 +- .../compute/ComputeShaderRenderpassExample.kt | 20 +- .../compute/CustomVolumeManagerExample.kt | 16 +- .../stresstests/LotsOfSpheresExample.kt | 10 +- .../examples/stresstests/PowerplantExample.kt | 28 +- .../examples/stresstests/RungholtExample.kt | 27 +- .../tests/examples/volumes/BDVExample.kt | 2 +- .../volumes/BigAndSmallVolumeExample.kt | 4 +- .../tests/examples/volumes/CroppingExample.kt | 62 +- .../volumes/FlybrainOutOfCoreExample.kt | 15 +- .../examples/volumes/OrthoViewExample.kt | 20 +- .../examples/volumes/OutOfCoreRAIExample.kt | 6 +- .../volumes/ProceduralVolumeExample.kt | 23 +- .../tests/examples/volumes/RAIExample.kt | 18 +- .../tests/examples/volumes/VolumeExample.kt | 33 +- .../examples/volumes/VolumeSamplingExample.kt | 49 +- .../scenery/tests/unit/CameraTests.kt | 16 +- .../graphics/scenery/tests/unit/ConeTests.kt | 2 +- .../graphics/scenery/tests/unit/MeshTests.kt | 36 +- .../graphics/scenery/tests/unit/NodeTests.kt | 262 ++-- .../scenery/tests/unit/RainbowTests.kt | 2 +- .../graphics/scenery/tests/unit/SceneTests.kt | 7 +- .../behaviours/ArcballCameraControlTests.kt | 12 +- .../behaviours/GamepadCameraControlTests.kt | 18 +- .../tests/unit/fonts/SDFFontAtlasTests.kt | 6 +- 165 files changed, 5455 insertions(+), 3746 deletions(-) create mode 100644 src/main/kotlin/graphics/scenery/DefaultNode.kt create mode 100644 src/main/kotlin/graphics/scenery/InstancedNode.kt delete mode 100644 src/main/kotlin/graphics/scenery/Renderable.kt create mode 100644 src/main/kotlin/graphics/scenery/RichNode.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/AttributesMap.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/DefaultAttributesMap.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/DelegatesProperties.kt rename src/main/kotlin/graphics/scenery/{DelegatesRendering.kt => attribute/DelegationType.kt} (52%) create mode 100644 src/main/kotlin/graphics/scenery/attribute/geometry/DefaultGeometry.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/geometry/DelegatesGeometry.kt rename src/main/kotlin/graphics/scenery/{geometry/HasGeometry.kt => attribute/geometry/Geometry.kt} (85%) create mode 100644 src/main/kotlin/graphics/scenery/attribute/geometry/HasCustomGeometry.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/geometry/HasGeometry.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/material/DefaultMaterial.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/material/DelegatesMaterial.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/material/HasCustomMaterial.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/material/HasMaterial.kt rename src/main/kotlin/graphics/scenery/{ => attribute/material}/Material.kt (62%) create mode 100644 src/main/kotlin/graphics/scenery/attribute/renderable/DefaultRenderable.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/renderable/DelegatesRenderable.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/renderable/HasCustomRenderable.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/renderable/HasRenderable.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/renderable/Renderable.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/spatial/DefaultSpatial.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/spatial/DelegatesSpatial.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/spatial/HasCustomSpatial.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/spatial/HasSpatial.kt create mode 100644 src/main/kotlin/graphics/scenery/attribute/spatial/Spatial.kt create mode 100644 src/test/kotlin/graphics/scenery/tests/examples/advanced/AttributesExample.kt diff --git a/src/main/kotlin/graphics/scenery/BoundingGrid.kt b/src/main/kotlin/graphics/scenery/BoundingGrid.kt index ce2ae454c..8660351b3 100644 --- a/src/main/kotlin/graphics/scenery/BoundingGrid.kt +++ b/src/main/kotlin/graphics/scenery/BoundingGrid.kt @@ -1,6 +1,9 @@ package graphics.scenery import graphics.scenery.primitives.TextBoard +import graphics.scenery.attribute.renderable.DefaultRenderable +import graphics.scenery.attribute.renderable.Renderable +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.* import org.joml.Vector3f import org.joml.Vector4f @@ -52,12 +55,12 @@ open class BoundingGrid : Mesh("Bounding Grid") { protected var nodeBoundingBoxHash: Int = -1 init { - material = ShaderMaterial.fromFiles("DefaultForward.vert", "BoundingGrid.frag") - material.blending.transparent = true - material.blending.opacity = 0.8f - material.blending.setOverlayBlending() - material.cullingMode = Material.CullingMode.Front - + setMaterial(ShaderMaterial.fromFiles("DefaultForward.vert", "BoundingGrid.frag")) { + blending.transparent = true + blending.opacity = 0.8f + blending.setOverlayBlending() + cullingMode = Material.CullingMode.Front + } labels = hashMapOf( "0" to TextBoard(), @@ -71,33 +74,39 @@ open class BoundingGrid : Mesh("Bounding Grid") { fontBoard.fontColor = Vector4f(1.0f, 1.0f, 1.0f, 1.0f) fontBoard.backgroundColor = Vector4f(0.0f, 0.0f, 0.0f, 1.0f) fontBoard.transparent = 1 - fontBoard.scale = Vector3f(0.3f, 0.3f, 0.3f) + fontBoard.spatial { + scale = Vector3f(0.3f, 0.3f, 0.3f) + } this.addChild(fontBoard) } } + override fun createRenderable(): Renderable { + return object : DefaultRenderable(this) { + override fun preDraw(): Boolean { + super.preDraw() + if (node?.getMaximumBoundingBox().hashCode() != nodeBoundingBoxHash) { + logger.info( + "Updating bounding box (${ + node?.getMaximumBoundingBox().hashCode() + } vs $nodeBoundingBoxHash" + ) + updateNode(node, node) + } + + return true + } + } + } + private fun updateNode(oldNode: Node?, newNode: Node?) { - if(newNode == null) { - newNode?.removeChild(this) - newNode?.updateWorld(true) - } else { + if(newNode != null) { oldNode?.removeChild(this) updateFromNode() newNode.addChild(this) - newNode.updateWorld(true) - } - } - - override fun preDraw(): Boolean { - super.preDraw() - - if(node?.getMaximumBoundingBox()?.hashCode() != nodeBoundingBoxHash) { - logger.info("Updating bounding box (${node?.getMaximumBoundingBox()?.hashCode()} vs $nodeBoundingBoxHash") - updateNode(node, node) + newNode.spatialOrNull()?.updateWorld(true) } - - return true } protected fun updateFromNode() { @@ -109,10 +118,12 @@ open class BoundingGrid : Mesh("Bounding Grid") { var min = maxBoundingBox.min var max = maxBoundingBox.max - logger.debug("Node ${node.name} is transparent: ${node.material.blending.transparent}") - if(node.material.blending.transparent || (node is DelegatesRendering && node.delegate?.material?.blending?.transparent == true)) { - min = min * (1.0f + slack) - max = max * (1.0f + slack) + node.ifMaterial { + logger.debug("Node ${node.name} is transparent: ${blending.transparent}") + if(blending.transparent) { + min = min * (1.0f + slack) + max = max * (1.0f + slack) + } } val b = Box(max - min) @@ -121,25 +132,33 @@ open class BoundingGrid : Mesh("Bounding Grid") { val center = (max - min)*0.5f - this.vertices = b.vertices - this.normals = b.normals - this.texcoords = b.texcoords - this.indices = b.indices - this.boundingBox = b.boundingBox - this.position = maxBoundingBox.min + center + + val bGeometry = b.geometry() + geometry { + vertices = bGeometry.vertices + normals = bGeometry.normals + texcoords = bGeometry.texcoords + indices = bGeometry.indices + } + spatial { + position = maxBoundingBox.min + center + } boundingBox?.let { bb -> // label coordinates are relative to the bounding box - labels["0"]?.position = bb.min - Vector3f(0.1f, 0.0f, 0.0f) - labels["x"]?.position = Vector3f(2.0f * bb.max.x() + 0.1f, 0.01f, 0.01f) - center - labels["y"]?.position = Vector3f(-0.1f, 2.0f * bb.max.y(), 0.01f) - center - labels["z"]?.position = Vector3f(-0.1f, 0.01f, 2.0f * bb.max.z()) - center - - this.needsUpdate = true - this.needsUpdateWorld = true - - this.dirty = true + labels["0"]?.spatial()?.position = bb.min - Vector3f(0.1f, 0.0f, 0.0f) + labels["x"]?.spatial()?.position = Vector3f(2.0f * bb.max.x() + 0.1f, 0.01f, 0.01f) - center + labels["y"]?.spatial()?.position = Vector3f(-0.1f, 2.0f * bb.max.y(), 0.01f) - center + labels["z"]?.spatial()?.position = Vector3f(-0.1f, 0.01f, 2.0f * bb.max.z()) - center + + spatial { + needsUpdate = true + needsUpdateWorld = true + } + geometry { + dirty = true + } name = "Bounding Grid of ${node.name}" } ?: logger.error("Bounding box of $b is null") diff --git a/src/main/kotlin/graphics/scenery/Box.kt b/src/main/kotlin/graphics/scenery/Box.kt index df2d82b8b..d1b5f3a0d 100644 --- a/src/main/kotlin/graphics/scenery/Box.kt +++ b/src/main/kotlin/graphics/scenery/Box.kt @@ -1,5 +1,6 @@ package graphics.scenery +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.* import org.joml.Vector3f import kotlin.jvm.JvmOverloads @@ -23,112 +24,113 @@ open class Box @JvmOverloads constructor(val sizes: Vector3f = Vector3f(1.0f, 1. side2 * sizes.y(), side2 * sizes.z()) - vertices = BufferUtils.allocateFloatAndPut(floatArrayOf( - // Front - -sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), - sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), - sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), - -sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), + geometry { - // Right - sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), - sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), - sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), - sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), + vertices = BufferUtils.allocateFloatAndPut(floatArrayOf( + // Front + -sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), + sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), + sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), + -sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), - // Back - -sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), - -sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), - sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), - sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), + // Right + sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), + sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), + sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), + sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), - // Left - -sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), - -sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), - -sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), - -sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), + // Back + -sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), + -sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), + sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), + sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), - // Bottom - -sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), - -sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), - sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), - sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), - // Top - -sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), - sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), - sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), - -sizes.x() * side2, side2*sizes.y(), -side2*sizes.z() - )) + // Left + -sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), + -sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), + -sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), + -sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), - val flip: Float = if(insideNormals) { -1.0f } else { 1.0f } - normals = BufferUtils.allocateFloatAndPut(floatArrayOf( - // Front - 0.0f, 0.0f, 1.0f*flip, - 0.0f, 0.0f, 1.0f*flip, - 0.0f, 0.0f, 1.0f*flip, - 0.0f, 0.0f, 1.0f*flip, - // Right - 1.0f*flip, 0.0f, 0.0f, - 1.0f*flip, 0.0f, 0.0f, - 1.0f*flip, 0.0f, 0.0f, - 1.0f*flip, 0.0f, 0.0f, - // Back - 0.0f, 0.0f, -1.0f*flip, - 0.0f, 0.0f, -1.0f*flip, - 0.0f, 0.0f, -1.0f*flip, - 0.0f, 0.0f, -1.0f*flip, - // Left - -1.0f*flip, 0.0f, 0.0f, - -1.0f*flip, 0.0f, 0.0f, - -1.0f*flip, 0.0f, 0.0f, - -1.0f*flip, 0.0f, 0.0f, - // Bottom - 0.0f, -1.0f*flip, 0.0f, - 0.0f, -1.0f*flip, 0.0f, - 0.0f, -1.0f*flip, 0.0f, - 0.0f, -1.0f*flip, 0.0f, - // Top - 0.0f, 1.0f*flip, 0.0f, - 0.0f, 1.0f*flip, 0.0f, - 0.0f, 1.0f*flip, 0.0f, - 0.0f, 1.0f*flip, 0.0f - )) + // Bottom + -sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), + -sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), + sizes.x() * side2, -side2*sizes.y(), -side2*sizes.z(), + sizes.x() * side2, -side2*sizes.y(), side2*sizes.z(), + // Top + -sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), + sizes.x() * side2, side2*sizes.y(), side2*sizes.z(), + sizes.x() * side2, side2*sizes.y(), -side2*sizes.z(), + -sizes.x() * side2, side2*sizes.y(), -side2*sizes.z() + )) - indices = BufferUtils.allocateIntAndPut(intArrayOf( - 0, 1, 2, 0, 2, 3, - 4, 5, 6, 4, 6, 7, - 8, 9, 10, 8, 10, 11, - 12, 13, 14, 12, 14, 15, - 16, 17, 18, 16, 18, 19, - 20, 21, 22, 20, 22, 23 - )) - - texcoords = BufferUtils.allocateFloatAndPut(floatArrayOf( - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f, - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f - )) + val flip: Float = if(insideNormals) { -1.0f } else { 1.0f } + normals = BufferUtils.allocateFloatAndPut(floatArrayOf( + // Front + 0.0f, 0.0f, 1.0f*flip, + 0.0f, 0.0f, 1.0f*flip, + 0.0f, 0.0f, 1.0f*flip, + 0.0f, 0.0f, 1.0f*flip, + // Right + 1.0f*flip, 0.0f, 0.0f, + 1.0f*flip, 0.0f, 0.0f, + 1.0f*flip, 0.0f, 0.0f, + 1.0f*flip, 0.0f, 0.0f, + // Back + 0.0f, 0.0f, -1.0f*flip, + 0.0f, 0.0f, -1.0f*flip, + 0.0f, 0.0f, -1.0f*flip, + 0.0f, 0.0f, -1.0f*flip, + // Left + -1.0f*flip, 0.0f, 0.0f, + -1.0f*flip, 0.0f, 0.0f, + -1.0f*flip, 0.0f, 0.0f, + -1.0f*flip, 0.0f, 0.0f, + // Bottom + 0.0f, -1.0f*flip, 0.0f, + 0.0f, -1.0f*flip, 0.0f, + 0.0f, -1.0f*flip, 0.0f, + 0.0f, -1.0f*flip, 0.0f, + // Top + 0.0f, 1.0f*flip, 0.0f, + 0.0f, 1.0f*flip, 0.0f, + 0.0f, 1.0f*flip, 0.0f, + 0.0f, 1.0f*flip, 0.0f + )) + indices = BufferUtils.allocateIntAndPut(intArrayOf( + 0, 1, 2, 0, 2, 3, + 4, 5, 6, 4, 6, 7, + 8, 9, 10, 8, 10, 11, + 12, 13, 14, 12, 14, 15, + 16, 17, 18, 16, 18, 19, + 20, 21, 22, 20, 22, 23 + )) + texcoords = BufferUtils.allocateFloatAndPut(floatArrayOf( + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f, + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + )) + } boundingBox = generateBoundingBox() } @@ -145,7 +147,9 @@ open class Box @JvmOverloads constructor(val sizes: Vector3f = Vector3f(1.0f, 1. val innerSize = outerSize - Vector3f(1.0f, 1.0f, 1.0f) * wallThickness * 0.5f val inner = Box(innerSize, insideNormals = true) - inner.material.cullingMode = Material.CullingMode.Front + inner.material { + cullingMode = Material.CullingMode.Front + } container.addChild(inner) return container diff --git a/src/main/kotlin/graphics/scenery/Camera.kt b/src/main/kotlin/graphics/scenery/Camera.kt index 32a92069a..fae6bd382 100644 --- a/src/main/kotlin/graphics/scenery/Camera.kt +++ b/src/main/kotlin/graphics/scenery/Camera.kt @@ -1,6 +1,10 @@ package graphics.scenery import graphics.scenery.primitives.TextBoard +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.spatial.DefaultSpatial +import graphics.scenery.attribute.spatial.HasCustomSpatial import graphics.scenery.utils.extensions.minus import graphics.scenery.utils.extensions.plus import graphics.scenery.utils.extensions.times @@ -21,7 +25,7 @@ import kotlin.math.tan * @constructor Creates a new camera with default position and right-handed * coordinate system. */ -open class Camera : Node("Camera") { +open class Camera : DefaultNode("Camera"), HasRenderable, HasMaterial, HasCustomSpatial { /** Enum class for camera projection types */ enum class ProjectionType { @@ -59,46 +63,17 @@ open class Camera : Node("Camera") { /** Disables culling for this camera. */ var disableCulling: Boolean = false - /** View matrix of the camera. Setting the view matrix will re-set the forward - * vector of the camera according to the given matrix. - */ - override var view: Matrix4f = Matrix4f().identity() - set(m) { - m.let { - this.right = Vector3f(m.get(0, 0), m.get(1, 0), m.get(2, 0)).normalize() - this.up = Vector3f(m.get(0, 1), m.get(1, 1), m.get(2, 1)).normalize() - this.forward = Vector3f(m.get(0, 2), m.get(1, 2), m.get(2, 2)).normalize() * -1.0f - - this.viewSpaceTripod = cameraTripod() - - this.needsUpdate = true - this.needsUpdateWorld = true - - if(!targeted) { - this.target = this.position + this.forward - } - } - field = m - } - - /** Rotation of the camera. The rotation is applied after the view matrix */ - override var rotation: Quaternionf = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f) - set(q) { - q.let { - field = q - val m = Matrix4f().set(q) - this.forward = Vector3f(m.get(0, 2), m.get(1, 2), m.get(2, 2)).normalize() * -1.0f - this.viewSpaceTripod = cameraTripod() - - this.needsUpdate = true - this.needsUpdateWorld = true - } - } - init { this.nodeType = "Camera" this.viewSpaceTripod = cameraTripod() this.name = "Camera-${counter.incrementAndGet()}" + addSpatial() + addRenderable() + addMaterial() + } + + override fun createSpatial(): CameraSpatial { + return CameraSpatial(this) } /** @@ -139,12 +114,14 @@ open class Camera : Node("Camera") { this.width = width this.height = height - this.projection = Matrix4f().perspective( - this.fov / 180.0f * Math.PI.toFloat(), - width.toFloat() / height.toFloat(), - this.nearPlaneDistance, - this.farPlaneDistance - ) + spatial { + this.projection = Matrix4f().perspective( + this@Camera.fov / 180.0f * Math.PI.toFloat(), + width.toFloat() / height.toFloat(), + this@Camera.nearPlaneDistance, + this@Camera.farPlaneDistance + ) + } this.projectionType = ProjectionType.Perspective } @@ -160,83 +137,12 @@ open class Camera : Node("Camera") { this.width = width this.height = height - this.projection = Matrix4f().orthoSymmetric(width.toFloat(), height.toFloat(), nearPlaneLocation, farPlaneLocation) + spatial { + this.projection = Matrix4f().orthoSymmetric(width.toFloat(), height.toFloat(), nearPlaneLocation, farPlaneLocation) + } this.projectionType = ProjectionType.Orthographic } - /** - * Returns this camera's transformation matrix. - */ - open fun getTransformation(): Matrix4f { - val tr = Matrix4f().translate(this.position * (-1.0f)) - val r = Matrix4f().set(this.rotation) - - return r * tr - } - - /** - * Returns this camera's transformation matrix, including a - * [preRotation] that is applied before the camera's transformation. - */ - open fun getTransformation(preRotation: Quaternionf): Matrix4f { - val tr = Matrix4f().translate(this.position * (-1.0f)) - val r = Matrix4f().set(preRotation * this.rotation) - - return r * tr - } - - /** - * Returns this camera's transformation for eye with index [eye]. - */ - open fun getTransformationForEye(eye: Int): Matrix4f { - val tr = Matrix4f().translate(this.position * (-1.0f)) - val r = Matrix4f().set(this.rotation) - - return r * tr - } - - /** - * Transforms a 3D vector from view space to world coordinates. - * - * @param v - The vector to be transformed into world space. - * @return Vector3f - [v] transformed into world space. - */ - fun viewToWorld(v: Vector3f): Vector4f = - Matrix4f(this.view).invert().transform(Vector4f(v.x(), v.y(), v.z(), 1.0f)) - - /** - * Transforms a 4D vector from view space to world coordinates. - * - * @param v - The vector to be transformed into world space. - * @return Vector3f - [v] transformed into world space. - */ - fun viewToWorld(v: Vector4f): Vector4f = - Matrix4f(this.view).invert().transform(v) - - /** - * Transforms a 2D [vector] in screen space to view space and returns this 3D vector. - */ - fun viewportToView(vector: Vector2f): Vector3f { - return Matrix4f(projection).invert().transform(Vector4f(vector.x, vector.y, 0.0f, 1.0f)).xyz() - } - - /** - * Transforms a 2D/3D [vector] from NDC coordinates to world coordinates. - * If the vector is 2D, [nearPlaneDistance] is assumed for the Z value, otherwise - * the Z value from the vector is taken. - */ - fun viewportToWorld(vector: Vector2f): Vector3f { - val pv = Matrix4f(projection) - pv.mul(getTransformation()) - val ipv = Matrix4f(pv).invert() - - var worldSpace = ipv.transform(Vector4f(vector.x(), vector.y(), 0.0f, 1.0f)) - - worldSpace = worldSpace.times(1.0f/worldSpace.w()) -// worldSpace.set(2, offset) - return worldSpace.xyz() - } - /** * Returns the list of objects (as [Scene.RaycastResult]) under the screen space position * indicated by [x] and [y], sorted by their distance to the observer. @@ -262,7 +168,7 @@ open class Camera : Node("Camera") { * Returns (worldPos, worldDir) */ fun screenPointToRay(x: Int, y: Int): Pair { - val view = (if (targeted) target - position else forward).normalize() + val view = (if (targeted) target - spatial().position else forward).normalize() var h = Vector3f(view).cross(up).normalize() var v = Vector3f(h).cross(view) @@ -276,8 +182,8 @@ open class Camera : Node("Camera") { val posX = (x - width / 2.0f) / (width / 2.0f) val posY = -1.0f * (y - height / 2.0f) / (height / 2.0f) - val worldPos = position + view * nearPlaneDistance + h * posX + v * posY - val worldDir = (worldPos - position).normalize() + val worldPos = spatial().position + view * nearPlaneDistance + h * posX + v * posY + val worldDir = (worldPos - spatial().position).normalize() return Pair(worldPos, worldDir) } @@ -299,7 +205,7 @@ open class Camera : Node("Camera") { */ fun canSee(node: Node): Boolean { // TODO: Figure out how to efficiently cull instances - if(disableCulling || node.instances.size > 0 || node is DisableFrustumCulling) { + if(disableCulling || node is InstancedNode || node is DisableFrustumCulling) { return true } @@ -310,7 +216,7 @@ open class Camera : Node("Camera") { val sphereX = 1.0f/cos(angleX) val (x, y, z) = viewSpaceTripod - val v = bs.origin - position + val v = bs.origin - spatial().position var result = true // check whether the sphere is within the Z bounds @@ -373,8 +279,10 @@ open class Camera : Node("Camera") { tb.fontColor = messageColor tb.backgroundColor = backgroundColor tb.text = message - tb.scale = Vector3f(size, size, size) - tb.position = Vector3f(0.0f, 0.0f, -1.0f * distance) + tb.spatial { + scale = Vector3f(size, size, size) + position = Vector3f(0.0f, 0.0f, -1.0f * distance) + } @Suppress("UNCHECKED_CAST") val messages = metadata.getOrPut("messages", { mutableListOf() }) as? MutableList? @@ -392,13 +300,156 @@ open class Camera : Node("Camera") { } } - override fun composeModel() { - model = getTransformation().invert() - } - companion object { protected val counter = AtomicInteger(0) } + + + open class CameraSpatial(val camera: Camera): DefaultSpatial(camera) { + /** View matrix of the camera. Setting the view matrix will re-set the forward + * vector of the camera according to the given matrix. + */ + override var view: Matrix4f = Matrix4f().identity() + set(m) { + m.let { + camera.right = Vector3f(m.get(0, 0), m.get(1, 0), m.get(2, 0)).normalize() + camera.up = Vector3f(m.get(0, 1), m.get(1, 1), m.get(2, 1)).normalize() + camera.forward = Vector3f(m.get(0, 2), m.get(1, 2), m.get(2, 2)).normalize() * -1.0f + + camera.viewSpaceTripod = camera.cameraTripod() + + this.needsUpdate = true + this.needsUpdateWorld = true + + if(!camera.targeted) { + camera.target = this.position + camera.forward + } + } + field = m + } + + /** Rotation of the camera. The rotation is applied after the view matrix */ + override var rotation: Quaternionf = Quaternionf(0.0f, 0.0f, 0.0f, 1.0f) + set(q) { + q.let { + field = q + val m = Matrix4f().set(q) + camera.forward = Vector3f(m.get(0, 2), m.get(1, 2), m.get(2, 2)).normalize() * -1.0f + camera.viewSpaceTripod = camera.cameraTripod() + + this.needsUpdate = true + this.needsUpdateWorld = true + } + } + + override fun composeModel() { + model = getTransformation().invert() + } + + /** + * Returns this camera's transformation matrix. + */ + open fun getTransformation(): Matrix4f { + val tr = Matrix4f().translate(this.position * (-1.0f)) + val r = Matrix4f().set(this.rotation) + + return r * tr + } + + /** + * Returns this camera's transformation matrix, including a + * [preRotation] that is applied before the camera's transformation. + */ + open fun getTransformation(preRotation: Quaternionf): Matrix4f { + val tr = Matrix4f().translate(this.position * (-1.0f)) + val r = Matrix4f().set(preRotation * this.rotation) + + return r * tr + } + + /** + * Returns this camera's transformation for eye with index [eye]. + */ + open fun getTransformationForEye(eye: Int): Matrix4f { + val tr = Matrix4f().translate(this.position * (-1.0f)) + val r = Matrix4f().set(this.rotation) + + return r * tr + } + + /** + * Transforms a 3D vector from view space to world coordinates. + * + * @param v - The vector to be transformed into world space. + * @return Vector3f - [v] transformed into world space. + */ + fun viewToWorld(v: Vector3f): Vector4f = + Matrix4f(this.view).invert().transform(Vector4f(v.x(), v.y(), v.z(), 1.0f)) + + /** + * Transforms a 4D vector from view space to world coordinates. + * + * @param v - The vector to be transformed into world space. + * @return Vector3f - [v] transformed into world space. + */ + fun viewToWorld(v: Vector4f): Vector4f = + Matrix4f(view).invert().transform(v) + + /** + * Transforms a 2D [vector] in screen space to view space and returns this 3D vector. + */ + fun viewportToView(vector: Vector2f): Vector3f { + return Matrix4f(projection).invert().transform(Vector4f(vector.x, vector.y, 0.0f, 1.0f)).xyz() + } + + /** + * Transforms a 2D/3D [vector] from NDC coordinates to world coordinates. + * If the vector is 2D, [nearPlaneDistance] is assumed for the Z value, otherwise + * the Z value from the vector is taken. + */ + fun viewportToWorld(vector: Vector2f): Vector3f { + val pv = Matrix4f(projection) + pv.mul(getTransformation()) + val ipv = Matrix4f(pv).invert() + + var worldSpace = ipv.transform(Vector4f(vector.x(), vector.y(), 0.0f, 1.0f)) + + worldSpace = worldSpace.times(1.0f/worldSpace.w()) +// worldSpace.set(2, offset) + return worldSpace.xyz() + } + + } + + @Deprecated(message = "", replaceWith = ReplaceWith("spatial().viewportToWorld(vector)")) + fun viewportToWorld(vector: Vector2f): Vector3f { + return spatial().viewportToWorld(vector) + } + + @Deprecated(message = "", replaceWith = ReplaceWith("spatial().viewportToView(vector)")) + fun viewportToView(vector: Vector2f): Vector3f { + return spatial().viewportToView(vector) + } + + @Deprecated(message = "", replaceWith = ReplaceWith("spatial().viewToWorld(vector)")) + fun viewToWorld(vector: Vector4f): Vector4f { + return spatial().viewToWorld(vector) + } + + @Deprecated(message = "", replaceWith = ReplaceWith("spatial().viewToWorld(vector)")) + fun viewToWorld(vector: Vector3f): Vector4f { + return spatial().viewToWorld(vector) + } + + @Deprecated(message = "", replaceWith = ReplaceWith("spatial().getTransformationForEye(eye)")) + fun getTransformationForEye(eye: Int): Matrix4f { + return spatial().getTransformationForEye(eye) + } + + @Deprecated(message = "", replaceWith = ReplaceWith("spatial().getTransformation(preRotation)")) + open fun getTransformation(preRotation: Quaternionf): Matrix4f { + return spatial().getTransformation(preRotation) + } } diff --git a/src/main/kotlin/graphics/scenery/DefaultNode.kt b/src/main/kotlin/graphics/scenery/DefaultNode.kt new file mode 100644 index 000000000..1017dc914 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/DefaultNode.kt @@ -0,0 +1,188 @@ +package graphics.scenery + +import graphics.scenery.attribute.DefaultAttributesMap +import graphics.scenery.utils.LazyLogger +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import org.joml.Vector3f +import java.sql.Timestamp +import java.util.* +import java.util.concurrent.CopyOnWriteArrayList +import java.util.concurrent.locks.ReentrantLock +import java.util.function.Consumer +import kotlin.collections.HashMap +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.findAnnotation +import kotlin.reflect.full.memberProperties +import kotlin.reflect.jvm.isAccessible + +open class DefaultNode(override var name: String = "Node") : Node { + @Transient override var children = CopyOnWriteArrayList() + @Transient override var linkedNodes = CopyOnWriteArrayList() + @Transient override var metadata: HashMap = HashMap() + override var parent: Node? = null + override var createdAt = (Timestamp(Date().time).time) + override var modifiedAt = 0L + override var discoveryBarrier = false + override var update: ArrayList<() -> Unit> = ArrayList() + override var postUpdate: ArrayList<() -> Unit> = ArrayList() + override var visible: Boolean = true + set(v) { + children.forEach { it.visible = v } + field = v + } + override var initialized: Boolean = false + override var state : State = State.Ready + override var lock: ReentrantLock = ReentrantLock() + override fun init(): Boolean { + return true + } + + override var nodeType = "Node" + override var boundingBox: OrientedBoundingBox? = null + override val logger by LazyLogger() + + private val properties = DefaultAttributesMap() + + private var uuid: UUID = UUID.randomUUID() + override fun getUuid(): UUID { + return uuid + } + + override fun getAttributes() = properties + + override fun addChild(child: Node) { + child.parent = this + this.children.add(child) + + val scene = this.getScene() ?: return + scene.sceneSize.incrementAndGet() + if(scene.onChildrenAdded.isNotEmpty()) { + GlobalScope.launch { + scene.onChildrenAdded.forEach { it.value.invoke(this@DefaultNode, child) } + } + } + } + + override fun removeChild(child: Node): Boolean { + this.getScene()?.sceneSize?.decrementAndGet() + GlobalScope.launch { this@DefaultNode.getScene()?.onChildrenRemoved?.forEach { it.value.invoke(this@DefaultNode, child) } } + + return this.children.remove(child) + } + + override fun removeChild(name: String): Boolean { + for (c in this.children) { + if (c.name.compareTo(name) == 0) { + c.parent = null + this.children.remove(c) + return true + } + } + + return false + } + + override fun getChildrenByName(name: String): List { + return children.filter { it.name == name } + } + + override fun getScene(): Scene? { + var p: Node? = this + while(p !is Scene && p != null) { + p = p.parent + } + return p as? Scene + } + + override fun getMaximumBoundingBox(): OrientedBoundingBox { + if(boundingBox == null && children.size == 0) { + return OrientedBoundingBox(this,0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) + } + + if(children.none { it !is BoundingGrid }) { + return OrientedBoundingBox(this,boundingBox?.min ?: Vector3f(0.0f, 0.0f, 0.0f), boundingBox?.max ?: Vector3f(0.0f, 0.0f, 0.0f)) + } + + return children + .filter { it !is BoundingGrid }.map { it.getMaximumBoundingBox().translate(it.spatialOrNull()?.position ?: Vector3f(0f, 0f, 0f)) } + .fold(boundingBox ?: OrientedBoundingBox(this, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), { lhs, rhs -> lhs.expand(lhs, rhs) }) + } + + override fun runRecursive(func: (Node) -> Unit) { + func.invoke(this) + + children.forEach { it.runRecursive(func) } + } + + override fun runRecursive(func: Consumer) { + func.accept(this) + + children.forEach { it.runRecursive(func) } + } + + @Transient private val shaderPropertyFieldCache = HashMap>() + override fun getShaderProperty(name: String): Any? { + // first, try to find the shader property in the cache, and either return it, + // or, if the member of the cache is the shaderProperties HashMap, return the member of it. + val f = shaderPropertyFieldCache[name] + if (f != null) { + val value = f.get(this) + + return if (value !is HashMap<*, *>) { + f.get(this) + } else { + value.get(name) + } + } + + // First fallthrough: In case the field is not in the cache, check all member properties + // containing the [ShaderProperty] annotation. If the property is found, + // cache it for performance reasons and return it. + val field = this.javaClass.kotlin.memberProperties.find { it.name == name && it.findAnnotation() != null } + + if (field != null) { + field.isAccessible = true + + shaderPropertyFieldCache.put(name, field) + + return field.get(this) + } + + // Last fallthrough: If [name] cannot be found as a property, try to locate it in the + // shaderProperties HashMap and return it. If it cannot be found here either, return null. + this.javaClass.kotlin.memberProperties + .filter { it.findAnnotation() != null } + .forEach { + it.isAccessible = true + if(logger.isTraceEnabled) { + logger.trace("ShaderProperty of ${this.name}: ${it.name} ${it.get(this)?.javaClass}") + } + } + val mappedProperties = this.javaClass.kotlin.memberProperties + .firstOrNull { + it.findAnnotation() != null && it.get(this) is HashMap<*, *> && it.name == "shaderProperties" + } + + return if (mappedProperties == null) { + logger.warn("Could not find shader property '$name' in class properties or properties map!") + null + } else { + mappedProperties.isAccessible = true + + @Suppress("UNCHECKED_CAST") + val map = mappedProperties.get(this) as? HashMap + if (map == null) { + logger.warn("$this: $name not found in shaderProperties hash map") + null + } else { + shaderPropertyFieldCache.put(name, mappedProperties) + map.get(name) + } + } + } + + override fun toString(): String { + return "$name(${javaClass.simpleName})" + } +} diff --git a/src/main/kotlin/graphics/scenery/DetachedHeadCamera.kt b/src/main/kotlin/graphics/scenery/DetachedHeadCamera.kt index 5fc833a96..f60a018a7 100644 --- a/src/main/kotlin/graphics/scenery/DetachedHeadCamera.kt +++ b/src/main/kotlin/graphics/scenery/DetachedHeadCamera.kt @@ -22,16 +22,6 @@ import kotlin.reflect.KProperty */ class DetachedHeadCamera(@Transient var tracker: TrackerInput? = null) : Camera() { - override var projection: Matrix4f = Matrix4f().identity() - get() = if(tracker != null && tracker is Display && tracker?.initializedAndWorking() == true) { - (tracker as? Display)?.getEyeProjection(0) ?: super.projection - } else { - super.projection - } - set(value) { - super.projection = value - field = value - } override var width: Int = 0 get() = if(tracker != null && tracker is Display && tracker?.initializedAndWorking() == true) { @@ -114,39 +104,48 @@ class DetachedHeadCamera(@Transient var tracker: TrackerInput? = null) : Camera( this.name = "DetachedHeadCamera-${tracker ?: "${counter.getAndIncrement()}"}" } - /** - * Returns this camera's transformation matrix, taking an eventually existing [TrackerInput] - * into consideration as well. - */ - override fun getTransformation(): Matrix4f { - val tr = Matrix4f().translate(this.position * (-1.0f)) + override fun createSpatial(): CameraSpatial { + return object: CameraSpatial(this) { + override var projection: Matrix4f = Matrix4f().identity() + get() = if(tracker != null && tracker is Display && tracker?.initializedAndWorking() == true) { + (tracker as? Display)?.getEyeProjection(0) ?: super.projection + } else { + super.projection + } + set(value) { + super.projection = value + field = value + } + /** + * Returns this camera's transformation matrix, taking an eventually existing [TrackerInput] + * into consideration as well. + */ + override fun getTransformation(): Matrix4f { + val tr = Matrix4f().translate(this.position * (-1.0f)) // val r = Matrix4f.fromQuaternion(this.rotation) // val hr = Matrix4f.fromQuaternion(this.headOrientation) - - return tracker?.getWorkingTracker()?.getPose()?.times(tr) ?: Matrix4f().set(rotation) * tr - } - - /** - * Returns this camera's transformation for eye with index [eye], taking an eventually existing [TrackerInput] - * into consideration as well. - */ - override fun getTransformationForEye(eye: Int): Matrix4f { - val tr = Matrix4f().translate(this.position * (-1.0f)) + return tracker?.getWorkingTracker()?.getPose()?.times(tr) ?: Matrix4f().set(spatial().rotation) * tr + } + /** + * Returns this camera's transformation for eye with index [eye], taking an eventually existing [TrackerInput] + * into consideration as well. + */ + override fun getTransformationForEye(eye: Int): Matrix4f { + val tr = Matrix4f().translate(this.position * (-1.0f)) // val r = Matrix4f.fromQuaternion(this.rotation) // val hr = Matrix4f.fromQuaternion(this.headOrientation) - - return tracker?.getWorkingTracker()?.getPoseForEye(eye)?.times(tr) ?: Matrix4f().set(rotation) * tr - } - - /** - * Returns this camera's transformation matrix, including a - * [preRotation] that is applied before the camera's transformation. - */ - override fun getTransformation(preRotation: Quaternionf): Matrix4f { - val tr = Matrix4f().translate(this.position * (-1.0f) + this.headPosition) - val r = Matrix4f().set(preRotation.mul(this.rotation)) - - return r * tr + return tracker?.getWorkingTracker()?.getPoseForEye(eye)?.times(tr) ?: Matrix4f().set(spatial().rotation) * tr + } + /** + * Returns this camera's transformation matrix, including a + * [preRotation] that is applied before the camera's transformation. + */ + override fun getTransformation(preRotation: Quaternionf): Matrix4f { + val tr = Matrix4f().translate(this.position * (-1.0f) + this@DetachedHeadCamera.headPosition) + val r = Matrix4f().set(preRotation.mul(this.rotation)) + return r * tr + } + } } companion object { diff --git a/src/main/kotlin/graphics/scenery/DirectionalLight.kt b/src/main/kotlin/graphics/scenery/DirectionalLight.kt index 18138f1fc..140e63e34 100644 --- a/src/main/kotlin/graphics/scenery/DirectionalLight.kt +++ b/src/main/kotlin/graphics/scenery/DirectionalLight.kt @@ -1,6 +1,7 @@ package graphics.scenery import graphics.scenery.geometry.GeometryType +import graphics.scenery.attribute.material.Material import org.joml.Vector3f /** @@ -38,43 +39,49 @@ class DirectionalLight(var direction: Vector3f = Vector3f(0.0f, 1.0f, 0.0f)) : L @ShaderProperty var debugMode = 0 init { - // fake geometry - this.vertices = BufferUtils.allocateFloatAndPut( - floatArrayOf( - -1.0f, -1.0f, 0.0f, - 1.0f, -1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, - -1.0f, 1.0f, 0.0f)) - - this.normals = BufferUtils.allocateFloatAndPut( - floatArrayOf( - 1.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f)) - - this.texcoords = BufferUtils.allocateFloatAndPut( - floatArrayOf( - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f)) - - this.indices = BufferUtils.allocateIntAndPut( - intArrayOf(0, 1, 2, 0, 2, 3)) - - this.geometryType = GeometryType.TRIANGLES - this.vertexSize = 3 - this.texcoordSize = 2 - - material.blending.transparent = true - material.blending.colorBlending = Blending.BlendOp.add - material.blending.sourceColorBlendFactor = Blending.BlendFactor.One - material.blending.destinationColorBlendFactor = Blending.BlendFactor.One - material.blending.sourceAlphaBlendFactor = Blending.BlendFactor.One - material.blending.destinationAlphaBlendFactor = Blending.BlendFactor.One - material.blending.alphaBlending = Blending.BlendOp.add - material.cullingMode = Material.CullingMode.Front - material.depthTest = Material.DepthTest.Greater + geometry { + + + // fake geometry + this.vertices = BufferUtils.allocateFloatAndPut( + floatArrayOf( + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f)) + + this.normals = BufferUtils.allocateFloatAndPut( + floatArrayOf( + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f)) + + this.texcoords = BufferUtils.allocateFloatAndPut( + floatArrayOf( + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f)) + + this.indices = BufferUtils.allocateIntAndPut( + intArrayOf(0, 1, 2, 0, 2, 3)) + + this.geometryType = GeometryType.TRIANGLES + this.vertexSize = 3 + this.texcoordSize = 2 + } + + material { + blending.transparent = true + blending.colorBlending = Blending.BlendOp.add + blending.sourceColorBlendFactor = Blending.BlendFactor.One + blending.destinationColorBlendFactor = Blending.BlendFactor.One + blending.sourceAlphaBlendFactor = Blending.BlendFactor.One + blending.destinationAlphaBlendFactor = Blending.BlendFactor.One + blending.alphaBlending = Blending.BlendOp.add + cullingMode = Material.CullingMode.Front + depthTest = Material.DepthTest.Greater + } } } diff --git a/src/main/kotlin/graphics/scenery/FullscreenObject.kt b/src/main/kotlin/graphics/scenery/FullscreenObject.kt index 6f229954a..3e79b7970 100644 --- a/src/main/kotlin/graphics/scenery/FullscreenObject.kt +++ b/src/main/kotlin/graphics/scenery/FullscreenObject.kt @@ -1,6 +1,7 @@ package graphics.scenery import graphics.scenery.geometry.GeometryType +import graphics.scenery.attribute.material.Material /** * @@ -9,44 +10,46 @@ import graphics.scenery.geometry.GeometryType */ class FullscreenObject : Mesh("FullscreenObject") { init { - // fake geometry - this.vertices = BufferUtils.allocateFloatAndPut( - floatArrayOf( - -1.0f, -1.0f, 0.0f, - 1.0f, -1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, - -1.0f, 1.0f, 0.0f)) - - this.normals = BufferUtils.allocateFloatAndPut( - floatArrayOf( - 1.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f)) - - this.texcoords = BufferUtils.allocateFloatAndPut( - floatArrayOf( - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f)) - - this.indices = BufferUtils.allocateIntAndPut( - intArrayOf(0, 1, 2, 0, 2, 3)) - - this.geometryType = GeometryType.TRIANGLES - this.vertexSize = 3 - this.texcoordSize = 2 - - material = ShaderMaterial.fromClass(FullscreenObject::class.java) - - material.cullingMode = Material.CullingMode.None - material.blending.transparent = true - material.blending.sourceColorBlendFactor = Blending.BlendFactor.One - material.blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha - material.blending.sourceAlphaBlendFactor = Blending.BlendFactor.One - material.blending.destinationAlphaBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha - material.blending.colorBlending = Blending.BlendOp.add - material.blending.alphaBlending = Blending.BlendOp.add + geometry { + // fake geometry + this.vertices = BufferUtils.allocateFloatAndPut( + floatArrayOf( + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f)) + + this.normals = BufferUtils.allocateFloatAndPut( + floatArrayOf( + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f)) + + this.texcoords = BufferUtils.allocateFloatAndPut( + floatArrayOf( + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f)) + + this.indices = BufferUtils.allocateIntAndPut( + intArrayOf(0, 1, 2, 0, 2, 3)) + + this.geometryType = GeometryType.TRIANGLES + this.vertexSize = 3 + this.texcoordSize = 2 + } + + setMaterial(ShaderMaterial.fromClass(FullscreenObject::class.java)) { + cullingMode = Material.CullingMode.None + blending.transparent = true + blending.sourceColorBlendFactor = Blending.BlendFactor.One + blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha + blending.sourceAlphaBlendFactor = Blending.BlendFactor.One + blending.destinationAlphaBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha + blending.colorBlending = Blending.BlendOp.add + blending.alphaBlending = Blending.BlendOp.add + } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/graphics/scenery/Group.kt b/src/main/kotlin/graphics/scenery/Group.kt index 43e7904ef..9f3eaa8bc 100644 --- a/src/main/kotlin/graphics/scenery/Group.kt +++ b/src/main/kotlin/graphics/scenery/Group.kt @@ -1,9 +1,19 @@ package graphics.scenery +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.spatial.HasSpatial + /** * Node to group other Nodes together. This is just a convenience class, * that -- apart from its name -- does not provide additional functionality. * * @author Ulrik Günther */ -class Group : Node("Group") +class Group : DefaultNode("Group"), HasSpatial, HasRenderable, HasMaterial { + init { + addSpatial() + addRenderable() + addMaterial() + } +} diff --git a/src/main/kotlin/graphics/scenery/Icosphere.kt b/src/main/kotlin/graphics/scenery/Icosphere.kt index c703a1e73..2246774b3 100644 --- a/src/main/kotlin/graphics/scenery/Icosphere.kt +++ b/src/main/kotlin/graphics/scenery/Icosphere.kt @@ -140,49 +140,52 @@ open class Icosphere(val radius: Float, val subdivisions: Int) : Mesh("Icosphere createBaseVertices(vertexBuffer, indexBuffer) val faces = refineTriangles(subdivisions, vertexBuffer, indexBuffer) - vertices = BufferUtils.allocateFloat(faces.size * 3 * 3) - normals = BufferUtils.allocateFloat(faces.size * 3 * 3) - texcoords = BufferUtils.allocateFloat(faces.size * 3 * 2) - indices = BufferUtils.allocateInt(0) - - faces.forEach { f -> - val v1 = vertexBuffer[f.first] - val v2 = vertexBuffer[f.second] - val v3 = vertexBuffer[f.third] - val uv1 = vertexToUV(v1.normalize()) - val uv2 = vertexToUV(v2.normalize()) - val uv3 = vertexToUV(v3.normalize()) - - (v1 * radius).get(vertices).position(vertices.position() + 3) - (v2 * radius).get(vertices).position(vertices.position() + 3) - (v3 * radius).get(vertices).position(vertices.position() + 3) - - v1.get(normals).position(normals.position() + 3) - v2.get(normals).position(normals.position() + 3) - v3.get(normals).position(normals.position() + 3) - - val uvNormal = (uv2 - uv1).cross(uv3 - uv1) - if(uvNormal.z() < 0.0f) { - if(uv1.x() < 0.25f) { - uv1.x = uv1.x() + 1.0f - } - if(uv2.x() < 0.25f) { - uv2.x = uv2.x() + 1.0f - } - if(uv3.x() < 0.25f) { - uv3.x = uv3.x() + 1.0f + geometry { + + vertices = BufferUtils.allocateFloat(faces.size * 3 * 3) + normals = BufferUtils.allocateFloat(faces.size * 3 * 3) + texcoords = BufferUtils.allocateFloat(faces.size * 3 * 2) + indices = BufferUtils.allocateInt(0) + + faces.forEach { f -> + val v1 = vertexBuffer[f.first] + val v2 = vertexBuffer[f.second] + val v3 = vertexBuffer[f.third] + val uv1 = vertexToUV(v1.normalize()) + val uv2 = vertexToUV(v2.normalize()) + val uv3 = vertexToUV(v3.normalize()) + + (v1 * radius).get(vertices).position(vertices.position() + 3) + (v2 * radius).get(vertices).position(vertices.position() + 3) + (v3 * radius).get(vertices).position(vertices.position() + 3) + + v1.get(normals).position(normals.position() + 3) + v2.get(normals).position(normals.position() + 3) + v3.get(normals).position(normals.position() + 3) + + val uvNormal = (uv2 - uv1).cross(uv3 - uv1) + if(uvNormal.z() < 0.0f) { + if(uv1.x() < 0.25f) { + uv1.x = uv1.x() + 1.0f + } + if(uv2.x() < 0.25f) { + uv2.x = uv2.x() + 1.0f + } + if(uv3.x() < 0.25f) { + uv3.x = uv3.x() + 1.0f + } } + + uv1.get(texcoords).position(texcoords.position() + 2) + uv2.get(texcoords).position(texcoords.position() + 2) + uv3.get(texcoords).position(texcoords.position() + 2) } - uv1.get(texcoords).position(texcoords.position() + 2) - uv2.get(texcoords).position(texcoords.position() + 2) - uv3.get(texcoords).position(texcoords.position() + 2) + vertices.flip() + normals.flip() + texcoords.flip() } - vertices.flip() - normals.flip() - texcoords.flip() - boundingBox = generateBoundingBox() } diff --git a/src/main/kotlin/graphics/scenery/InstancedNode.kt b/src/main/kotlin/graphics/scenery/InstancedNode.kt new file mode 100644 index 000000000..9390c2e2b --- /dev/null +++ b/src/main/kotlin/graphics/scenery/InstancedNode.kt @@ -0,0 +1,81 @@ +package graphics.scenery + +import graphics.scenery.attribute.DelegatesProperties +import graphics.scenery.attribute.DelegationType +import graphics.scenery.attribute.geometry.DelegatesGeometry +import graphics.scenery.attribute.geometry.Geometry +import graphics.scenery.attribute.material.DelegatesMaterial +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.DelegatesRenderable +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.renderable.Renderable +import graphics.scenery.attribute.spatial.HasSpatial +import org.joml.Matrix4f +import java.util.concurrent.CopyOnWriteArrayList + +open class InstancedNode(template: Node, override var name: String = "InstancedNode") : DefaultNode(name), DelegatesProperties, DelegatesRenderable, + DelegatesGeometry, DelegatesMaterial { + /** instances */ + val instances = CopyOnWriteArrayList() + /** instanced properties */ + val instancedProperties = LinkedHashMap Any>() + private val delegationType: DelegationType = DelegationType.ForEachNode + override fun getDelegationType(): DelegationType { + return delegationType + } + + var template: Node? = null + set(node) { +// val instancedMaterial = ShaderMaterial.fromFiles("DefaultDeferredInstanced.vert", "DefaultDeferred.frag") +// node?.renderable { +// material = instancedMaterial +// } + instancedProperties.put("ModelMatrix", { node?.spatialOrNull()?.model ?: Matrix4f() }) + field = node + } + // val updateStrategy = // TODO make enum for different strategies -> one time, every second (or fixed time interval), each frame + + init { + this.template = template + boundingBox = template.generateBoundingBox() + } + + override fun getDelegateRenderable(): Renderable? { + return template?.renderableOrNull() + } + + override fun getDelegateMaterial(): Material? { + return template?.materialOrNull() + } + + override fun getDelegateGeometry(): Geometry? { + return template?.geometryOrNull() + } + + fun addInstance(): Instance { + val node = Instance(this) + node.instancedProperties.put("ModelMatrix", { node.spatial().world }) + node.boundingBox = node.generateBoundingBox() + instances.add(node) + return node + } + + override fun generateBoundingBox(): OrientedBoundingBox? { + //TODO? generate joint boundingbox of all instances, set bounding box + return template?.generateBoundingBox() + } + + class Instance(val instancedParent : InstancedNode, override var name: String = "Instance") : DefaultNode(name), + HasRenderable, HasSpatial { + var instancedProperties = LinkedHashMap Any>() + + init { + addRenderable() + addSpatial() + } + + override fun generateBoundingBox(): OrientedBoundingBox? { + return instancedParent.template?.generateBoundingBox() + } + } +} diff --git a/src/main/kotlin/graphics/scenery/Light.kt b/src/main/kotlin/graphics/scenery/Light.kt index ef83a323a..db7b53a2d 100644 --- a/src/main/kotlin/graphics/scenery/Light.kt +++ b/src/main/kotlin/graphics/scenery/Light.kt @@ -45,7 +45,9 @@ abstract class Light(name: String = "Light") : Mesh(name) { return tetrahedron.map { position -> val light = T::class.createInstance() - light.position = position + center + light.spatial { + this.position = position + center + } light.emissionColor = color light.intensity = intensity if(light is PointLight) { diff --git a/src/main/kotlin/graphics/scenery/Mesh.kt b/src/main/kotlin/graphics/scenery/Mesh.kt index 44fc7b410..66f8497cb 100644 --- a/src/main/kotlin/graphics/scenery/Mesh.kt +++ b/src/main/kotlin/graphics/scenery/Mesh.kt @@ -3,10 +3,14 @@ package graphics.scenery import org.joml.Vector3f import gnu.trove.map.hash.THashMap import gnu.trove.set.hash.TLinkedHashSet -import graphics.scenery.* import graphics.scenery.geometry.GeometryType -import graphics.scenery.geometry.HasGeometry import graphics.scenery.primitives.PointCloud +import graphics.scenery.attribute.geometry.HasGeometry +import graphics.scenery.attribute.material.DefaultMaterial +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.spatial.HasSpatial import graphics.scenery.textures.Texture import graphics.scenery.utils.Image import graphics.scenery.utils.LazyLogger @@ -21,35 +25,25 @@ import java.io.FileNotFoundException import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.FloatBuffer -import java.nio.IntBuffer import java.nio.file.Files import java.util.ArrayList import java.util.HashMap /** - * Simple Mesh class to store geometry, inherits from [HasGeometry]. + * Simple Mesh class to store geometry. * Can also be used for grouping objects easily. * - * Also see [HasGeomerty] for more interface details. - * * @author Ulrik Günther */ -open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { - /** Vertex storage array. Also see [HasGeometry] */ - @Transient final override var vertices: FloatBuffer = BufferUtils.allocateFloat(0) - /** Normal storage array. Also see [HasGeometry] */ - @Transient final override var normals: FloatBuffer = BufferUtils.allocateFloat(0) - /** Texcoord storage array. Also see [HasGeometry] */ - @Transient final override var texcoords: FloatBuffer = BufferUtils.allocateFloat(0) - /** Index storage array. Also see [HasGeometry] */ - @Transient final override var indices: IntBuffer = BufferUtils.allocateInt(0) - - /** Vertex element size. Also see [HasGeometry] */ - final override var vertexSize = 3 - /** Texcoord element size. Also see [HasGeometry] */ - final override var texcoordSize = 2 - /** Geometry type of the Mesh. Also see [HasGeometry] and [GeometryType] */ - final override var geometryType = GeometryType.TRIANGLES +open class Mesh(override var name: String = "Mesh") : DefaultNode(name), HasRenderable, HasMaterial, HasSpatial, + HasGeometry { + + init { + addGeometry() + addRenderable() + addMaterial() + addSpatial() + } /** * Reads geometry from a file given by [filename]. The extension of [filename] will determine @@ -99,7 +93,7 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { } val lines = Files.lines(p) - var currentMaterial: Material? = Material() + var currentMaterial: Material? = DefaultMaterial() fun addTexture(material: Material?, slot: String, file: String) { if(material == null) { @@ -123,7 +117,7 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { "#" -> { } "newmtl" -> { - val m = Material() + val m = DefaultMaterial() m.name = tokens[1] materials[tokens[1]] = m @@ -293,7 +287,8 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { var count = 0 - var targetObject: HasGeometry = this + val meshGeometry = geometryOrNull() + var targetObject: Node = this val triangleIndices = intArrayOf(0, 1, 2) val quadIndices = intArrayOf(0, 1, 2, 0, 2, 3) @@ -347,18 +342,18 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { vertexCountMap.forEach { objectName, objectVertexCount -> vertexBuffers[objectName] = Triple( - MemoryUtil.memAlloc(objectVertexCount * vertexSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(), - MemoryUtil.memAlloc(objectVertexCount * vertexSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(), - MemoryUtil.memAlloc(objectVertexCount * texcoordSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer() + MemoryUtil.memAlloc(objectVertexCount * meshGeometry.vertexSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(), + MemoryUtil.memAlloc(objectVertexCount * meshGeometry.vertexSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer(), + MemoryUtil.memAlloc(objectVertexCount * meshGeometry.texcoordSize * 4).order(ByteOrder.nativeOrder()).asFloatBuffer() ) indexBuffers[objectName] = ArrayList(objectVertexCount) faceBuffers[objectName] = TIndexedHashSet(((faceCountMap[objectName] ?: throw IllegalStateException("Face count map does not contain $objectName")) * 1.5).toInt()) } - val tmpV = ArrayList(vertexCountMap.values.sum() * vertexSize) - val tmpN = ArrayList(vertexCountMap.values.sum() * vertexSize) - val tmpUV = ArrayList(vertexCountMap.values.sum() * texcoordSize) + val tmpV = ArrayList(vertexCountMap.values.sum() * meshGeometry.vertexSize) + val tmpN = ArrayList(vertexCountMap.values.sum() * meshGeometry.vertexSize) + val tmpUV = ArrayList(vertexCountMap.values.sum() * meshGeometry.texcoordSize) lines = Files.lines(p) @@ -381,9 +376,9 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { } 'u' -> { - if (targetObject is Node && importMaterials) { + if (importMaterials) { materials[tokens.substringAfter(" ").trim().trimEnd()]?.let { - (targetObject as? Node)?.material = it + targetObject.addAttribute(Material::class.java, it) } } } @@ -510,20 +505,22 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { calculateNormals(vb.first, vb.second) } - targetObject.vertices = vb.first - targetObject.normals = vb.second - targetObject.texcoords = vb.third - targetObject.indices = BufferUtils.allocateIntAndPut(ib.toIntArray()) - targetObject.geometryType = GeometryType.TRIANGLES - - targetObject.vertices.flip() - targetObject.normals.flip() - targetObject.texcoords.flip() - - vertexCount += targetObject.vertices.limit() - normalCount += targetObject.normals.limit() - uvCount += targetObject.texcoords.limit() - indexCount += targetObject.indices.limit() + targetObject.ifGeometry { + this.vertices = vb.first + this.normals = vb.second + this.texcoords = vb.third + this.indices = BufferUtils.allocateIntAndPut(ib.toIntArray()) + this.geometryType = GeometryType.TRIANGLES + + this.vertices.flip() + this.normals.flip() + this.texcoords.flip() + + vertexCount += this.vertices.limit() + normalCount += this.normals.limit() + uvCount += this.texcoords.limit() + indexCount += this.indices.limit() + } // add new child mesh if (this is PointCloud) { @@ -531,7 +528,7 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { child.name = tokens.substringAfter(" ").trim().trimEnd() name = tokens.substringAfter(" ").trim().trimEnd() if (!importMaterials) { - child.material = Material() + setMaterial(DefaultMaterial()) } (targetObject as? PointCloud)?.boundingBox = OrientedBoundingBox(this, boundingBox) @@ -544,7 +541,7 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { child.name = tokens.substringAfter(" ").trim().trimEnd() name = tokens.substringAfter(" ").trim().trimEnd() if (!importMaterials) { - child.material = Material() + child.setMaterial(DefaultMaterial()) } (targetObject as? Mesh)?.boundingBox = OrientedBoundingBox(this, boundingBox) @@ -575,19 +572,21 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { calculateNormals(vb.first, vb.second) } - targetObject.vertices = vb.first - targetObject.normals = vb.second - targetObject.texcoords = vb.third - targetObject.indices = BufferUtils.allocateIntAndPut(ib.toIntArray()) + targetObject.ifGeometry { + this.vertices = vb.first + this.normals = vb.second + this.texcoords = vb.third + this.indices = BufferUtils.allocateIntAndPut(ib.toIntArray()) - targetObject.vertices.flip() - targetObject.normals.flip() - targetObject.texcoords.flip() + this.vertices.flip() + this.normals.flip() + this.texcoords.flip() - vertexCount += targetObject.vertices.limit() - normalCount += targetObject.normals.limit() - uvCount += targetObject.texcoords.limit() - indexCount += targetObject.indices.limit() + vertexCount += this.vertices.limit() + normalCount += this.normals.limit() + uvCount += this.texcoords.limit() + indexCount += this.indices.limit() + } if (this is PointCloud) { (targetObject as? PointCloud)?.boundingBox = OrientedBoundingBox(this, boundingBox) @@ -595,7 +594,7 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { (targetObject as? Mesh)?.boundingBox = OrientedBoundingBox(this, boundingBox) } - logger.info("Read ${vertexCount / vertexSize}/${normalCount / vertexSize}/${uvCount / texcoordSize}/$indexCount v/n/uv/i of model $name in ${(end - start) / 1e6} ms") + logger.info("Read ${vertexCount / meshGeometry.vertexSize}/${normalCount / meshGeometry.vertexSize}/${uvCount / meshGeometry.texcoordSize}/$indexCount v/n/uv/i of model $name in ${(end - start) / 1e6} ms") return this } @@ -839,10 +838,12 @@ open class Mesh(override var name: String = "Mesh") : Node(name), HasGeometry { val end = System.nanoTime() logger.info("Read ${vbuffer.size} vertices/${nbuffer.size} normals of model $name in ${(end - start) / 1e6} ms") - vertices = BufferUtils.allocateFloatAndPut(vbuffer.toFloatArray()) - normals = BufferUtils.allocateFloatAndPut(nbuffer.toFloatArray()) - texcoords = BufferUtils.allocateFloat(0) - indices = BufferUtils.allocateInt(0) + geometry { + vertices = BufferUtils.allocateFloatAndPut(vbuffer.toFloatArray()) + normals = BufferUtils.allocateFloatAndPut(nbuffer.toFloatArray()) + texcoords = BufferUtils.allocateFloat(0) + indices = BufferUtils.allocateInt(0) + } logger.info("Bounding box of $name is ${boundingBox.joinToString(",")}") this.boundingBox = OrientedBoundingBox(this, boundingBox) diff --git a/src/main/kotlin/graphics/scenery/Node.kt b/src/main/kotlin/graphics/scenery/Node.kt index 8d0f451c2..b0a60c6a5 100644 --- a/src/main/kotlin/graphics/scenery/Node.kt +++ b/src/main/kotlin/graphics/scenery/Node.kt @@ -1,206 +1,152 @@ package graphics.scenery -import graphics.scenery.backends.Renderer -import graphics.scenery.geometry.HasGeometry -import graphics.scenery.utils.LazyLogger import graphics.scenery.geometry.GeometryType +import graphics.scenery.attribute.AttributesMap +import graphics.scenery.attribute.geometry.Geometry +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.Renderable +import graphics.scenery.attribute.spatial.Spatial import graphics.scenery.utils.MaybeIntersects -import graphics.scenery.utils.extensions.* -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch import net.imglib2.Localizable import net.imglib2.RealLocalizable -import net.imglib2.RealPositionable import org.joml.Matrix4f import org.joml.Quaternionf import org.joml.Vector3f -import org.joml.Vector4f import java.io.Serializable -import java.sql.Timestamp +import java.nio.FloatBuffer +import java.nio.IntBuffer import java.util.* import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.locks.ReentrantLock import java.util.function.Consumer import kotlin.collections.ArrayList -import kotlin.collections.HashMap -import kotlin.math.max -import kotlin.math.min -import kotlin.properties.Delegates -import kotlin.reflect.KProperty -import kotlin.reflect.KProperty1 -import kotlin.reflect.full.findAnnotation -import kotlin.reflect.full.memberProperties -import kotlin.reflect.jvm.isAccessible - -/** - * Class describing a [Node] of a [Scene], inherits from [Renderable] - * - * @author Ulrik Günther - * @constructor Creates a node with default settings, e.g. identity matrices - * for model, view, projection, etc. - * @property[name] The name of the [Node] - */ -open class Node(open var name: String = "Node") : Renderable, Serializable, RealLocalizable, RealPositionable { - protected val logger by LazyLogger() - /** Unique ID of the Node */ - var uuid: UUID = UUID.randomUUID() - private set - /** Hash map used for storing metadata for the Node. [Renderer] implementations use - * it to e.g. store renderer-specific state. */ - @Transient var metadata: HashMap = HashMap() - - /** Material of the Node */ - final override var material: Material = Material.DefaultMaterial() - /** Initialisation flag. */ - override var initialized: Boolean = false +interface Node : Serializable { + var name: String + var nodeType: String + /** Children of the Node. */ + var children: CopyOnWriteArrayList + /** Other nodes that have linked transforms. */ + var linkedNodes: CopyOnWriteArrayList + /** Parent node of this node. */ + var parent: Node? + /** Creation timestamp of the node. */ + var createdAt: Long + /** Modification timestamp of the node. */ + var modifiedAt: Long + /** Flag to set whether the object is visible or not. */ + var visible: Boolean + var discoveryBarrier: Boolean + /** Hash map used for storing metadata for the Node. */ + var metadata: HashMap + /** Node update routine, called before updateWorld */ + var update: ArrayList<() -> Unit> + /** Node update routine, called after updateWorld */ + var postUpdate: ArrayList<() -> Unit> + /** Whether the object has been initialized. Used by renderers. */ + var initialized: Boolean /** State of the Node **/ - override var state : State = State.Ready - /** Whether the Node is dirty and needs updating. */ - override var dirty: Boolean = true - /** Flag to set whether the Node is visible or not, recursively affects children. */ - override var visible: Boolean = true - set(v) { - children.forEach { it.visible = v } - field = v - } - /** instanced properties */ - var instancedProperties = LinkedHashMap Any>() - /** The Node's lock. */ - override var lock: ReentrantLock = ReentrantLock() + var state: State + /** [ReentrantLock] to be used if the object is being updated and should not be + * touched in the meantime. */ + var lock: ReentrantLock + /** Initialisation function for the object */ + fun init(): Boolean + + val logger: org.slf4j.Logger /** bounding box **/ - var boundingBox: OrientedBoundingBox? = null + var boundingBox: OrientedBoundingBox? - /** - * Initialisation function for the Node. - * - * @return True of false whether initialisation was successful. - */ - override fun init(): Boolean { - return true - } + fun getAttributes(): AttributesMap - /** Name of the Node's type */ - var nodeType = "Node" - protected set + fun getAttributeOrNull(attributeType: Class): U? { + return getAttributes().get(attributeType) + } - /** Node update routine, called before updateWorld */ - var update: ArrayList<() -> Unit> = ArrayList() + fun addAttribute(attributeType: Class, attribute: U) { + getAttributes().put(attributeType, attribute) + } - /** Node update routine, called after updateWorld */ - var postUpdate: ArrayList<() -> Unit> = ArrayList() + fun addAttribute(attributeType: Class, attribute: U, block: U.() -> Unit) { + attribute.block() + addAttribute(attributeType, attribute) + } - /** World transform matrix. Will create inverse [iworld] upon modification. */ - override var world: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } - /** Inverse [world] transform matrix. */ - override var iworld: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } - /** Local model transform matrix. Will create inverse [imodel] upon modification. */ - override var model: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } - /** Inverse [world] transform matrix. */ - override var imodel: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + fun ifHasAttribute(attributeType: Class, block: U.() -> Unit) : U? { + val attribute = getAttributeOrNull(attributeType) ?: return null + attribute.block() + return attribute + } - /** View matrix. Will create inverse [iview] upon modification. */ - override var view: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } - /** Inverse [view] matrix. */ - override var iview: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + @Throws(IllegalStateException::class) + fun getAttribute(attributeType: Class, block: U.() -> Unit) : U { + val attribute = getAttribute(attributeType) + attribute.block() + return attribute + } - /** Projection matrix. Will create inverse [iprojection] upon modification. */ - override var projection: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } - /** Inverse [projection] transform matrix. */ - override var iprojection: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + @Throws(IllegalStateException::class) + fun getAttribute(attributeType: Class) : U { + return getAttributeOrNull(attributeType) ?: throw IllegalStateException("Node doesn't have attribute named " + attributeType) + } - /** ModelView matrix. Will create inverse [imodelView] upon modification. */ - override var modelView: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } - /** Inverse [modelView] transform matrix. */ - override var imodelView: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + fun ifSpatial(block: Spatial.() -> Unit): Spatial? { + return ifHasAttribute(Spatial::class.java, block) + } - /** ModelViewProjection matrix. */ - override var mvp: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + fun spatialOrNull(): Spatial? { + return ifSpatial {} + } - /** Local position of the Node, used to construct [model] matrix. Setting will trigger [model] and [world] update. */ - override var position: Vector3f by Delegates.observable(Vector3f(0.0f, 0.0f, 0.0f)) { property, old, new -> propertyChanged(property, old, new) } + fun ifGeometry(block: Geometry.() -> Unit): Geometry? { + return ifHasAttribute(Geometry::class.java, block) + } - /** x/y/z scale of the Node, used to construct [model]. Setting will trigger [model] and [world] update. */ - override var scale: Vector3f by Delegates.observable(Vector3f(1.0f, 1.0f, 1.0f)) { property, old, new -> propertyChanged(property, old, new) } + fun geometryOrNull(): Geometry? { + return ifGeometry {} + } - /** Rotation of the Node, used to construct [model]. Setting will trigger [model] and [world] update. */ - override var rotation: Quaternionf by Delegates.observable(Quaternionf(0.0f, 0.0f, 0.0f, 1.0f)) { property, old, new -> propertyChanged(property, old, new) } + fun ifRenderable(block: Renderable.() -> Unit): Renderable? { + return ifHasAttribute(Renderable::class.java, block) + } - /** Children of the Node. */ - @Transient var children: CopyOnWriteArrayList - /** Other nodes that have linked transforms. */ - @Transient var linkedNodes: CopyOnWriteArrayList - /** Parent node of this node. */ - var parent: Node? = null + fun renderableOrNull(): Renderable? { + return ifRenderable {} + } - /** Flag to store whether the node is a billboard and will always face the camera. */ - override var isBillboard: Boolean = false + fun ifMaterial(block: Material.() -> Unit): Material? { + return ifHasAttribute(Material::class.java, block) + } - /** Creation timestamp of the node. */ - var createdAt: Long = 0 - /** Modification timestamp of the node. */ - var modifiedAt: Long = 0 - - /** Stores whether the [model] matrix needs an update. */ - var wantsComposeModel = true - /** Stores whether the [model] matrix needs an update. */ - var needsUpdate = true - /** Stores whether the [world] matrix needs an update. */ - var needsUpdateWorld = true - - var discoveryBarrier = false - - val instances = CopyOnWriteArrayList() - - @Suppress("UNUSED_PARAMETER") - protected fun propertyChanged(property: KProperty<*>, old: R, new: R, custom: String = "") { - if(property.name == "rotation" - || property.name == "position" - || property.name == "scale" - || property.name == custom) { - needsUpdate = true - needsUpdateWorld = true - } + fun materialOrNull(): Material? { + return ifMaterial {} } - init { - createdAt = (Timestamp(Date().time).time) + fun setMaterial(material: Material) { + return addAttribute(Material::class.java, material) + } - children = CopyOnWriteArrayList() - linkedNodes = CopyOnWriteArrayList() - // null should be the signal to use the default shader + fun setMaterial(material: Material, block: Material.() -> Unit) { + return addAttribute(Material::class.java, material, block) } + /** Unique ID of the Node */ + fun getUuid(): UUID + /** * Attaches a child node to this node. * * @param[child] The child to attach to this node. */ - fun addChild(child: Node) { - child.parent = this - this.children.add(child) - - val scene = this.getScene() ?: return - scene.sceneSize.incrementAndGet() - if(scene.onChildrenAdded.isNotEmpty()) { - GlobalScope.launch { - scene.onChildrenAdded.forEach { it.value.invoke(this@Node, child) } - } - } - } + fun addChild(child: Node) /** * Removes a given node from the set of children of this node. * * @param[child] The child node to remove. */ - fun removeChild(child: Node): Boolean { - this.getScene()?.sceneSize?.decrementAndGet() - GlobalScope.launch { this@Node.getScene()?.onChildrenRemoved?.forEach { it.value.invoke(this@Node, child) } } - - return this.children.remove(child) - } + fun removeChild(child: Node): Boolean /** * Removes a given node from the set of children of this node. @@ -208,794 +154,499 @@ open class Node(open var name: String = "Node") : Renderable, Serializable, Real * * @param[name] The name of the child node to remove. */ - fun removeChild(name: String): Boolean { - for (c in this.children) { - if (c.name.compareTo(name) == 0) { - c.parent = null - this.children.remove(c) - return true - } - } - - return false - } + fun removeChild(name: String): Boolean /** * Returns all children with the given [name]. */ - fun getChildrenByName(name: String): List { - return children.filter { it.name == name } - } + fun getChildrenByName(name: String): List /** - * Routine to call if the node has special requirements for drawing. + * Returns the [Scene] this Node is ultimately attached to. + * Will return null in case the Node is not attached to a [Scene] yet. */ - open fun draw() { - - } - - internal open fun preUpdate(renderer: Renderer, hub: Hub?) { - - } + fun getScene(): Scene? /** - * PreDraw function, to be called before the actual rendering, useful for - * per-timestep preparation. + * Runs an operation recursively on the node itself and all child nodes. + * + * @param[func] A lambda accepting a [Node], representing this node and its potential children. */ - open fun preDraw(): Boolean { - return true - } + fun runRecursive(func: (Node) -> Unit) /** - * Update the the [world] matrix of the [Node]. - * - * This method will update the [model] and [world] matrices of the node, - * if [needsUpdate] is true, or [force] is true. If [recursive] is true, - * this method will also recurse into the [children] and [linkedNodes] of - * the node and update these as well. - * - * @param[recursive] Whether the [children] should be recursed into. - * @param[force] Force update irrespective of [needsUpdate] state. + * Generates an [OrientedBoundingBox] for this [Node]. This will take + * geometry information into consideration if this Node implements [Geometry]. + * In case a bounding box cannot be determined, the function will return null. */ - @Synchronized fun updateWorld(recursive: Boolean, force: Boolean = false) { - update.forEach { it.invoke() } - - if ((needsUpdate or force)) { - if(wantsComposeModel) { - this.composeModel() - } - - needsUpdate = false - needsUpdateWorld = true - } - - if (needsUpdateWorld or force) { - val p = parent - if (p == null || p is Scene) { - world.set(model) - } else { - world.set(p.world) - world.mul(this.model) - } - } - - if (recursive) { - this.children.forEach { it.updateWorld(true, needsUpdateWorld) } - // also update linked nodes -- they might need updated - // model/view/proj matrices as well - this.linkedNodes.forEach { it.updateWorld(true, needsUpdateWorld) } - } - - if(needsUpdateWorld) { - needsUpdateWorld = false + fun generateBoundingBox(): OrientedBoundingBox? { + val geometry = geometryOrNull() + if(geometry == null) { + logger.warn("$name: Assuming 3rd party BB generation") + return boundingBox + } else { + boundingBox = geometry.generateBoundingBox(children) + return boundingBox } - - postUpdate.forEach { it.invoke() } } /** - * This method composes the [model] matrices of the node from its - * [position], [scale] and [rotation]. + * Returns the maximum [OrientedBoundingBox] of this [Node] and all its children. */ - open fun composeModel() { - @Suppress("SENSELESS_COMPARISON") - if(position != null && rotation != null && scale != null) { - model.translationRotateScale( - Vector3f(position.x(), position.y(), position.z()), - this.rotation, - Vector3f(this.scale.x(), this.scale.y(), this.scale.z())) - } - } + fun getMaximumBoundingBox(): OrientedBoundingBox /** - * Generates an [OrientedBoundingBox] for this [Node]. This will take - * geometry information into consideration if this Node implements [HasGeometry]. - * In case a bounding box cannot be determined, the function will return null. + * Runs an operation recursively on the node itself and all child nodes. + * + * @param[func] A Java [Consumer] accepting a [Node], representing this node and its potential children. */ - open fun generateBoundingBox(): OrientedBoundingBox? { - if (this is HasGeometry) { - val vertexBufferView = vertices.asReadOnlyBuffer() - val boundingBoxCoords = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) - - if (vertexBufferView.capacity() == 0 || vertexBufferView.remaining() == 0) { - boundingBox = if(!children.none()) { - getMaximumBoundingBox() - } else { - logger.warn("$name: Zero vertices currently, returning empty bounding box") - OrientedBoundingBox(this,0.0f, 0.0f, 0.0f, - 0.0f, 0.0f, 0.0f) - } + fun runRecursive(func: Consumer) - return boundingBox - } else { - - val vertex = floatArrayOf(0.0f, 0.0f, 0.0f) - vertexBufferView.get(vertex) - - boundingBoxCoords[0] = vertex[0] - boundingBoxCoords[1] = vertex[0] - - boundingBoxCoords[2] = vertex[1] - boundingBoxCoords[3] = vertex[1] - - boundingBoxCoords[4] = vertex[2] - boundingBoxCoords[5] = vertex[2] - - while(vertexBufferView.remaining() >= 3) { - vertexBufferView.get(vertex) - - boundingBoxCoords[0] = minOf(boundingBoxCoords[0], vertex[0]) - boundingBoxCoords[2] = minOf(boundingBoxCoords[2], vertex[1]) - boundingBoxCoords[4] = minOf(boundingBoxCoords[4], vertex[2]) - - boundingBoxCoords[1] = maxOf(boundingBoxCoords[1], vertex[0]) - boundingBoxCoords[3] = maxOf(boundingBoxCoords[3], vertex[1]) - boundingBoxCoords[5] = maxOf(boundingBoxCoords[5], vertex[2]) - } - - logger.debug("$name: Calculated bounding box with ${boundingBoxCoords.joinToString(", ")}") - return OrientedBoundingBox(this, Vector3f(boundingBoxCoords[0], boundingBoxCoords[2], boundingBoxCoords[4]), - Vector3f(boundingBoxCoords[1], boundingBoxCoords[3], boundingBoxCoords[5])) - } - } else { - logger.warn("$name: Assuming 3rd party BB generation") - return boundingBox - } - } - - @Transient private val shaderPropertyFieldCache = HashMap>() /** * Returns the [ShaderProperty] given by [name], if it exists and is declared by * this class or a subclass inheriting from [Node]. Returns null if the [name] can * neither be found as a property, or as member of the shaderProperties HashMap the Node * might declare. */ - fun getShaderProperty(name: String): Any? { - // first, try to find the shader property in the cache, and either return it, - // or, if the member of the cache is the shaderProperties HashMap, return the member of it. - val f = shaderPropertyFieldCache[name] - if (f != null) { - val value = f.get(this) - - return if (value !is HashMap<*, *>) { - f.get(this) - } else { - value.get(name) - } - } - - // First fallthrough: In case the field is not in the cache, check all member properties - // containing the [ShaderProperty] annotation. If the property is found, - // cache it for performance reasons and return it. - val field = this.javaClass.kotlin.memberProperties.find { it.name == name && it.findAnnotation() != null } - - if (field != null) { - field.isAccessible = true + fun getShaderProperty(name: String): Any? - shaderPropertyFieldCache.put(name, field) - - return field.get(this) - } + companion object NodeHelpers { + /** + * Depth-first search for elements in a Scene. + * + * @param[origin] The [Node] to start the search at. + * @param[func] A lambda taking a [Node] and returning a Boolean for matching. + * @return A list of [Node]s that match [func]. + */ + @Suppress("unused") + fun discover(origin: Node, func: (Node) -> Boolean): ArrayList { + val visited = HashSet() + val matched = ArrayList() - // Last fallthrough: If [name] cannot be found as a property, try to locate it in the - // shaderProperties HashMap and return it. If it cannot be found here either, return null. - this.javaClass.kotlin.memberProperties - .filter { it.findAnnotation() != null } - .forEach { - it.isAccessible = true - if(logger.isTraceEnabled) { - logger.trace("ShaderProperty of ${this@Node.name}: ${it.name} ${it.get(this)?.javaClass}") + fun discover(current: Node, f: (Node) -> Boolean) { + if (!visited.add(current)) return + for (v in current.children) { + if (f(v)) { + matched.add(v) + } + discover(v, f) } } - val mappedProperties = this.javaClass.kotlin.memberProperties - .firstOrNull { - it.findAnnotation() != null && it.get(this) is HashMap<*, *> && it.name == "shaderProperties" - } - return if (mappedProperties == null) { - logger.warn("Could not find shader property '$name' in class properties or properties map!") - null - } else { - mappedProperties.isAccessible = true - - @Suppress("UNCHECKED_CAST") - val map = mappedProperties.get(this) as? HashMap - if (map == null) { - logger.warn("$this: $name not found in shaderProperties hash map") - null - } else { - shaderPropertyFieldCache.put(name, mappedProperties) - map.get(name) - } - } - } + discover(origin, func) - /** - * Returns the [Scene] this Node is ultimately attached to. - * Will return null in case the Node is not attached to a [Scene] yet. - */ - fun getScene(): Scene? { - var p: Node? = this - while(p !is Scene && p != null) { - p = p.parent + return matched } - - return p as? Scene } - /** - * Centers the [Node] on a given position. - * - * @param[position] - the position to center the [Node] on. - * @return Vector3f - the center offset calculcated for the [Node]. - */ - fun centerOn(position: Vector3f): Vector3f { - val min = getMaximumBoundingBox().min - val max = getMaximumBoundingBox().max - - val center = (max - min) * 0.5f - this.position = position - (getMaximumBoundingBox().min + center) - - return center + @Deprecated(message = "Moved to attribute material(), see AttributesExample for usage details", replaceWith = ReplaceWith("material()")) + fun getMaterial(): Material { + return this.materialOrNull()!! } - /** - * Taking this [Node]'s [boundingBox] into consideration, puts it above - * the [position] entirely. - */ - fun putAbove(position: Vector3f): Vector3f { - val center = centerOn(position) - - val diffY = center.y() + position.y() - val diff = Vector3f(0.0f, diffY, 0.0f) - this.position = this.position + diff - - return diff - } - - /** - * Fits the [Node] within a box of the given dimension. - * - * @param[sideLength] - The size of the box to fit the [Node] uniformly into. - * @param[scaleUp] - Whether the model should only be scaled down, or also up. - * @return Vector3f - containing the applied scaling - */ - fun fitInto(sideLength: Float, scaleUp: Boolean = false): Vector3f { - val min = getMaximumBoundingBox().min.xyzw() - val max = getMaximumBoundingBox().max.xyzw() - - val maxDimension = (max - min).max() - val scaling = sideLength/maxDimension + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().dirty")) + var dirty: Boolean + get() = this.geometryOrNull()!!.dirty + set(value) { + this.ifGeometry { + dirty = value + } + } - if((scaleUp && scaling > 1.0f) || scaling <= 1.0f) { - this.scale = Vector3f(scaling, scaling, scaling) - } else { - this.scale = Vector3f(1.0f, 1.0f, 1.0f) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().world")) + var world: Matrix4f + get() = this.spatialOrNull()!!.world + set(value) { + this.ifSpatial { + world = value + } } - return this.scale - } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().iworld")) + var iworld: Matrix4f + get() = Matrix4f(spatialOrNull()!!.world).invert() + set(ignored) {} + + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().model")) + var model: Matrix4f + get() = this.spatialOrNull()!!.model + set(value) { + this.ifSpatial { + model = value + } + } - /** - * Orients the Node between points [p1] and [p2], and optionally - * [rescale]s and [reposition]s it. - */ - @JvmOverloads fun orientBetweenPoints(p1: Vector3f, p2: Vector3f, rescale: Boolean = false, reposition: Boolean = false): Quaternionf { - val direction = p2 - p1 - val length = direction.length() + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().imodel")) + var imodel: Matrix4f + get() = Matrix4f(spatialOrNull()!!.model).invert() + set(ignored) {} + + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().view")) + var view: Matrix4f + get() = this.spatialOrNull()!!.view + set(value) { + this.ifSpatial { + view = value + } + } - this.rotation = Quaternionf().rotationTo(Vector3f(0.0f, 1.0f, 0.0f), direction.normalize()) - if(rescale) { - this.scale = Vector3f(1.0f, length, 1.0f) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().iview")) + var iview: Matrix4f + get() = Matrix4f(spatialOrNull()!!.view).invert() + set(ignored) {} + + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().projection")) + var projection: Matrix4f + get() = this.spatialOrNull()!!.projection + set(value) { + this.ifSpatial { + projection = value + } } - if(reposition) { - this.position = Vector3f(p1) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().iprojection")) + var iprojection: Matrix4f + get() = Matrix4f(spatialOrNull()!!.projection).invert() + set(ignored) {} + +// @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().modelView")) +// var modelView: Matrix4f +// get() = this.spatial().modelView +// set(value) { +// this.ifSpatial { +// modelView = value +// } +// } +// +// @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().imodelView")) +// var imodelView: Matrix4f +// get() = this.spatial().imodelView +// set(value) { +// this.ifSpatial { +// imodelView = value +// } +// } +// +// @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().mvp")) +// var mvp: Matrix4f +// get() = this.spatial().mvp +// set(value) { +// this.ifSpatial { +// mvp = value +// } +// } + + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().position")) + var position: Vector3f + get() = this.spatialOrNull()!!.position + set(value) { + this.ifSpatial { + position = value + } } - return this.rotation - } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().scale")) + var scale: Vector3f + get() = this.spatialOrNull()!!.scale + set(value) { + this.ifSpatial { + scale = value + } + } - /** - * Returns the maximum [OrientedBoundingBox] of this [Node] and all its children. - */ - fun getMaximumBoundingBox(): OrientedBoundingBox { - if(boundingBox == null && children.size == 0) { - return OrientedBoundingBox(this,0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().rotation")) + var rotation: Quaternionf + get() = this.spatialOrNull()!!.rotation + set(value) { + this.ifSpatial { + rotation = value + } } - if(children.none { it !is BoundingGrid }) { - return OrientedBoundingBox(this,boundingBox?.min ?: Vector3f(0.0f, 0.0f, 0.0f), boundingBox?.max ?: Vector3f(0.0f, 0.0f, 0.0f)) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().wantsComposeModel")) + var wantsComposeModel: Boolean + get() = this.spatialOrNull()!!.wantsComposeModel + set(value) { + this.ifSpatial { + wantsComposeModel = value + } } - return children - .filter { it !is BoundingGrid }.map { it.getMaximumBoundingBox().translate(it.position) } - .fold(boundingBox ?: OrientedBoundingBox(this, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f), { lhs, rhs -> lhs.expand(lhs, rhs) }) - } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().needsUpdate")) + var needsUpdate: Boolean + get() = this.spatialOrNull()!!.needsUpdate + set(value) { + this.ifSpatial { + needsUpdate = value + } + } - /** - * Checks whether two node's bounding boxes do intersect using a simple bounding sphere test. - */ - fun intersects(other: Node): Boolean { - boundingBox?.let { ownOBB -> - other.boundingBox?.let { otherOBB -> - return ownOBB.intersects(otherOBB) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().needsUpdateWorld")) + var needsUpdateWorld: Boolean + get() = this.spatialOrNull()!!.needsUpdateWorld + set(value) { + this.ifSpatial { + needsUpdateWorld = value } } - return false + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().updateWorld(recursive, force)")) + fun updateWorld(recursive: Boolean, force: Boolean = false) { + ifSpatial { + updateWorld(recursive, force) + } } - /** - * Returns the [Node]'s world position - * - * @returns The position in world space - */ - fun worldPosition(v: Vector3f? = null): Vector3f { - val target = v ?: position - return if(parent is Scene && v == null) { - Vector3f(target) - } else { - world.transform(Vector4f().set(target, 1.0f)).xyz() + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().composeModel()")) + fun composeModel() { + ifSpatial { + composeModel() } } - /** - * Extracts the scaling component from the world matrix. - * - * Is not correct for world matrices with shear! - * - * @return world scale - */ - fun worldScale(): Vector3f { - val wm = world - val sx = Vector3f(wm[0,0],wm[0,1],wm[0,2]).length() - val sy = Vector3f(wm[1,0],wm[1,1],wm[1,2]).length() - val sz = Vector3f(wm[2,0],wm[2,1],wm[2,2]).length() - - return Vector3f(sx,sy,sz) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().centerOn(position)")) + fun centerOn(position: Vector3f): Vector3f { + return this.spatialOrNull()!!.centerOn(position) } - /** - * Runs an operation recursively on the node itself and all child nodes. - * - * @param[func] A lambda accepting a [Node], representing this node and its potential children. - */ - fun runRecursive(func: (Node) -> Unit) { - func.invoke(this) - - children.forEach { it.runRecursive(func) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().putAbove(position)")) + fun putAbove(position: Vector3f): Vector3f { + return this.spatialOrNull()!!.putAbove(position) } - /** - * Runs an operation recursively on the node itself and all child nodes. - * - * @param[func] A Java [Consumer] accepting a [Node], representing this node and its potential children. - */ - fun runRecursive(func: Consumer) { - func.accept(this) - - children.forEach { it.runRecursive(func) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().fitInto(sideLength, scaleUp)")) + fun fitInto(sideLength: Float, scaleUp: Boolean = false): Vector3f { + return this.spatialOrNull()!!.fitInto(sideLength, scaleUp) } - /** - * Performs a intersection test with an axis-aligned bounding box of this [Node], where - * the test ray originates at [origin] and points into [dir]. - * - * Returns a Pair of Boolean and Float, indicating whether an intersection is possible, - * and at which distance. - * - * Code adapted from [zachamarz](http://gamedev.stackexchange.com/a/18459). - */ - fun intersectAABB(origin: Vector3f, dir: Vector3f): MaybeIntersects { - val bbmin = getMaximumBoundingBox().min.xyzw() - val bbmax = getMaximumBoundingBox().max.xyzw() - - val min = world.transform(bbmin) - val max = world.transform(bbmax) - - // skip if inside the bounding box - if(origin.isInside(min.xyz(), max.xyz())) { - return MaybeIntersects.NoIntersection() - } - - val invDir = Vector3f(1 / (dir.x() + Float.MIN_VALUE), 1 / (dir.y() + Float.MIN_VALUE), 1 / (dir.z() + Float.MIN_VALUE)) - - val t1 = (min.x() - origin.x()) * invDir.x() - val t2 = (max.x() - origin.x()) * invDir.x() - val t3 = (min.y() - origin.y()) * invDir.y() - val t4 = (max.y() - origin.y()) * invDir.y() - val t5 = (min.z() - origin.z()) * invDir.z() - val t6 = (max.z() - origin.z()) * invDir.z() - - val tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6)) - val tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6)) - - // we are in front of the AABB - if (tmax < 0) { - return MaybeIntersects.NoIntersection() - } - - // we have missed the AABB - if (tmin > tmax) { - return MaybeIntersects.NoIntersection() - } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().orientBetweenPoints(p1, p2, rescale, reposition)")) + fun orientBetweenPoints(p1: Vector3f, p2: Vector3f, rescale: Boolean, reposition: Boolean): Quaternionf { + return this.spatialOrNull()!!.orientBetweenPoints(p1, p2, rescale, reposition) + } - // we have a match! calculate entry and exit points - val entry = origin + dir * tmin - val exit = origin + dir * tmax - val localEntry = Matrix4f(world).invert().transform(Vector4f().set(entry, 1.0f)) - val localExit = Matrix4f(world).invert().transform(Vector4f().set(exit, 1.0f)) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().orientBetweenPoints(p1, p2, rescale)")) + fun orientBetweenPoints(p1: Vector3f, p2: Vector3f, rescale: Boolean): Quaternionf { + return this.spatialOrNull()!!.orientBetweenPoints(p1, p2, rescale, false) + } - return MaybeIntersects.Intersection(tmin, entry, exit, localEntry.xyz(), localExit.xyz()) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().orientBetweenPoints(p1, p2)")) + fun orientBetweenPoints(p1: Vector3f, p2: Vector3f): Quaternionf { + return this.spatialOrNull()!!.orientBetweenPoints(p1, p2, false, false) } - private fun Vector3f.isInside(min: Vector3f, max: Vector3f): Boolean { - return this.x() > min.x() && this.x() < max.x() - && this.y() > min.y() && this.y() < max.y() - && this.z() > min.z() && this.z() < max.z() + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().intersects(other)")) + fun intersects(other: Node): Boolean { + return this.spatialOrNull()!!.intersects(other) } - /** - * Write the current position into the passed array. - * - * @param position - * receives current position - */ - override fun localize(position: FloatArray?) { - position?.set(0, this.position.x()) - position?.set(1, this.position.y()) - position?.set(2, this.position.z()) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().worldPosition(v)")) + fun worldPosition(v: Vector3f? = null): Vector3f { + return this.spatialOrNull()!!.worldPosition(v) } - /** - * Write the current position into the passed array. - * - * @param position - * receives current position - */ - override fun localize(position: DoubleArray?) { - position?.set(0, this.position.x().toDouble()) - position?.set(1, this.position.y().toDouble()) - position?.set(2, this.position.z().toDouble()) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().intersectAABB(origin, dir)")) + fun intersectAABB(origin: Vector3f, dir: Vector3f): MaybeIntersects { + return this.spatialOrNull()!!.intersectAABB(origin, dir) } - /** - * Return the current position in a given dimension. - * - * @param d - * dimension - * @return dimension of current position - */ - override fun getFloatPosition(d: Int): Float { - return this.position[d] + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().localize(position)")) + fun localize(position: FloatArray?) { + return this.spatialOrNull()!!.localize(position) } - override fun toString(): String { - return "$name(${javaClass.simpleName})" + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().localize(position)")) + fun localize(position: DoubleArray?) { + return this.spatialOrNull()!!.localize(position) } - /** - * Move by -1 in one dimension. - * - * @param d - * dimension - */ - override fun bck(d: Int) { - move(-1, d) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().getFloatPosition(d)")) + fun getFloatPosition(d: Int): Float { + return this.spatialOrNull()!!.getFloatPosition(d) } - /** - * Move the element in one dimension for some distance. - * - * @param distance - * @param d - */ - override fun move(distance: Float, d: Int) { - setPosition( getFloatPosition(d) + distance, d ) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().bck(d)")) + fun bck(d: Int) { + return this.spatialOrNull()!!.bck(d) } - /** - * Move the element in one dimension for some distance. - * - * @param distance - * @param d - */ - override fun move(distance: Double, d: Int) { - setPosition( getDoublePosition(d) + distance, d ) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance, d)")) + fun move(distance: Float, d: Int) { + return this.spatialOrNull()!!.move(distance, d) } - /** - * Move the element relative to its current location using a - * [RealLocalizable] as distance vector. - * - * @param distance - * relative offset, [RealLocalizable.numDimensions] must - * be [.numDimensions] - */ - override fun move(distance: RealLocalizable?) { - distance?.getDoublePosition(0)?.let { move(it, 0) } - distance?.getDoublePosition(1)?.let { move(it, 1) } - distance?.getDoublePosition(2)?.let { move(it, 2) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance, d)")) + fun move(distance: Double, d: Int) { + return this.spatialOrNull()!!.move(distance, d) } - /** - * Move the element relative to its current location using a float[] as - * distance vector. - * - * @param distance, - * length must be [.numDimensions] - */ - override fun move(distance: FloatArray?) { - distance?.get(0)?.let { move(it, 0 ) } - distance?.get(1)?.let { move(it, 1 ) } - distance?.get(2)?.let { move(it, 2 ) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance)")) + fun move(distance: RealLocalizable?) { + return this.spatialOrNull()!!.move(distance) } - /** - * Move the element relative to its current location using a float[] as - * distance vector. - * - * @param distance, - * length must be [.numDimensions] - */ - override fun move(distance: DoubleArray?) { - distance?.get(0)?.let { move(it, 0 ) } - distance?.get(1)?.let { move(it, 1 ) } - distance?.get(2)?.let { move(it, 2 ) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance)")) + fun move(distance: FloatArray?) { + return this.spatialOrNull()!!.move(distance) } - /** - * Move the element in one dimension for some distance. - * - * @param distance - * relative offset in dimension d - * @param d - * dimension - */ - override fun move(distance: Int, d: Int) { - move( distance.toLong(), d ) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance)")) + fun move(distance: DoubleArray?) { + return this.spatialOrNull()!!.move(distance) } - /** - * Move the element in one dimension for some distance. - * - * @param distance - * relative offset in dimension d - * @param d - * dimension - */ - override fun move(distance: Long, d: Int) { - this.position = this.position + Vector3f().setComponent(d, distance.toFloat()) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance, d)")) + fun move(distance: Int, d: Int) { + return this.spatialOrNull()!!.move(distance, d) } - /** - * Move the element relative to its current location using an - * [Localizable] as distance vector. - * - * @param distance - * relative offset, [Localizable.numDimensions] must be - * [.numDimensions] - */ - override fun move(distance: Localizable?) { - distance?.getDoublePosition(0)?.let { move(it, 0) } - distance?.getDoublePosition(1)?.let { move(it, 1) } - distance?.getDoublePosition(2)?.let { move(it, 2) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance, d)")) + fun move(distance: Long, d: Int) { + return this.spatialOrNull()!!.move(distance, d) } - /** - * Move the element relative to its current location using an int[] as - * distance vector. - * - * @param distance - * relative offset, length must be [.numDimensions] - */ - override fun move(distance: IntArray?) { - distance?.get(0)?.let { move(it, 0 ) } - distance?.get(1)?.let { move(it, 1 ) } - distance?.get(2)?.let { move(it, 2 ) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance)")) + fun move(distance: Localizable?) { + return this.spatialOrNull()!!.move(distance) } - /** - * Move the element relative to its current location using a long[] as - * distance vector. - * - * @param distance - * relative offset, length must be [.numDimensions] - */ - override fun move(distance: LongArray?) { - distance?.get(0)?.let { move(it, 0 ) } - distance?.get(1)?.let { move(it, 1 ) } - distance?.get(2)?.let { move(it, 2 ) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance)")) + fun move(distance: IntArray?) { + return this.spatialOrNull()!!.move(distance) } - /** Gets the space's number of dimensions. */ - override fun numDimensions(): Int { - return 3 + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().move(distance)")) + fun move(distance: LongArray?) { + return this.spatialOrNull()!!.move(distance) } - /** - * Move by 1 in one dimension. - * - * @param d - * dimension - */ - override fun fwd(d: Int) { - move( 1, d) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().numDimensions()")) + fun numDimensions(): Int { + return this.spatialOrNull()!!.numDimensions() } - /** - * Return the current position in a given dimension. - * - * @param d - * dimension - * @return dimension of current position - */ - override fun getDoublePosition(d: Int): Double { - return this.position[d].toDouble() + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().fwd(d)")) + fun fwd(d: Int) { + ifSpatial { + fwd(d) + } } - override fun setPosition(pos: RealLocalizable) { - position.setComponent( 0, pos.getFloatPosition(0) ) - position.setComponent( 1, pos.getFloatPosition(1) ) - position.setComponent( 2, pos.getFloatPosition(2) ) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().getDoublePosition(d)")) + fun getDoublePosition(d: Int): Double { + return this.spatialOrNull()!!.getDoublePosition(d) } - /** - * Set the position of the element. - * - * @param position - * absolute position, length must be - * [.numDimensions] - */ - override fun setPosition(pos: FloatArray?) { - pos?.get(0)?.let { setPosition(it, 0 ) } - pos?.get(1)?.let { setPosition(it, 1 ) } - pos?.get(2)?.let { setPosition(it, 2 ) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos)")) + fun setPosition(pos: RealLocalizable) { + return this.spatialOrNull()!!.setPosition(pos) } - /** - * Set the position of the element. - * - * @param position - * absolute position, length must be - * [.numDimensions] - */ - override fun setPosition(pos: DoubleArray?) { - pos?.get(0)?.let { setPosition(it, 0 ) } - pos?.get(1)?.let { setPosition(it, 1 ) } - pos?.get(2)?.let { setPosition(it, 2 ) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos)")) + fun setPosition(pos: FloatArray?) { + return this.spatialOrNull()!!.setPosition(pos) } - /** - * Set the position of the element for one dimension. - * - * @param position - * @param d - */ - override fun setPosition(pos: Float, d: Int) { - position.setComponent( d, pos ) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos)")) + fun setPosition(pos: DoubleArray?) { + return this.spatialOrNull()!!.setPosition(pos) } - /** - * Set the position of the element for one dimension. - * - * @param position - * @param d - */ - override fun setPosition(pos: Double, d: Int) { - setPosition( pos.toFloat(), d ) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos, d)")) + fun setPosition(pos: Float, d: Int) { + return this.spatialOrNull()!!.setPosition(pos, d) } - /** - * Place the element at the same location as a given [Localizable] - * - * @param position - * absolute position, [Localizable.numDimensions] must be - * [.numDimensions] - */ - override fun setPosition(pos: Localizable?) { - pos?.getIntPosition(0)?.let { setPosition(it, 0 ) } - pos?.getIntPosition(1)?.let { setPosition(it, 1 ) } - pos?.getIntPosition(2)?.let { setPosition(it, 2 ) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos, d)")) + fun setPosition(pos: Double, d: Int) { + return this.spatialOrNull()!!.setPosition(pos, d) } - /** - * Set the position of the element. - * - * @param position - * absolute position, length must be - * [.numDimensions] - */ - override fun setPosition(pos: IntArray?) { - pos?.get(0)?.let { setPosition(it, 0) } - pos?.get(1)?.let { setPosition(it, 1) } - pos?.get(2)?.let { setPosition(it, 2) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos)")) + fun setPosition(pos: Localizable?) { + return this.spatialOrNull()!!.setPosition(pos) } - /** - * Set the position of the element. - * - * @param position - * absolute position, length must be - * [.numDimensions] - */ - override fun setPosition(pos: LongArray?) { - pos?.get(0)?.let { setPosition(it, 0) } - pos?.get(1)?.let { setPosition(it, 1) } - pos?.get(2)?.let { setPosition(it, 2) } + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos)")) + fun setPosition(pos: IntArray?) { + return this.spatialOrNull()!!.setPosition(pos) } - /** - * Set the position of the element for one dimension. - * - * @param position - * absolute position in dimension d - * @param d - * dimension - */ - override fun setPosition(position: Int, d: Int) { - setPosition(position.toLong(), d) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(pos)")) + fun setPosition(pos: LongArray?) { + return this.spatialOrNull()!!.setPosition(pos) } - /** - * Set the position of the element for one dimension. - * - * @param position - * absolute position in dimension d - * @param d - * dimension - */ - override fun setPosition(position: Long, d: Int) { - setPosition(position.toFloat(), d) + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(position, d)")) + fun setPosition(position: Int, d: Int) { + return this.spatialOrNull()!!.setPosition(position, d) } - companion object NodeHelpers { - /** - * Depth-first search for elements in a Scene. - * - * @param[origin] The [Node] to start the search at. - * @param[func] A lambda taking a [Node] and returning a Boolean for matching. - * @return A list of [Node]s that match [func]. - */ - @Suppress("unused") - fun discover(origin: Node, func: (Node) -> Boolean): ArrayList { - val visited = HashSet() - val matched = ArrayList() + @Deprecated(message = "Moved to attribute spatial(), see AttributesExample for usage details", replaceWith = ReplaceWith("spatial().setPosition(position, d)")) + fun setPosition(position: Long, d: Int) { + return this.spatialOrNull()!!.setPosition(position, d) + } - fun discover(current: Node, f: (Node) -> Boolean) { - if (!visited.add(current)) return - for (v in current.children) { - if (f(v)) { - matched.add(v) - } - discover(v, f) - } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().vertexSize")) + var vertexSize: Int + get() = this.geometryOrNull()!!.vertexSize + set(value) { + this.ifGeometry { + vertexSize = value + } + } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().texcoordSize")) + var texcoordSize: Int + get() = this.geometryOrNull()!!.texcoordSize + set(value) { + this.ifGeometry { + texcoordSize = value + } + } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().geometryType")) + var geometryType: GeometryType + get() = this.geometryOrNull()!!.geometryType + set(value) { + this.ifGeometry { + geometryType = value } - - discover(origin, func) - - return matched + } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().vertices")) + var vertices: FloatBuffer + get() = this.geometryOrNull()!!.vertices + set(value) { + this.ifGeometry { + vertices = value + } + } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().normals")) + var normals: FloatBuffer + get() = this.geometryOrNull()!!.normals + set(value) { + this.ifGeometry { + normals = value + } + } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().texcoords")) + var texcoords: FloatBuffer + get() = this.geometryOrNull()!!.texcoords + set(value) { + this.ifGeometry { + texcoords = value + } + } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().indices")) + var indices: IntBuffer + get() = this.geometryOrNull()!!.indices + set(value) { + this.ifGeometry { + indices = value + } + } + @Deprecated(message = "Moved to attribute geometry(), see AttributesExample for usage details", replaceWith = ReplaceWith("geometry().recalculateNormals()")) + fun recalculateNormals() { + ifGeometry { + recalculateNormals() } } + } diff --git a/src/main/kotlin/graphics/scenery/OrientedBoundingBox.kt b/src/main/kotlin/graphics/scenery/OrientedBoundingBox.kt index 801fc61ca..e9f4cb49d 100644 --- a/src/main/kotlin/graphics/scenery/OrientedBoundingBox.kt +++ b/src/main/kotlin/graphics/scenery/OrientedBoundingBox.kt @@ -34,17 +34,20 @@ open class OrientedBoundingBox(val n: Node, val min: Vector3f, val max: Vector3f * Returns the maximum bounding sphere of this bounding box. */ fun getBoundingSphere(): BoundingSphere { - if(n.needsUpdate || n.needsUpdateWorld) { - n.updateWorld(true, false) + var origin = Vector3f(0f, 0f, 0f) + var radius = 0f + n.ifSpatial { + if(needsUpdate || needsUpdateWorld) { + updateWorld(true, false) + } + + val worldMin = worldPosition(min) + val worldMax = worldPosition(max) + + origin = worldMin + (worldMax - worldMin) * 0.5f + radius = (worldMax - origin).length() } - val worldMin = n.worldPosition(min) - val worldMax = n.worldPosition(max) - - val origin = worldMin + (worldMax - worldMin) * 0.5f - - val radius = (worldMax - origin).length() - return BoundingSphere(origin, radius) } @@ -95,7 +98,9 @@ open class OrientedBoundingBox(val n: Node, val min: Vector3f, val max: Vector3f * Return an [OrientedBoundingBox] in World coordinates. */ fun asWorld(): OrientedBoundingBox { - return OrientedBoundingBox(n, n.worldPosition(min), n.worldPosition(max)) + return OrientedBoundingBox(n, + n.spatialOrNull()?.worldPosition(min) ?: Vector3f(0.0f, 0.0f, 0.0f), + n.spatialOrNull()?.worldPosition(max)?: Vector3f(0.0f, 0.0f, 0.0f)) } /** diff --git a/src/main/kotlin/graphics/scenery/PointLight.kt b/src/main/kotlin/graphics/scenery/PointLight.kt index 85d06e17b..313ecf15a 100644 --- a/src/main/kotlin/graphics/scenery/PointLight.kt +++ b/src/main/kotlin/graphics/scenery/PointLight.kt @@ -1,5 +1,6 @@ package graphics.scenery +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.xyz import org.joml.Vector3f import org.joml.Vector4f @@ -34,12 +35,15 @@ class PointLight(radius: Float = 5.0f) : Light("PointLight") { logger.info("Resetting light radius") field = value proxySphere = Sphere(value * 1.1f, 10) - this.vertices = proxySphere.vertices - this.normals = proxySphere.normals - this.texcoords = proxySphere.texcoords - this.boundingBox = proxySphere.boundingBox + geometry { + val proxyGeom = proxySphere.geometry() + this.vertices = proxyGeom.vertices + this.normals = proxyGeom.normals + this.texcoords = proxyGeom.texcoords + this@PointLight.boundingBox = proxySphere.boundingBox + this.dirty = true + } - this.dirty = true } } @@ -47,34 +51,41 @@ class PointLight(radius: Float = 5.0f) : Light("PointLight") { override var name = "PointLight" @Suppress("unused") // will be serialised into ShaderProperty buffer - @ShaderProperty val worldPosition - get(): Vector3f = - if(this.parent != null && this.parent !is Scene) { - this.world.transform(Vector4f(position.x(), position.y(), position.z(), 1.0f)).xyz() + @ShaderProperty val worldPosition: Vector3f + get(): Vector3f { + val spatial = spatial() + return if(this.parent != null && this.parent !is Scene) { + spatial.world.transform(Vector4f(spatial.position.x(), spatial.position.y(), spatial.position.z(), 1.0f)).xyz() } else { - Vector3f(position.x(), position.y(), position.z()) + Vector3f(spatial.position.x(), spatial.position.y(), spatial.position.z()) } + } @Suppress("unused") // will be serialised into ShaderProperty buffer @ShaderProperty var debugMode = 0 init { - this.vertices = proxySphere.vertices - this.normals = proxySphere.normals - this.texcoords = proxySphere.texcoords - this.geometryType = proxySphere.geometryType - this.vertexSize = proxySphere.vertexSize - this.texcoordSize = proxySphere.texcoordSize + geometry { + val proxyGeom = proxySphere.geometry() + this.vertices = proxyGeom.vertices + this.normals = proxyGeom.normals + this.texcoords = proxyGeom.texcoords + this.geometryType = proxyGeom.geometryType + this.vertexSize = proxyGeom.vertexSize + this.texcoordSize = proxyGeom.texcoordSize + } this.boundingBox = proxySphere.boundingBox - material.blending.transparent = true - material.blending.colorBlending = Blending.BlendOp.add - material.blending.sourceColorBlendFactor = Blending.BlendFactor.One - material.blending.destinationColorBlendFactor = Blending.BlendFactor.One - material.blending.sourceAlphaBlendFactor = Blending.BlendFactor.One - material.blending.destinationAlphaBlendFactor = Blending.BlendFactor.One - material.blending.alphaBlending = Blending.BlendOp.add - material.cullingMode = Material.CullingMode.Front - material.depthTest = Material.DepthTest.Greater + material { + blending.transparent = true + blending.colorBlending = Blending.BlendOp.add + blending.sourceColorBlendFactor = Blending.BlendFactor.One + blending.destinationColorBlendFactor = Blending.BlendFactor.One + blending.sourceAlphaBlendFactor = Blending.BlendFactor.One + blending.destinationAlphaBlendFactor = Blending.BlendFactor.One + blending.alphaBlending = Blending.BlendOp.add + cullingMode = Material.CullingMode.Front + depthTest = Material.DepthTest.Greater + } } } diff --git a/src/main/kotlin/graphics/scenery/Renderable.kt b/src/main/kotlin/graphics/scenery/Renderable.kt deleted file mode 100644 index f5d27a0e1..000000000 --- a/src/main/kotlin/graphics/scenery/Renderable.kt +++ /dev/null @@ -1,70 +0,0 @@ -package graphics.scenery - -import org.joml.Matrix4f -import org.joml.Quaternionf -import org.joml.Vector3f -import java.util.concurrent.locks.ReentrantLock - -/** - * Generic interface for objects that can be rendered - * - * Matrices that are set to null shall be treated as identity matrix - * by the renderer. See e.g. [projection] or [view]. - * - * @author Ulrik Günther - */ -interface Renderable { - /** Model matrix **/ - var model: Matrix4f - /** Inverse [model] matrix */ - var imodel: Matrix4f - - /** World transform matrix */ - var world: Matrix4f - /** Inverse of [world] */ - var iworld: Matrix4f - - /** View matrix. May be null. */ - var view: Matrix4f - /** Inverse of [view] matrix. May be null. */ - var iview: Matrix4f - /** Projection matrix. May be null. */ - var projection: Matrix4f - /** Inverse of [projection]. May be null. */ - var iprojection: Matrix4f - /** modelView matrix. May be null. */ - var modelView: Matrix4f - /** Inverse of [modelView]. May be null. */ - var imodelView: Matrix4f - /** ModelViewProjection matrix. May be null. */ - var mvp: Matrix4f - - /** World position of the [Renderable] object. */ - var position: Vector3f - /** X/Y/Z scale of the object. */ - var scale: Vector3f - /** Quaternion defining the rotation of the object in local coordinates. */ - var rotation: Quaternionf - - /** Whether the object has been initialized. Used by renderers. */ - var initialized: Boolean - - /** State of the Node **/ - var state: State - /** Whether the object is dirty and somehow needs to be updated. Used by renderers. */ - var dirty: Boolean - /** Flag to set whether the object is visible or not. */ - var visible: Boolean - /** Flag to set whether the object is a billboard and will always face the camera. */ - var isBillboard: Boolean - - /** The [Material] of the object. */ - var material: Material - - /** [ReentrantLock] to be used if the object is being updated and should not be - * touched in the meantime. */ - var lock: ReentrantLock - - /** Initialisation function for the object */ - fun init(): Boolean -} diff --git a/src/main/kotlin/graphics/scenery/RichNode.kt b/src/main/kotlin/graphics/scenery/RichNode.kt new file mode 100644 index 000000000..cf9f936ec --- /dev/null +++ b/src/main/kotlin/graphics/scenery/RichNode.kt @@ -0,0 +1,13 @@ +package graphics.scenery + +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.spatial.HasSpatial + +open class RichNode(override var name: String = "Node") : DefaultNode (name), HasRenderable, HasMaterial, HasSpatial { + init { + addRenderable() + addMaterial() + addSpatial() + } +} diff --git a/src/main/kotlin/graphics/scenery/Scene.kt b/src/main/kotlin/graphics/scenery/Scene.kt index ef31c512a..5e0d61b16 100644 --- a/src/main/kotlin/graphics/scenery/Scene.kt +++ b/src/main/kotlin/graphics/scenery/Scene.kt @@ -1,5 +1,9 @@ package graphics.scenery +import graphics.scenery.attribute.material.DefaultMaterial +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.spatial.HasSpatial import org.joml.Vector3f import graphics.scenery.utils.MaybeIntersects import graphics.scenery.utils.extensions.plus @@ -11,7 +15,9 @@ import kotlinx.coroutines.runBlocking import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.atomic.AtomicLong +import java.util.stream.Stream import kotlin.collections.ArrayList +import kotlin.streams.asSequence /** * Scene class. A Scene is a special kind of [Node] that can only exist once per graph, @@ -19,7 +25,7 @@ import kotlin.collections.ArrayList * * @author Ulrik Günther */ -open class Scene : Node("RootNode") { +open class Scene : DefaultNode("RootNode"), HasRenderable, HasMaterial, HasSpatial { /** Temporary storage of the active observer ([Camera]) of the Scene. */ var activeObserver: Camera? = null @@ -33,6 +39,12 @@ open class Scene : Node("RootNode") { /** Callbacks to be called when a child is removed from the scene */ var onNodePropertiesChanged = ConcurrentHashMap Unit>() + init { + addRenderable() + addMaterial() + addSpatial() + } + /** * Adds a [Node] to the Scene, at the position given by [parent] * @@ -195,15 +207,17 @@ open class Scene : Node("RootNode") { ignoredObjects: List>, debug: Boolean = false): RaycastResult { if (debug) { - val indicatorMaterial = Material() + val indicatorMaterial = DefaultMaterial() indicatorMaterial.diffuse = Vector3f(1.0f, 0.2f, 0.2f) indicatorMaterial.specular = Vector3f(1.0f, 0.2f, 0.2f) indicatorMaterial.ambient = Vector3f(0.0f, 0.0f, 0.0f) for(it in 5..50) { val s = Box(Vector3f(0.08f, 0.08f, 0.08f)) - s.material = indicatorMaterial - s.position = position + direction * it.toFloat() + s.setMaterial(indicatorMaterial) + s.spatial { + this.position = position + direction * it.toFloat() + } this.addChild(s) } } @@ -223,13 +237,13 @@ open class Scene : Node("RootNode") { if (debug) { logger.info(matches.joinToString(", ") { "${it.node.name} at distance ${it.distance}" }) - val m = Material() + val m = DefaultMaterial() m.diffuse = Vector3f(1.0f, 0.0f, 0.0f) m.specular = Vector3f(0.0f, 0.0f, 0.0f) m.ambient = Vector3f(0.0f, 0.0f, 0.0f) matches.firstOrNull()?.let { - it.node.material = m + it.node.setMaterial(m) } } diff --git a/src/main/kotlin/graphics/scenery/SceneryBase.kt b/src/main/kotlin/graphics/scenery/SceneryBase.kt index 279e3db3b..f486770b5 100644 --- a/src/main/kotlin/graphics/scenery/SceneryBase.kt +++ b/src/main/kotlin/graphics/scenery/SceneryBase.kt @@ -261,7 +261,7 @@ open class SceneryBase @JvmOverloads constructor(var applicationName: String, scene.discover(scene, { n -> n.visible && n.state == State.Ready }, useDiscoveryBarriers = true) - .map { it.updateWorld(recursive = true, force = false); it } + .map { it.spatialOrNull()?.updateWorld(recursive = true, force = false); it } } while (!shouldClose || gracePeriod > 0) { @@ -281,7 +281,7 @@ open class SceneryBase @JvmOverloads constructor(var applicationName: String, scene.discover(scene, { n -> n.visible && n.state == State.Ready }, useDiscoveryBarriers = true) - .map { it.updateWorld(recursive = true, force = false); it } + .map { it.spatialOrNull()?.updateWorld(recursive = true, force = false); it } } profiler?.end() diff --git a/src/main/kotlin/graphics/scenery/ShaderMaterial.kt b/src/main/kotlin/graphics/scenery/ShaderMaterial.kt index 5d9440c5b..d5a6d3781 100644 --- a/src/main/kotlin/graphics/scenery/ShaderMaterial.kt +++ b/src/main/kotlin/graphics/scenery/ShaderMaterial.kt @@ -2,6 +2,7 @@ package graphics.scenery import graphics.scenery.backends.ShaderType import graphics.scenery.backends.Shaders +import graphics.scenery.attribute.material.DefaultMaterial /** * This class stores paths to GLSL shader files to be used for rendering preferentially, @@ -10,7 +11,7 @@ import graphics.scenery.backends.Shaders * @param[shaders]: The list of custom shaders to use as material * @author Ulrik Günther */ -class ShaderMaterial(var shaders: Shaders) : Material() { +class ShaderMaterial(var shaders: Shaders) : DefaultMaterial() { /** * Returns true if the current material is only use for compute diff --git a/src/main/kotlin/graphics/scenery/Sphere.kt b/src/main/kotlin/graphics/scenery/Sphere.kt index 566687f4f..5631b5f7d 100644 --- a/src/main/kotlin/graphics/scenery/Sphere.kt +++ b/src/main/kotlin/graphics/scenery/Sphere.kt @@ -97,9 +97,11 @@ open class Sphere(val radius: Float, val segments: Int) : Mesh("sphere") { } } - vertices = BufferUtils.allocateFloatAndPut(vbuffer.toFloatArray()) - normals = BufferUtils.allocateFloatAndPut(nbuffer.toFloatArray()) - texcoords = BufferUtils.allocateFloatAndPut(tbuffer.toFloatArray()) + geometry { + vertices = BufferUtils.allocateFloatAndPut(vbuffer.toFloatArray()) + normals = BufferUtils.allocateFloatAndPut(nbuffer.toFloatArray()) + texcoords = BufferUtils.allocateFloatAndPut(tbuffer.toFloatArray()) + } boundingBox = generateBoundingBox() } diff --git a/src/main/kotlin/graphics/scenery/VolumeMeasurer.kt b/src/main/kotlin/graphics/scenery/VolumeMeasurer.kt index 175333e9c..e0ba4fbb2 100644 --- a/src/main/kotlin/graphics/scenery/VolumeMeasurer.kt +++ b/src/main/kotlin/graphics/scenery/VolumeMeasurer.kt @@ -46,10 +46,10 @@ class VolumeMeasurer { //the clockwise ordering in triangle strips makes it necessary. val volume = ((-i1 + i2 + i3 - i4 - i5 + i6)/6f).absoluteValue subVolumes.add(volume) - if(arraySize%9 == 0 && mesh.geometryType == GeometryType.TRIANGLES) { + if(arraySize%9 == 0 && mesh.geometry().geometryType == GeometryType.TRIANGLES) { i += 9 } - else if (arraySize%3 == 0 && mesh.geometryType == GeometryType.TRIANGLE_STRIP) { + else if (arraySize%3 == 0 && mesh.geometry().geometryType == GeometryType.TRIANGLE_STRIP) { if(i < arraySize-9) { i += 3 } diff --git a/src/main/kotlin/graphics/scenery/attribute/AttributesMap.kt b/src/main/kotlin/graphics/scenery/attribute/AttributesMap.kt new file mode 100644 index 000000000..650eaef2a --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/AttributesMap.kt @@ -0,0 +1,7 @@ +package graphics.scenery.attribute + +interface AttributesMap { + fun put(attributeClass: Class, attribute: U) + fun get(attributeClass: Class): U + fun remove(attributeClass: Class): U +} diff --git a/src/main/kotlin/graphics/scenery/attribute/DefaultAttributesMap.kt b/src/main/kotlin/graphics/scenery/attribute/DefaultAttributesMap.kt new file mode 100644 index 000000000..e39953634 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/DefaultAttributesMap.kt @@ -0,0 +1,16 @@ +package graphics.scenery.attribute + +class DefaultAttributesMap: AttributesMap { + private val attributes = HashMap, Any>() + override fun put(attributeClass: Class, attribute: U) { + this.attributes.put(attributeClass, attribute as Any) + } + override fun get(attributeClass: Class): U { + @Suppress("UNCHECKED_CAST") + return this.attributes.get(attributeClass) as U + } + override fun remove(attributeClass: Class): U { + @Suppress("UNCHECKED_CAST") + return this.attributes.remove(attributeClass) as U + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/DelegatesProperties.kt b/src/main/kotlin/graphics/scenery/attribute/DelegatesProperties.kt new file mode 100644 index 000000000..9c90d3b8f --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/DelegatesProperties.kt @@ -0,0 +1,5 @@ +package graphics.scenery.attribute + +interface DelegatesProperties { + fun getDelegationType(): DelegationType +} diff --git a/src/main/kotlin/graphics/scenery/DelegatesRendering.kt b/src/main/kotlin/graphics/scenery/attribute/DelegationType.kt similarity index 52% rename from src/main/kotlin/graphics/scenery/DelegatesRendering.kt rename to src/main/kotlin/graphics/scenery/attribute/DelegationType.kt index 0acbfa46c..784094070 100644 --- a/src/main/kotlin/graphics/scenery/DelegatesRendering.kt +++ b/src/main/kotlin/graphics/scenery/attribute/DelegationType.kt @@ -1,4 +1,4 @@ -package graphics.scenery +package graphics.scenery.attribute /** * Delegation type class. @@ -15,12 +15,3 @@ enum class DelegationType { /** Will render for each node independent of referring to the same delegate. */ ForEachNode } - -/** - * Node type that enables delegation of rendering. For rendering, not the node itself will be drawn, - * but the node referred as [delegate]. A [delegationType] can be selected to choose whether the delegate - * will be drawn for all nodes that refer to it, or only once. - * - * @author Ulrik Guenther - */ -open class DelegatesRendering(val delegationType: DelegationType = DelegationType.OncePerDelegate, var delegate: Node? = null): Node("DelegatesRendering") diff --git a/src/main/kotlin/graphics/scenery/attribute/geometry/DefaultGeometry.kt b/src/main/kotlin/graphics/scenery/attribute/geometry/DefaultGeometry.kt new file mode 100644 index 000000000..5ae4ab6fb --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/geometry/DefaultGeometry.kt @@ -0,0 +1,69 @@ +package graphics.scenery.attribute.geometry + +import graphics.scenery.BufferUtils +import graphics.scenery.Node +import graphics.scenery.OrientedBoundingBox +import graphics.scenery.geometry.GeometryType +import graphics.scenery.utils.LazyLogger +import org.joml.Vector3f +import java.nio.FloatBuffer +import java.nio.IntBuffer + +open class DefaultGeometry(private var node: Node): Geometry { + @Transient override var vertices: FloatBuffer = BufferUtils.allocateFloat(0) + @Transient override var normals: FloatBuffer = BufferUtils.allocateFloat(0) + @Transient override var texcoords: FloatBuffer = BufferUtils.allocateFloat(0) + @Transient override var indices: IntBuffer = BufferUtils.allocateInt(0) + override var vertexSize = 3 + override var texcoordSize = 2 + override var dirty: Boolean = true + override var geometryType = GeometryType.TRIANGLES + private val logger by LazyLogger() + override fun generateBoundingBox(children: List): OrientedBoundingBox? { + val vertexBufferView = vertices.asReadOnlyBuffer() + val boundingBoxCoords = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) + + if (vertexBufferView.capacity() == 0 || vertexBufferView.remaining() == 0) { + val boundingBox = if(!children.none()) { + node.getMaximumBoundingBox() + } else { + logger.warn("$node.name: Zero vertices currently, returning empty bounding box") + OrientedBoundingBox(node,0.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 0.0f) + } + + return boundingBox + } else { + + val vertex = floatArrayOf(0.0f, 0.0f, 0.0f) + vertexBufferView.get(vertex) + + boundingBoxCoords[0] = vertex[0] + boundingBoxCoords[1] = vertex[0] + + boundingBoxCoords[2] = vertex[1] + boundingBoxCoords[3] = vertex[1] + + boundingBoxCoords[4] = vertex[2] + boundingBoxCoords[5] = vertex[2] + + while (vertexBufferView.remaining() >= 3) { + vertexBufferView.get(vertex) + + boundingBoxCoords[0] = minOf(boundingBoxCoords[0], vertex[0]) + boundingBoxCoords[2] = minOf(boundingBoxCoords[2], vertex[1]) + boundingBoxCoords[4] = minOf(boundingBoxCoords[4], vertex[2]) + + boundingBoxCoords[1] = maxOf(boundingBoxCoords[1], vertex[0]) + boundingBoxCoords[3] = maxOf(boundingBoxCoords[3], vertex[1]) + boundingBoxCoords[5] = maxOf(boundingBoxCoords[5], vertex[2]) + } + logger.debug("$node.name: Calculated bounding box with ${boundingBoxCoords.joinToString(", ")}") + return OrientedBoundingBox( + node, Vector3f(boundingBoxCoords[0], boundingBoxCoords[2], boundingBoxCoords[4]), + Vector3f(boundingBoxCoords[1], boundingBoxCoords[3], boundingBoxCoords[5]) + ) + } + } + +} diff --git a/src/main/kotlin/graphics/scenery/attribute/geometry/DelegatesGeometry.kt b/src/main/kotlin/graphics/scenery/attribute/geometry/DelegatesGeometry.kt new file mode 100644 index 000000000..51f3d6dfd --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/geometry/DelegatesGeometry.kt @@ -0,0 +1,22 @@ +package graphics.scenery.attribute.geometry + +import graphics.scenery.Node + +interface DelegatesGeometry: Node { + + fun getDelegateGeometry(): Geometry? + + override fun ifGeometry(block: Geometry.() -> Unit): Geometry? { + val delegateGeometry = getDelegateGeometry() + delegateGeometry?.block() + return delegateGeometry + } + + @Throws(IllegalStateException::class) + fun geometry(block: Geometry.() -> Unit): Geometry { + val delegateGeometry = getDelegateGeometry() + ?: throw IllegalStateException(name + ": delegates geometry properties, but the delegate is null") + delegateGeometry.block() + return delegateGeometry + } +} diff --git a/src/main/kotlin/graphics/scenery/geometry/HasGeometry.kt b/src/main/kotlin/graphics/scenery/attribute/geometry/Geometry.kt similarity index 85% rename from src/main/kotlin/graphics/scenery/geometry/HasGeometry.kt rename to src/main/kotlin/graphics/scenery/attribute/geometry/Geometry.kt index 48ecfa27f..915e823fa 100644 --- a/src/main/kotlin/graphics/scenery/geometry/HasGeometry.kt +++ b/src/main/kotlin/graphics/scenery/attribute/geometry/Geometry.kt @@ -1,6 +1,7 @@ -package graphics.scenery.geometry +package graphics.scenery.attribute.geometry -import graphics.scenery.BufferUtils +import graphics.scenery.* +import graphics.scenery.geometry.GeometryType import graphics.scenery.utils.extensions.minus import org.joml.Vector3f import java.io.Serializable @@ -14,11 +15,11 @@ import java.util.* * * @author Ulrik Günther */ -interface HasGeometry : Serializable { +interface Geometry : Serializable { /** How many elements does a vertex store? */ - val vertexSize: Int + var vertexSize: Int /** How many elements does a texture coordinate store? */ - val texcoordSize: Int + var texcoordSize: Int /** The [GeometryType] of the [Node] */ var geometryType: GeometryType @@ -30,6 +31,10 @@ interface HasGeometry : Serializable { var texcoords: FloatBuffer /** Array of the indices to create an indexed mesh. Optional, but advisable to use to minimize the number of submitted vertices. */ var indices: IntBuffer + /** Whether the object is dirty and somehow needs to be updated. Used by renderers. */ + var dirty: Boolean + + fun generateBoundingBox(children: List): OrientedBoundingBox? /** * Recalculates normals, assuming CCW winding order and taking diff --git a/src/main/kotlin/graphics/scenery/attribute/geometry/HasCustomGeometry.kt b/src/main/kotlin/graphics/scenery/attribute/geometry/HasCustomGeometry.kt new file mode 100644 index 000000000..8be2e1062 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/geometry/HasCustomGeometry.kt @@ -0,0 +1,34 @@ +package graphics.scenery.attribute.geometry + +import graphics.scenery.Node + +interface HasCustomGeometry: Node { + + fun createGeometry(): T + + fun addGeometry() { + addAttribute(Geometry::class.java, createGeometry()) + } + + fun addGeometry(block: T.() -> Unit) { + addAttribute(Geometry::class.java, createGeometry(), block) + } + + fun geometry(block: T.() -> Unit): T { + val props = geometry() + props.block() + return props + } + + fun geometry(): T { + return getAttribute(Geometry::class.java) + } + + override fun ifGeometry(block: Geometry.() -> Unit): T { + return this.geometry(block) + } + + override fun geometryOrNull(): T { + return this.geometry() + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/geometry/HasGeometry.kt b/src/main/kotlin/graphics/scenery/attribute/geometry/HasGeometry.kt new file mode 100644 index 000000000..2eb77a1d0 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/geometry/HasGeometry.kt @@ -0,0 +1,7 @@ +package graphics.scenery.attribute.geometry + +interface HasGeometry: HasCustomGeometry { + override fun createGeometry(): Geometry { + return DefaultGeometry(this) + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/material/DefaultMaterial.kt b/src/main/kotlin/graphics/scenery/attribute/material/DefaultMaterial.kt new file mode 100644 index 000000000..49833b66e --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/material/DefaultMaterial.kt @@ -0,0 +1,32 @@ +package graphics.scenery.attribute.material + +import graphics.scenery.Blending +import graphics.scenery.textures.Texture +import graphics.scenery.utils.TimestampedConcurrentHashMap +import org.joml.Vector3f +import java.io.Serializable + +open class DefaultMaterial : Material, Serializable { + + override var name: String = "Material" + override var diffuse: Vector3f = Vector3f(0.9f, 0.5f, 0.5f) + override var specular: Vector3f = Vector3f(0.5f, 0.5f, 0.5f) + override var ambient: Vector3f = Vector3f(0.5f, 0.5f, 0.5f) + override var roughness: Float = 1.0f + override var metallic: Float = 0.0f + override var blending: Blending = Blending() + @Volatile override var textures: TimestampedConcurrentHashMap = TimestampedConcurrentHashMap() + override var cullingMode: Material.CullingMode = Material.CullingMode.Back + override var depthTest: Material.DepthTest = Material.DepthTest.LessEqual + override var wireframe: Boolean = false + + /** Companion object for Material, emulating static methods */ + companion object Factory { + /** + * Factory method returning the default material + * + * @return Material with default properties + */ + @JvmStatic fun Material(): DefaultMaterial = DefaultMaterial() + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/material/DelegatesMaterial.kt b/src/main/kotlin/graphics/scenery/attribute/material/DelegatesMaterial.kt new file mode 100644 index 000000000..66c7aec2a --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/material/DelegatesMaterial.kt @@ -0,0 +1,29 @@ +package graphics.scenery.attribute.material + +import graphics.scenery.Node + +/** + * Node type that enables delegation of rendering. For rendering, not the node itself will be drawn, + * but the node referred as [delegateRendering]. A [delegationType] can be selected to choose whether the delegate + * will be drawn for all nodes that refer to it, or only once. + * + * @author Ulrik Guenther + */ +interface DelegatesMaterial: Node { + + fun getDelegateMaterial(): Material? + + override fun ifMaterial(block: Material.() -> Unit): Material? { + val delegateMaterial = getDelegateMaterial() + delegateMaterial?.block() + return delegateMaterial + } + + @Throws(IllegalStateException::class) + fun material(block: Material.() -> Unit): Material { + val delegateMaterial = getDelegateMaterial() + ?: throw IllegalStateException(name + ": delegates material properties, but the delegate is null") + delegateMaterial.block() + return delegateMaterial + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/material/HasCustomMaterial.kt b/src/main/kotlin/graphics/scenery/attribute/material/HasCustomMaterial.kt new file mode 100644 index 000000000..bad1a8631 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/material/HasCustomMaterial.kt @@ -0,0 +1,34 @@ +package graphics.scenery.attribute.material + +import graphics.scenery.Node + +interface HasCustomMaterial: Node { + + fun createMaterial(): T + + fun addMaterial() { + addAttribute(Material::class.java, createMaterial()) + } + + fun addMaterial(block: T.() -> Unit) { + addAttribute(Material::class.java, createMaterial(), block) + } + + fun material(block: T.() -> Unit): T { + val prop = material() + prop.block() + return prop + } + + fun material(): T { + return getAttribute(Material::class.java) + } + + override fun ifMaterial(block: Material.() -> Unit): T { + return this.material(block) + } + + override fun materialOrNull(): T { + return this.material() + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/material/HasMaterial.kt b/src/main/kotlin/graphics/scenery/attribute/material/HasMaterial.kt new file mode 100644 index 000000000..77da82c08 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/material/HasMaterial.kt @@ -0,0 +1,8 @@ +package graphics.scenery.attribute.material + +interface HasMaterial: HasCustomMaterial { + override fun createMaterial(): Material { + return DefaultMaterial() + } +} + diff --git a/src/main/kotlin/graphics/scenery/Material.kt b/src/main/kotlin/graphics/scenery/attribute/material/Material.kt similarity index 62% rename from src/main/kotlin/graphics/scenery/Material.kt rename to src/main/kotlin/graphics/scenery/attribute/material/Material.kt index 2e7824f11..9972eaad2 100644 --- a/src/main/kotlin/graphics/scenery/Material.kt +++ b/src/main/kotlin/graphics/scenery/attribute/material/Material.kt @@ -1,18 +1,17 @@ -package graphics.scenery +package graphics.scenery.attribute.material -import org.joml.Vector3f -import graphics.scenery.Material.CullingMode.* +import graphics.scenery.Blending +import graphics.scenery.attribute.material.Material.CullingMode.* import graphics.scenery.textures.Texture import graphics.scenery.utils.TimestampedConcurrentHashMap -import java.io.Serializable -import java.util.concurrent.ConcurrentHashMap +import org.joml.Vector3f /** - * Material class, storing material colors, textures, opacity properties, etc. + * Material interface, storing material colors, textures, opacity properties, etc. * * @author Ulrik Günther */ -open class Material : Serializable { +interface Material { /** * Culling Mode enum, to determine which faces are culling when assuming CCW order @@ -27,44 +26,34 @@ open class Material : Serializable { enum class DepthTest { Less, Greater, LessEqual, GreaterEqual, Always, Never, Equal } /** Name of the material. */ - var name: String = "Material" + var name: String /** Diffuse color of the material. */ - var diffuse: Vector3f = Vector3f(0.9f, 0.5f, 0.5f) + var diffuse: Vector3f /** Specular color of the material. */ - var specular: Vector3f = Vector3f(0.5f, 0.5f, 0.5f) + var specular: Vector3f /** Ambient color of the material. */ - var ambient: Vector3f = Vector3f(0.5f, 0.5f, 0.5f) + var ambient: Vector3f /** Specular exponent */ - var roughness: Float = 1.0f + var roughness: Float /** Metallicity, 0.0 is non-metal, 1.0 is full metal */ - var metallic: Float = 0.0f + var metallic: Float /** Blending settings for this material. See [Blending]. */ - var blending: Blending = Blending() + var blending: Blending /** Hash map storing the type and origin of the material's textures. Key is the * type, e.g. ("diffuse", "normal", "displacement"...), value can be a file path or * via "fromBuffer:[transferTextureName], a named [Texture] in [transferTextures]. */ - @Volatile var textures: TimestampedConcurrentHashMap = TimestampedConcurrentHashMap() + var textures: TimestampedConcurrentHashMap /** Culling mode of the material. @see[CullingMode] */ - var cullingMode: CullingMode = CullingMode.Back + var cullingMode: CullingMode /** depth testing mode for this material */ - var depthTest: DepthTest = DepthTest.LessEqual + var depthTest: DepthTest /** Flag to make the object wireframe */ - var wireframe: Boolean = false - - /** Companion object for Material, emulating static methods */ - companion object Factory { - /** - * Factory method returning the default material - * - * @return Material with default properties - */ - @JvmStatic fun DefaultMaterial(): Material = Material() - } + var wireframe: Boolean /** * Returns a hash of the material, with properties relevant for diff --git a/src/main/kotlin/graphics/scenery/attribute/renderable/DefaultRenderable.kt b/src/main/kotlin/graphics/scenery/attribute/renderable/DefaultRenderable.kt new file mode 100644 index 000000000..fb15c9237 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/renderable/DefaultRenderable.kt @@ -0,0 +1,16 @@ +package graphics.scenery.attribute.renderable + +import graphics.scenery.Node +import java.util.* +import kotlin.collections.HashMap + +open class DefaultRenderable(override var parent: Node): Renderable { + @Transient override var metadata: HashMap = HashMap() + + private var uuid: UUID = UUID.randomUUID() + override fun getUuid(): UUID { + return uuid + } + override var isBillboard: Boolean = false + +} diff --git a/src/main/kotlin/graphics/scenery/attribute/renderable/DelegatesRenderable.kt b/src/main/kotlin/graphics/scenery/attribute/renderable/DelegatesRenderable.kt new file mode 100644 index 000000000..db576a357 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/renderable/DelegatesRenderable.kt @@ -0,0 +1,29 @@ +package graphics.scenery.attribute.renderable + +import graphics.scenery.Node + +/** + * Node type that enables delegation of rendering. For rendering, not the node itself will be drawn, + * but the node referred as [delegateRenderable]. A [delegationType] can be selected to choose whether the delegate + * will be drawn for all nodes that refer to it, or only once. + * + * @author Ulrik Guenther + */ +interface DelegatesRenderable: Node { + + fun getDelegateRenderable(): Renderable? + + override fun ifRenderable(block: Renderable.() -> Unit): Renderable? { + val delegateRenderable = getDelegateRenderable() + delegateRenderable?.block() + return delegateRenderable + } + + @Throws(IllegalStateException::class) + fun renderable(block: Renderable.() -> Unit): Renderable { + val delegateRenderable = getDelegateRenderable() + ?: throw IllegalStateException(name + ": delegates renderable properties, but the delegate is null") + delegateRenderable.block() + return delegateRenderable + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/renderable/HasCustomRenderable.kt b/src/main/kotlin/graphics/scenery/attribute/renderable/HasCustomRenderable.kt new file mode 100644 index 000000000..7efe9efa1 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/renderable/HasCustomRenderable.kt @@ -0,0 +1,34 @@ +package graphics.scenery.attribute.renderable + +import graphics.scenery.Node + +interface HasCustomRenderable: Node { + + fun createRenderable(): T + + fun addRenderable() { + addAttribute(Renderable::class.java, createRenderable()) + } + + fun addRenderable(block: T.() -> Unit) { + addAttribute(Renderable::class.java, createRenderable(), block) + } + + fun renderable(block: T.() -> Unit): T { + val prop = this.renderable() + prop.block() + return prop + } + + fun renderable(): T { + return getAttribute(Renderable::class.java) + } + + override fun ifRenderable(block: Renderable.() -> Unit): T { + return this.renderable(block) + } + + override fun renderableOrNull(): T { + return this.renderable() + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/renderable/HasRenderable.kt b/src/main/kotlin/graphics/scenery/attribute/renderable/HasRenderable.kt new file mode 100644 index 000000000..18370c0c0 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/renderable/HasRenderable.kt @@ -0,0 +1,8 @@ +package graphics.scenery.attribute.renderable + +interface HasRenderable: HasCustomRenderable { + override fun createRenderable(): Renderable { + return DefaultRenderable(this) + } +} + diff --git a/src/main/kotlin/graphics/scenery/attribute/renderable/Renderable.kt b/src/main/kotlin/graphics/scenery/attribute/renderable/Renderable.kt new file mode 100644 index 000000000..67c326bfb --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/renderable/Renderable.kt @@ -0,0 +1,41 @@ +package graphics.scenery.attribute.renderable + +import graphics.scenery.Hub +import graphics.scenery.Node +import graphics.scenery.backends.Renderer +import java.util.* + +/** + * Generic interface for objects that can be rendered + * + * Matrices that are set to null shall be treated as identity matrix + * by the renderer. See e.g. [projection] or [view]. + * + * @author Ulrik Günther + */ +interface Renderable { + + // FIXME move to material + /** Flag to set whether the object is a billboard and will always face the camera. */ + var isBillboard: Boolean + + + var parent: Node + + /** Hash map used for storing metadata for the [Renderable]. [Renderer] implementations use + * it to e.g. store renderer-specific state. */ + var metadata: HashMap + + /** Unique ID of the Renderable */ + fun getUuid(): UUID + + fun preUpdate(renderer: Renderer, hub: Hub?) {} + + /** + * PreDraw function, to be called before the actual rendering, useful for + * per-timestep preparation. + */ + fun preDraw(): Boolean { + return true + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/spatial/DefaultSpatial.kt b/src/main/kotlin/graphics/scenery/attribute/spatial/DefaultSpatial.kt new file mode 100644 index 000000000..7b6636408 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/spatial/DefaultSpatial.kt @@ -0,0 +1,370 @@ +package graphics.scenery.attribute.spatial + +import graphics.scenery.Node +import graphics.scenery.Scene +import graphics.scenery.utils.LazyLogger +import graphics.scenery.utils.MaybeIntersects +import graphics.scenery.utils.extensions.* +import net.imglib2.Localizable +import net.imglib2.RealLocalizable +import org.joml.Matrix4f +import org.joml.Quaternionf +import org.joml.Vector3f +import org.joml.Vector4f +import java.lang.Float.max +import java.lang.Float.min +import kotlin.properties.Delegates +import kotlin.reflect.KProperty + +open class DefaultSpatial(private var node: Node): Spatial { + override var world: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + override var model: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + override var view: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + override var projection: Matrix4f by Delegates.observable(Matrix4f().identity()) { property, old, new -> propertyChanged(property, old, new) } + override var scale: Vector3f by Delegates.observable(Vector3f(1.0f, 1.0f, 1.0f)) { property, old, new -> propertyChanged(property, old, new) } + override var rotation: Quaternionf by Delegates.observable(Quaternionf(0.0f, 0.0f, 0.0f, 1.0f)) { property, old, new -> propertyChanged(property, old, new) } + override var position: Vector3f by Delegates.observable(Vector3f(0.0f, 0.0f, 0.0f)) { property, old, new -> propertyChanged(property, old, new) } + override var wantsComposeModel = true + override var needsUpdate = true + override var needsUpdateWorld = true + + val logger by LazyLogger() + + @Suppress("UNUSED_PARAMETER") + override fun propertyChanged(property: KProperty<*>, old: R, new: R, custom: String) { + if(property.name == "rotation" + || property.name == "position" + || property.name == "scale" + || property.name == custom) { + needsUpdate = true + needsUpdateWorld = true + } + } + + @Synchronized override fun updateWorld(recursive: Boolean, force: Boolean) { + node.update.forEach { it.invoke() } + + if ((needsUpdate or force)) { + if(wantsComposeModel) { + this.composeModel() + } + + needsUpdate = false + needsUpdateWorld = true + } + + if (needsUpdateWorld or force) { + val p = node.parent + if (p == null || p is Scene) { + world.set(model) + } else { + world.set(p.spatialOrNull()?.world) + world.mul(this.model) + } + } + + if (recursive) { + node.children.forEach { it.spatialOrNull()?.updateWorld(true, needsUpdateWorld) } + // also update linked nodes -- they might need updated + // model/view/proj matrices as well + node.linkedNodes.forEach { it.spatialOrNull()?.updateWorld(true, needsUpdateWorld) } + } + + if(needsUpdateWorld) { + needsUpdateWorld = false + } + + node.postUpdate.forEach { it.invoke() } + } + + override fun worldScale(): Vector3f { + val wm = world + val sx = Vector3f(wm[0,0],wm[0,1],wm[0,2]).length() + val sy = Vector3f(wm[1,0],wm[1,1],wm[1,2]).length() + val sz = Vector3f(wm[2,0],wm[2,1],wm[2,2]).length() + + return Vector3f(sx,sy,sz) + } + + /** + * This method composes the [model] matrices of the node from its + * [position], [scale] and [rotation]. + */ + override fun composeModel() { + + @Suppress("SENSELESS_COMPARISON") + if(position != null && rotation != null && scale != null) { + model.translationRotateScale( + Vector3f(position.x(), position.y(), position.z()), + this.rotation, + Vector3f(this.scale.x(), this.scale.y(), this.scale.z())) + } + } + + override fun centerOn(position: Vector3f): Vector3f { + val min = node.getMaximumBoundingBox().min + val max = node.getMaximumBoundingBox().max + + val center = (max - min) * 0.5f + this.position = position - (node.getMaximumBoundingBox().min + center) + + return center + } + + override fun putAbove(position: Vector3f): Vector3f { + val center = centerOn(position) + + val diffY = center.y() + position.y() + val diff = Vector3f(0.0f, diffY, 0.0f) + this.position = this.position + diff + + return diff + } + + override fun fitInto(sideLength: Float, scaleUp: Boolean): Vector3f { + val min = node.getMaximumBoundingBox().min.xyzw() + val max = node.getMaximumBoundingBox().max.xyzw() + + val maxDimension = (max - min).max() + val scaling = sideLength/maxDimension + + if((scaleUp && scaling > 1.0f) || scaling <= 1.0f) { + this.scale = Vector3f(scaling, scaling, scaling) + } else { + this.scale = Vector3f(1.0f, 1.0f, 1.0f) + } + + return this.scale + } + + override fun orientBetweenPoints(p1: Vector3f, p2: Vector3f, rescale: Boolean, reposition: Boolean): Quaternionf { + val direction = p2 - p1 + val length = direction.length() + + this.rotation = Quaternionf().rotationTo(Vector3f(0.0f, 1.0f, 0.0f), direction.normalize()) + if(rescale) { + this.scale = Vector3f(1.0f, length, 1.0f) + } + + if(reposition) { + this.position = Vector3f(p1) + } + + return this.rotation + } + + + override fun intersects(other: Node): Boolean { + node.boundingBox?.let { ownOBB -> + other.boundingBox?.let { otherOBB -> + return ownOBB.intersects(otherOBB) + } + } + + return false + } + + override fun worldPosition(v: Vector3f?): Vector3f { + val target = v ?: position + return if(node.parent is Scene && v == null) { + Vector3f(target) + } else { + world.transform(Vector4f().set(target, 1.0f)).xyz() + } + } + + /** + * Performs a intersection test with an axis-aligned bounding box of this [Node], where + * the test ray originates at [origin] and points into [dir]. + * + * Returns a Pair of Boolean and Float, indicating whether an intersection is possible, + * and at which distance. + * + * Code adapted from [zachamarz](http://gamedev.stackexchange.com/a/18459). + */ + override fun intersectAABB(origin: Vector3f, dir: Vector3f): MaybeIntersects { + val bbmin = node.getMaximumBoundingBox().min.xyzw() + val bbmax = node.getMaximumBoundingBox().max.xyzw() + + val min = world.transform(bbmin) + val max = world.transform(bbmax) + + // skip if inside the bounding box + if(origin.isInside(min.xyz(), max.xyz())) { + return MaybeIntersects.NoIntersection() + } + + val invDir = Vector3f(1 / (dir.x() + Float.MIN_VALUE), 1 / (dir.y() + Float.MIN_VALUE), 1 / (dir.z() + Float.MIN_VALUE)) + + val t1 = (min.x() - origin.x()) * invDir.x() + val t2 = (max.x() - origin.x()) * invDir.x() + val t3 = (min.y() - origin.y()) * invDir.y() + val t4 = (max.y() - origin.y()) * invDir.y() + val t5 = (min.z() - origin.z()) * invDir.z() + val t6 = (max.z() - origin.z()) * invDir.z() + + val tmin = max(max(min(t1, t2), min(t3, t4)), min(t5, t6)) + val tmax = min(min(max(t1, t2), max(t3, t4)), max(t5, t6)) + + // we are in front of the AABB + if (tmax < 0) { + return MaybeIntersects.NoIntersection() + } + + // we have missed the AABB + if (tmin > tmax) { + return MaybeIntersects.NoIntersection() + } + + // we have a match! calculate entry and exit points + val entry = origin + dir * tmin + val exit = origin + dir * tmax + val localEntry = Matrix4f(world).invert().transform(Vector4f().set(entry, 1.0f)) + val localExit = Matrix4f(world).invert().transform(Vector4f().set(exit, 1.0f)) + + return MaybeIntersects.Intersection(tmin, entry, exit, localEntry.xyz(), localExit.xyz()) + } + + private fun Vector3f.isInside(min: Vector3f, max: Vector3f): Boolean { + return this.x() > min.x() && this.x() < max.x() + && this.y() > min.y() && this.y() < max.y() + && this.z() > min.z() && this.z() < max.z() + } + + override fun localize(position: FloatArray?) { + position?.set(0, this.position.x()) + position?.set(1, this.position.y()) + position?.set(2, this.position.z()) + } + + override fun localize(position: DoubleArray?) { + position?.set(0, this.position.x().toDouble()) + position?.set(1, this.position.y().toDouble()) + position?.set(2, this.position.z().toDouble()) + } + + override fun getFloatPosition(d: Int): Float { + return this.position[d] + } + + override fun bck(d: Int) { + move(-1, d) + } + + override fun move(distance: Float, d: Int) { + setPosition( getFloatPosition(d) + distance, d ) + } + + override fun move(distance: Double, d: Int) { + setPosition( getDoublePosition(d) + distance, d ) + } + + override fun move(distance: RealLocalizable?) { + distance?.getDoublePosition(0)?.let { move(it, 0) } + distance?.getDoublePosition(1)?.let { move(it, 1) } + distance?.getDoublePosition(2)?.let { move(it, 2) } + } + + override fun move(distance: FloatArray?) { + distance?.get(0)?.let { move(it, 0 ) } + distance?.get(1)?.let { move(it, 1 ) } + distance?.get(2)?.let { move(it, 2 ) } + } + + override fun move(distance: DoubleArray?) { + distance?.get(0)?.let { move(it, 0 ) } + distance?.get(1)?.let { move(it, 1 ) } + distance?.get(2)?.let { move(it, 2 ) } + } + + override fun move(distance: Int, d: Int) { + move( distance.toLong(), d ) + } + + override fun move(distance: Long, d: Int) { + this.position = this.position + Vector3f().setComponent(d, distance.toFloat()) + } + + override fun move(distance: Localizable?) { + distance?.getDoublePosition(0)?.let { move(it, 0) } + distance?.getDoublePosition(1)?.let { move(it, 1) } + distance?.getDoublePosition(2)?.let { move(it, 2) } + } + + override fun move(distance: IntArray?) { + distance?.get(0)?.let { move(it, 0 ) } + distance?.get(1)?.let { move(it, 1 ) } + distance?.get(2)?.let { move(it, 2 ) } + } + + override fun move(distance: LongArray?) { + distance?.get(0)?.let { move(it, 0 ) } + distance?.get(1)?.let { move(it, 1 ) } + distance?.get(2)?.let { move(it, 2 ) } + } + + override fun numDimensions(): Int { + return 3 + } + + override fun fwd(d: Int) { + move( 1, d) + } + + override fun getDoublePosition(d: Int): Double { + return this.position[d].toDouble() + } + + override fun setPosition(pos: RealLocalizable) { + position.setComponent( 0, pos.getFloatPosition(0) ) + position.setComponent( 1, pos.getFloatPosition(1) ) + position.setComponent( 2, pos.getFloatPosition(2) ) + } + + override fun setPosition(pos: FloatArray?) { + pos?.get(0)?.let { setPosition(it, 0 ) } + pos?.get(1)?.let { setPosition(it, 1 ) } + pos?.get(2)?.let { setPosition(it, 2 ) } + } + + override fun setPosition(pos: DoubleArray?) { + pos?.get(0)?.let { setPosition(it, 0 ) } + pos?.get(1)?.let { setPosition(it, 1 ) } + pos?.get(2)?.let { setPosition(it, 2 ) } + } + + override fun setPosition(pos: Float, d: Int) { + position.setComponent( d, pos ) + } + + override fun setPosition(pos: Double, d: Int) { + setPosition( pos.toFloat(), d ) + } + + override fun setPosition(pos: Localizable?) { + pos?.getIntPosition(0)?.let { setPosition(it, 0 ) } + pos?.getIntPosition(1)?.let { setPosition(it, 1 ) } + pos?.getIntPosition(2)?.let { setPosition(it, 2 ) } + } + + override fun setPosition(pos: IntArray?) { + pos?.get(0)?.let { setPosition(it, 0) } + pos?.get(1)?.let { setPosition(it, 1) } + pos?.get(2)?.let { setPosition(it, 2) } + } + + override fun setPosition(pos: LongArray?) { + pos?.get(0)?.let { setPosition(it, 0) } + pos?.get(1)?.let { setPosition(it, 1) } + pos?.get(2)?.let { setPosition(it, 2) } + } + + override fun setPosition(position: Int, d: Int) { + setPosition(position.toLong(), d) + } + + override fun setPosition(position: Long, d: Int) { + setPosition(position.toFloat(), d) + } + +} diff --git a/src/main/kotlin/graphics/scenery/attribute/spatial/DelegatesSpatial.kt b/src/main/kotlin/graphics/scenery/attribute/spatial/DelegatesSpatial.kt new file mode 100644 index 000000000..344af5bdd --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/spatial/DelegatesSpatial.kt @@ -0,0 +1,22 @@ +package graphics.scenery.attribute.spatial + +import graphics.scenery.Node + +interface DelegatesSpatial: Node { + + fun getDelegateSpatial(): Spatial? + + override fun ifSpatial(block: Spatial.() -> Unit): Spatial? { + val delegateSpatial = getDelegateSpatial() + delegateSpatial?.block() + return delegateSpatial + } + + @Throws(IllegalStateException::class) + fun spatial(block: Spatial.() -> Unit): Spatial { + val delegateSpatial = getDelegateSpatial() + ?: throw IllegalStateException(name + ": delegates spatial properties, but the delegate is null") + delegateSpatial.block() + return delegateSpatial + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/spatial/HasCustomSpatial.kt b/src/main/kotlin/graphics/scenery/attribute/spatial/HasCustomSpatial.kt new file mode 100644 index 000000000..79dfb2330 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/spatial/HasCustomSpatial.kt @@ -0,0 +1,34 @@ +package graphics.scenery.attribute.spatial + +import graphics.scenery.Node + +interface HasCustomSpatial: Node { + + fun createSpatial(): T + + fun addSpatial() { + addAttribute(Spatial::class.java, createSpatial()) + } + + fun addSpatial(block: T.() -> Unit) { + addAttribute(Spatial::class.java, createSpatial(), block) + } + + fun spatial(block: T.() -> Unit): T { + val prop = this.spatial() + prop.block() + return prop + } + + fun spatial(): T { + return getAttribute(Spatial::class.java) + } + + override fun ifSpatial(block: Spatial.() -> Unit): T? { + return this.spatial(block) + } + + override fun spatialOrNull(): T? { + return this.spatial() + } +} diff --git a/src/main/kotlin/graphics/scenery/attribute/spatial/HasSpatial.kt b/src/main/kotlin/graphics/scenery/attribute/spatial/HasSpatial.kt new file mode 100644 index 000000000..32ffefa74 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/spatial/HasSpatial.kt @@ -0,0 +1,8 @@ +package graphics.scenery.attribute.spatial + +interface HasSpatial: HasCustomSpatial { + override fun createSpatial(): Spatial { + return DefaultSpatial(this) + } +} + diff --git a/src/main/kotlin/graphics/scenery/attribute/spatial/Spatial.kt b/src/main/kotlin/graphics/scenery/attribute/spatial/Spatial.kt new file mode 100644 index 000000000..fc45f7b78 --- /dev/null +++ b/src/main/kotlin/graphics/scenery/attribute/spatial/Spatial.kt @@ -0,0 +1,109 @@ +package graphics.scenery.attribute.spatial + +import graphics.scenery.Node +import graphics.scenery.utils.MaybeIntersects +import net.imglib2.RealLocalizable +import net.imglib2.RealPositionable +import org.joml.Matrix4f +import org.joml.Quaternionf +import org.joml.Vector3f +import kotlin.reflect.KProperty + +interface Spatial: RealLocalizable, RealPositionable { + /** Model matrix **/ + var model: Matrix4f + /** World transform matrix */ + var world: Matrix4f + /** View matrix. May be null. */ + var view: Matrix4f + /** Projection matrix. May be null. */ + var projection: Matrix4f + /** World position of the [Renderable] object. */ + var position: Vector3f + /** X/Y/Z scale of the object. */ + var scale: Vector3f + /** Quaternion defining the rotation of the object in local coordinates. */ + var rotation: Quaternionf + /** Stores whether the [model] matrix needs an update. */ + var wantsComposeModel: Boolean + /** Stores whether the [model] matrix needs an update. */ + var needsUpdate: Boolean + /** Stores whether the [world] matrix needs an update. */ + var needsUpdateWorld: Boolean + + /** + * Update the the [world] matrix of the [Spatial] node. + * + * This method will update the [model] and [world] matrices of the node, + * if [needsUpdate] is true, or [force] is true. If [recursive] is true, + * this method will also recurse into the [children] and [linkedNodes] of + * the node and update these as well. + * + * @param[recursive] Whether the [children] should be recursed into. + * @param[force] Force update irrespective of [needsUpdate] state. + */ + fun updateWorld(recursive: Boolean, force: Boolean = false) + + /** + * Extracts the scaling component from the world matrix. + * + * Is not correct for world matrices with shear! + * + * @return world scale + */ + fun worldScale(): Vector3f + + fun intersectAABB(origin: Vector3f, dir: Vector3f): MaybeIntersects + + /** + * Returns the [Node]'s world position + * + * @returns The position in world space + */ + fun worldPosition(v: Vector3f? = null): Vector3f + + /** + * Checks whether two node's bounding boxes do intersect using a simple bounding sphere test. + */ + fun intersects(other: Node): Boolean + + /** + * Fits the [Node] within a box of the given dimension. + * + * @param[sideLength] - The size of the box to fit the [Node] uniformly into. + * @param[scaleUp] - Whether the model should only be scaled down, or also up. + * @return Vector3f - containing the applied scaling + */ + fun fitInto(sideLength: Float, scaleUp: Boolean = false): Vector3f + + /** + * Taking this [Node]'s [boundingBox] into consideration, puts it above + * the [position] entirely. + */ + fun putAbove(position: Vector3f): Vector3f + + /** + * Centers the [Node] on a given position. + * + * @param[position] - the position to center the [Node] on. + * @return Vector3f - the center offset calculcated for the [Node]. + */ + fun centerOn(position: Vector3f): Vector3f + + /** + * Orients the Node between points [p1] and [p2], and optionally + * [rescale]s and [reposition]s it. + */ + fun orientBetweenPoints(p1: Vector3f, p2: Vector3f, rescale: Boolean = false, reposition: Boolean = false): Quaternionf + + fun orientBetweenPoints(p1: Vector3f, p2: Vector3f, rescale: Boolean): Quaternionf { + return orientBetweenPoints(p1, p2, rescale, false) + } + fun orientBetweenPoints(p1: Vector3f, p2: Vector3f): Quaternionf { + return orientBetweenPoints(p1, p2, false, false) + } + + fun propertyChanged(property: KProperty<*>, old: R, new: R, custom: String = "") + fun composeModel() +} + diff --git a/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLRenderer.kt b/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLRenderer.kt index 7eaf006a5..4c29f31c2 100644 --- a/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLRenderer.kt +++ b/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLRenderer.kt @@ -10,8 +10,12 @@ import com.jogamp.opengl.util.awt.AWTGLReadBufferUtil import graphics.scenery.* import graphics.scenery.backends.* import graphics.scenery.geometry.GeometryType -import graphics.scenery.geometry.HasGeometry import graphics.scenery.primitives.Plane +import graphics.scenery.attribute.DelegatesProperties +import graphics.scenery.attribute.DelegationType +import graphics.scenery.attribute.geometry.Geometry +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.Renderable import graphics.scenery.spirvcrossj.Loader import graphics.scenery.spirvcrossj.libspirvcrossj import graphics.scenery.textures.Texture @@ -587,7 +591,7 @@ open class OpenGLRenderer(hub: Hub, initialized = true } - private fun Node.rendererMetadata(): OpenGLObjectState? { + private fun Renderable.rendererMetadata(): OpenGLObjectState? { return this.metadata["OpenGLRenderer"] as? OpenGLObjectState } @@ -1053,11 +1057,11 @@ open class OpenGLRenderer(hub: Hub, * Convenience function that extracts the [OpenGLObjectState] from a [Node]'s * metadata. * - * @param[node] The node of interest - * @return The [OpenGLObjectState] of the [Node] + * @param[renderable] The node of interest + * @return The [OpenGLObjectState] of the [Renderable] */ - fun getOpenGLObjectStateFromNode(node: Node): OpenGLObjectState { - return node.metadata["OpenGLRenderer"] as OpenGLObjectState + fun getOpenGLObjectStateFromNode(renderable: Renderable): OpenGLObjectState { + return renderable.metadata["OpenGLRenderer"] as OpenGLObjectState } /** @@ -1065,10 +1069,11 @@ open class OpenGLRenderer(hub: Hub, * before [render]. */ @Synchronized override fun initializeScene() { - scene.discover(scene, { it is HasGeometry }) - .forEach { it -> - it.metadata["OpenGLRenderer"] = OpenGLObjectState() - initializeNode(it) + scene.discover(scene, { it.geometryOrNull() != null }) + .forEach { node -> + val renderable = node.renderableOrNull() + if(renderable != null) renderable.metadata["OpenGLRenderer"] = OpenGLObjectState() + initializeNode(node) } scene.initialized = true @@ -1082,7 +1087,9 @@ open class OpenGLRenderer(hub: Hub, val hmd = hub?.getWorkingHMDDisplay()?.wantsVR(settings) - cam.view = cam.getTransformation() + val camSpatial = cam.spatial() + + camSpatial.view = camSpatial.getTransformation() buffers.VRParameters.reset() val vrUbo = uboCache.computeIfAbsent("VRParameters") { @@ -1091,19 +1098,19 @@ open class OpenGLRenderer(hub: Hub, vrUbo.add("projection0", { (hmd?.getEyeProjection(0, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection) + ?: camSpatial.projection) }) vrUbo.add("projection1", { (hmd?.getEyeProjection(1, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection) + ?: camSpatial.projection) }) vrUbo.add("inverseProjection0", { Matrix4f(hmd?.getEyeProjection(0, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection).invert() + ?: camSpatial.projection).invert() }) vrUbo.add("inverseProjection1", { Matrix4f(hmd?.getEyeProjection(1, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection).invert() + ?: camSpatial.projection).invert() }) vrUbo.add("headShift", { hmd?.getHeadToEyeTransform(0) ?: Matrix4f().identity() }) vrUbo.add("IPD", { hmd?.getIPD() ?: 0.05f }) @@ -1117,11 +1124,14 @@ open class OpenGLRenderer(hub: Hub, sceneUBOs.forEach { node -> var nodeUpdated: Boolean by StickyBoolean(initial = false) - if (!node.metadata.containsKey(className)) { + val renderable = node.renderableOrNull() ?: return@forEach + val material = node.materialOrNull() ?: return@forEach + val spatial = node.spatialOrNull() + if (!renderable.metadata.containsKey(className)) { return@forEach } - val s = node.metadata[className] as? OpenGLObjectState + val s = renderable.metadata[className] as? OpenGLObjectState if(s == null) { logger.warn("Could not get OpenGLObjectState for ${node.name}") return@forEach @@ -1137,10 +1147,10 @@ open class OpenGLRenderer(hub: Hub, var bufferOffset = ubo.advanceBackingBuffer() ubo.offset = bufferOffset - node.view.set(cam.view) + spatial?.view?.set(camSpatial.view) nodeUpdated = ubo.populate(offset = bufferOffset.toLong()) - val materialUbo = (node.metadata["OpenGLRenderer"]!! as OpenGLObjectState).UBOs.getValue("MaterialProperties") + val materialUbo = (renderable.metadata["OpenGLRenderer"]!! as OpenGLObjectState).UBOs.getValue("MaterialProperties") bufferOffset = ubo.advanceBackingBuffer() materialUbo.offset = bufferOffset @@ -1156,7 +1166,7 @@ open class OpenGLRenderer(hub: Hub, nodeUpdated = loadTexturesForNode(node, s) - nodeUpdated = if(node.material.materialHashCode() != s.materialHash) { + nodeUpdated = if(material.materialHashCode() != s.materialHash) { s.initialized = false initializeNode(node) true @@ -1180,13 +1190,13 @@ open class OpenGLRenderer(hub: Hub, OpenGLUBO(backingBuffer = buffers.LightParameters) } - lightUbo.add("ViewMatrix0", { cam.getTransformationForEye(0) }) - lightUbo.add("ViewMatrix1", { cam.getTransformationForEye(1) }) - lightUbo.add("InverseViewMatrix0", { cam.getTransformationForEye(0).invert() }) - lightUbo.add("InverseViewMatrix1", { cam.getTransformationForEye(1).invert() }) - lightUbo.add("ProjectionMatrix", { cam.projection }) - lightUbo.add("InverseProjectionMatrix", { Matrix4f(cam.projection).invert() }) - lightUbo.add("CamPosition", { cam.position }) + lightUbo.add("ViewMatrix0", { camSpatial.getTransformationForEye(0) }) + lightUbo.add("ViewMatrix1", { camSpatial.getTransformationForEye(1) }) + lightUbo.add("InverseViewMatrix0", { camSpatial.getTransformationForEye(0).invert() }) + lightUbo.add("InverseViewMatrix1", { camSpatial.getTransformationForEye(1).invert() }) + lightUbo.add("ProjectionMatrix", { camSpatial.projection }) + lightUbo.add("InverseProjectionMatrix", { Matrix4f(camSpatial.projection).invert() }) + lightUbo.add("CamPosition", { camSpatial.position }) // lightUbo.add("numLights", { lights.size }) // lights.forEachIndexed { i, light -> @@ -1221,33 +1231,34 @@ open class OpenGLRenderer(hub: Hub, /** * Update a [Node]'s geometry, if needed and run it's preDraw() routine. * - * @param[n] The Node to update and preDraw() + * @param[node] The Node to update and preDraw() */ - private fun preDrawAndUpdateGeometryForNode(n: Node) { - if (n is HasGeometry) { - if (n.dirty) { - n.preUpdate(this, hub) - if (n.lock.tryLock()) { - if (n.vertices.remaining() > 0 && n.normals.remaining() > 0) { - updateVertices(n) - updateNormals(n) + private fun preDrawAndUpdateGeometryForNode(node: Node) { + val renderable = node.renderableOrNull() ?: return + node.ifGeometry { + if (dirty) { + renderable.preUpdate(this@OpenGLRenderer, hub) + if (node.lock.tryLock()) { + if (vertices.remaining() > 0 && normals.remaining() > 0) { + updateVertices(getOpenGLObjectStateFromNode(renderable)) + updateNormals(getOpenGLObjectStateFromNode(renderable)) } - if (n.texcoords.remaining() > 0) { - updateTextureCoords(n) + if (texcoords.remaining() > 0) { + updateTextureCoords(getOpenGLObjectStateFromNode(renderable)) } - if (n.indices.remaining() > 0) { - updateIndices(n) + if (indices.remaining() > 0) { + updateIndices(getOpenGLObjectStateFromNode(renderable)) } - n.dirty = false + dirty = false - n.lock.unlock() + node.lock.unlock() } } - n.preDraw() + renderable.preDraw() } } @@ -1365,15 +1376,16 @@ open class OpenGLRenderer(hub: Hub, } private fun updateInstanceBuffers(sceneObjects:List): Boolean { - val instanceMasters = sceneObjects.filter { it.instances.size > 0 } + val instanceMasters = sceneObjects.filter { it is InstancedNode }.map { it as InstancedNode } instanceMasters.forEach { parent -> - var metadata = parent.rendererMetadata() + val renderable = parent.renderableOrNull() + var metadata = renderable?.rendererMetadata() if(metadata == null) { - parent.metadata["OpenGLRenderer"] = OpenGLObjectState() + if(renderable != null) renderable.metadata["OpenGLRenderer"] = OpenGLObjectState() initializeNode(parent) - metadata = parent.rendererMetadata() + metadata = renderable?.rendererMetadata() } updateInstanceBuffer(parent, metadata) @@ -1382,7 +1394,7 @@ open class OpenGLRenderer(hub: Hub, return instanceMasters.isNotEmpty() } - private fun updateInstanceBuffer(parentNode: Node, state: OpenGLObjectState?): OpenGLObjectState { + private fun updateInstanceBuffer(parentNode: InstancedNode, state: OpenGLObjectState?): OpenGLObjectState { if(state == null) { throw IllegalStateException("Metadata for ${parentNode.name} is null at updateInstanceBuffer(${parentNode.name}). This is a bug.") } @@ -1421,7 +1433,9 @@ open class OpenGLRenderer(hub: Hub, val index = AtomicInteger(0) instances.parallelStream().forEach { node -> if(node.visible) { - node.updateWorld(true, false) + node.ifSpatial { + updateWorld(true, false) + } stagingBuffer.duplicate().order(ByteOrder.LITTLE_ENDIAN).run { ubo.populateParallel(this, @@ -1539,17 +1553,21 @@ open class OpenGLRenderer(hub: Hub, } protected fun destroyNode(node: Node) { - node.metadata.remove("OpenGLRenderer") - val s = node.metadata["OpenGLRenderer"] as? OpenGLObjectState ?: return + node.ifRenderable { + this.metadata.remove("OpenGLRenderer") + val s = this.metadata["OpenGLRenderer"] as? OpenGLObjectState ?: return@ifRenderable - gl.glDeleteBuffers(s.mVertexBuffers.size, s.mVertexBuffers, 0) - gl.glDeleteBuffers(1, s.mIndexBuffer, 0) + gl.glDeleteBuffers(s.mVertexBuffers.size, s.mVertexBuffers, 0) + gl.glDeleteBuffers(1, s.mIndexBuffer, 0) - s.additionalBufferIds.forEach { _, id -> - gl.glDeleteBuffers(1, intArrayOf(id), 0) - } + s.additionalBufferIds.forEach { _, id -> + gl.glDeleteBuffers(1, intArrayOf(id), 0) + } + + node.metadata.remove("OpenGLRenderer") - node.initialized = false + initialized = false + } } protected var previousSceneObjects: HashSet = HashSet(256) @@ -1777,41 +1795,35 @@ open class OpenGLRenderer(hub: Hub, var currentShader: OpenGLShaderProgram? = null - val seenDelegates = ArrayList(5) + val seenDelegates = ArrayList(5) actualObjects.forEach renderLoop@ { node -> - val n = if(node is DelegatesRendering) { - val delegate = node.delegate - if(node.delegationType == DelegationType.OncePerDelegate && delegate != null) { - if(delegate in seenDelegates) { - return@renderLoop - } else { - seenDelegates.add(delegate) - delegate - } + val renderable = node.renderableOrNull() ?: return@renderLoop + val material = node.materialOrNull() ?: return@renderLoop + if(node is DelegatesProperties && node.getDelegationType() == DelegationType.OncePerDelegate) { + if(seenDelegates.contains(renderable)) { + return@renderLoop } else { - node.delegate ?: return@renderLoop + seenDelegates.add(renderable) } - } else { - node } - if (pass.passConfig.renderOpaque && n.material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { + if (pass.passConfig.renderOpaque && material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { return@renderLoop } - if (pass.passConfig.renderTransparent && !n.material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { + if (pass.passConfig.renderTransparent && !material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { return@renderLoop } gl.glEnable(GL4.GL_CULL_FACE) - when(n.material.cullingMode) { + when(material.cullingMode) { Material.CullingMode.None -> gl.glDisable(GL4.GL_CULL_FACE) Material.CullingMode.Front -> gl.glCullFace(GL4.GL_FRONT) Material.CullingMode.Back -> gl.glCullFace(GL4.GL_BACK) Material.CullingMode.FrontAndBack -> gl.glCullFace(GL4.GL_FRONT_AND_BACK) } - val depthTest = when(n.material.depthTest) { + val depthTest = when(material.depthTest) { Material.DepthTest.Less -> GL4.GL_LESS Material.DepthTest.Greater -> GL4.GL_GREATER Material.DepthTest.LessEqual -> GL4.GL_LEQUAL @@ -1823,14 +1835,14 @@ open class OpenGLRenderer(hub: Hub, gl.glDepthFunc(depthTest) - if(n.material.wireframe) { + if(material.wireframe) { gl.glPolygonMode(GL4.GL_FRONT_AND_BACK, GL4.GL_LINE) } else { gl.glPolygonMode(GL4.GL_FRONT_AND_BACK, GL4.GL_FILL) } - if (n.material.blending.transparent) { - with(n.material.blending) { + if (material.blending.transparent) { + with(material.blending) { gl.glBlendFuncSeparate( sourceColorBlendFactor.toOpenGL(), destinationColorBlendFactor.toOpenGL(), @@ -1845,13 +1857,13 @@ open class OpenGLRenderer(hub: Hub, } } - if (!n.metadata.containsKey("OpenGLRenderer") || !n.initialized) { - n.metadata["OpenGLRenderer"] = OpenGLObjectState() - initializeNode(n) + if (!renderable.metadata.containsKey("OpenGLRenderer") || !node.initialized) { + renderable.metadata["OpenGLRenderer"] = OpenGLObjectState() + initializeNode(node) return@renderLoop } - val s = getOpenGLObjectStateFromNode(n) + val s = getOpenGLObjectStateFromNode(renderable) val shader = s.shader ?: pass.defaultShader!! @@ -1931,10 +1943,10 @@ open class OpenGLRenderer(hub: Hub, if(shader.uboSpecs.containsKey(actualName) && shader.isValid()) { val index = shader.getUniformBlockIndex(actualName) - logger.trace("Binding {} for {}, index={}, binding={}, size={}", actualName, n.name, index, binding, ubo.getSize()) + logger.trace("Binding {} for {}, index={}, binding={}, size={}", actualName, node.name, index, binding, ubo.getSize()) if (index == -1) { - logger.error("Failed to bind UBO $actualName for ${n.name} to $binding") + logger.error("Failed to bind UBO $actualName for ${node.name} to $binding") } else { gl.glUniformBlockBinding(shader.id, index, binding) gl.glBindBufferRange(GL4.GL_UNIFORM_BUFFER, binding, @@ -1966,10 +1978,10 @@ open class OpenGLRenderer(hub: Hub, } } - if(n.instances.size > 0) { - drawNodeInstanced(n) + if(node is InstancedNode) { + drawNodeInstanced(node) } else { - drawNode(n) + drawNode(node) } } } else { @@ -2237,7 +2249,9 @@ open class OpenGLRenderer(hub: Hub, quad = nodeStore.getOrPut(quadName) { val q = Plane(Vector3f(1.0f, 1.0f, 0.0f)) - q.metadata["OpenGLRenderer"] = OpenGLObjectState() + q.ifRenderable { + this.metadata["OpenGLRenderer"] = OpenGLObjectState() + } initializeNode(q) q @@ -2260,25 +2274,23 @@ open class OpenGLRenderer(hub: Hub, * * If the [Node] implements [HasGeometry], it's geometry is also initialized by this function. * - * @param[n]: The [Node] to initialise. + * @param[node]: The [Node] to initialise. * @return True if the initialisation went alright, False if it failed. */ - @Synchronized fun initializeNode(n: Node): Boolean { - val node = if(n is DelegatesRendering) { - n.delegate ?: return false - } else { - n - } + @Synchronized fun initializeNode(node: Node): Boolean { + val renderable = node.renderableOrNull() ?: return false + val material = node.materialOrNull() ?: return false + val spatial = node.spatialOrNull() if(!node.lock.tryLock()) { return false } - if(node.rendererMetadata() == null) { - node.metadata["OpenGLRenderer"] = OpenGLObjectState() + if(renderable.rendererMetadata() == null) { + renderable.metadata["OpenGLRenderer"] = OpenGLObjectState() } - val s = node.metadata["OpenGLRenderer"] as OpenGLObjectState + val s = renderable.metadata["OpenGLRenderer"] as OpenGLObjectState if (s.initialized) { return true @@ -2292,8 +2304,8 @@ open class OpenGLRenderer(hub: Hub, gl.glGenBuffers(1, s.mIndexBuffer, 0) when { - node.material is ShaderMaterial -> { - val shaders = (node.material as ShaderMaterial).shaders + material is ShaderMaterial -> { + val shaders = material.shaders try { s.shader = prepareShaderProgram(shaders) @@ -2305,28 +2317,30 @@ open class OpenGLRenderer(hub: Hub, else -> s.shader = null } - if (node is HasGeometry) { - setVerticesAndCreateBufferForNode(node) - setNormalsAndCreateBufferForNode(node) + node.ifGeometry { + setVerticesAndCreateBufferForNode(s) + setNormalsAndCreateBufferForNode(s) - if (node.texcoords.limit() > 0) { - setTextureCoordsAndCreateBufferForNode(node) + if (this.texcoords.limit() > 0) { + setTextureCoordsAndCreateBufferForNode(s) } - if (node.indices.limit() > 0) { - setIndicesAndCreateBufferForNode(node) + if (this.indices.limit() > 0) { + setIndicesAndCreateBufferForNode(s) } } - s.materialHash = node.material.materialHashCode() + s.materialHash = material.materialHashCode() val matricesUbo = OpenGLUBO(backingBuffer = buffers.UBOs) with(matricesUbo) { name = "Matrices" - add("ModelMatrix", { node.world }) - add("NormalMatrix", { Matrix4f(node.world).invert().transpose() }) - add("isBillboard", { node.isBillboard.toInt() }) + if(spatial != null) { + add("ModelMatrix", { spatial.world }) + add("NormalMatrix", { Matrix4f(spatial.world).invert().transpose() }) + } + add("isBillboard", { renderable.isBillboard.toInt() }) sceneUBOs.add(node) @@ -2339,34 +2353,34 @@ open class OpenGLRenderer(hub: Hub, with(materialUbo) { name = "MaterialProperties" - add("materialType", { node.materialToMaterialType() }) - add("Ka", { node.material.ambient }) - add("Kd", { node.material.diffuse }) - add("Ks", { node.material.specular }) - add("Roughness", { node.material.roughness }) - add("Metallic", { node.material.metallic }) - add("Opacity", { node.material.blending.opacity }) + add("materialType", { material.materialToMaterialType(s) }) + add("Ka", { material.ambient }) + add("Kd", { material.diffuse }) + add("Ks", { material.specular }) + add("Roughness", { material.roughness }) + add("Metallic", { material.metallic }) + add("Opacity", { material.blending.opacity }) s.UBOs.put("MaterialProperties", this) } - if (node.javaClass.kotlin.memberProperties.filter { it.findAnnotation() != null }.count() > 0) { + if (renderable.parent.javaClass.kotlin.memberProperties.filter { it.findAnnotation() != null }.count() > 0) { val shaderPropertyUBO = OpenGLUBO(backingBuffer = buffers.ShaderProperties) with(shaderPropertyUBO) { name = "ShaderProperties" - val shader = if (node.material is ShaderMaterial) { + val shader = if (material is ShaderMaterial) { s.shader } else { renderpasses.filter { (it.value.passConfig.type == RenderConfigReader.RenderpassType.geometry || it.value.passConfig.type == RenderConfigReader.RenderpassType.lights) - && it.value.passConfig.renderTransparent == node.material.blending.transparent + && it.value.passConfig.renderTransparent == material.blending.transparent }.entries.firstOrNull()?.value?.defaultShader } logger.debug("Shader properties are: ${shader?.getShaderPropertyOrder()}") shader?.getShaderPropertyOrder()?.forEach { name, offset -> - add(name, { node.getShaderProperty(name) ?: 0 }, offset) + add(name, { renderable.parent.getShaderProperty(name) ?: 0 }, offset) } } @@ -2375,7 +2389,7 @@ open class OpenGLRenderer(hub: Hub, s.initialized = true node.initialized = true - node.metadata[className] = s + renderable.metadata[className] = s s.initialized = true node.lock.unlock() @@ -2384,29 +2398,26 @@ open class OpenGLRenderer(hub: Hub, private val defaultTextureNames = arrayOf("ambient", "diffuse", "specular", "normal", "alphamask", "displacement") - private fun Node.materialToMaterialType(): Int { + private fun Material.materialToMaterialType(s: OpenGLObjectState): Int { var materialType = 0 - val s = this.metadata["OpenGLRenderer"] as? OpenGLObjectState ?: return 0 - - - if (this.material.textures.containsKey("ambient") && !s.defaultTexturesFor.contains("ambient")) { + if (this.textures.containsKey("ambient") && !s.defaultTexturesFor.contains("ambient")) { materialType = materialType or MATERIAL_HAS_AMBIENT } - if (this.material.textures.containsKey("diffuse") && !s.defaultTexturesFor.contains("diffuse")) { + if (this.textures.containsKey("diffuse") && !s.defaultTexturesFor.contains("diffuse")) { materialType = materialType or MATERIAL_HAS_DIFFUSE } - if (this.material.textures.containsKey("specular") && !s.defaultTexturesFor.contains("specular")) { + if (this.textures.containsKey("specular") && !s.defaultTexturesFor.contains("specular")) { materialType = materialType or MATERIAL_HAS_SPECULAR } - if (this.material.textures.containsKey("normal") && !s.defaultTexturesFor.contains("normal")) { + if (this.textures.containsKey("normal") && !s.defaultTexturesFor.contains("normal")) { materialType = materialType or MATERIAL_HAS_NORMAL } - if (this.material.textures.containsKey("alphamask") && !s.defaultTexturesFor.contains("alphamask")) { + if (this.textures.containsKey("alphamask") && !s.defaultTexturesFor.contains("alphamask")) { materialType = materialType or MATERIAL_HAS_ALPHAMASK } @@ -2513,10 +2524,11 @@ open class OpenGLRenderer(hub: Hub, */ @Suppress("USELESS_ELVIS") private fun loadTexturesForNode(node: Node, s: OpenGLObjectState): Boolean { + val material = node.materialOrNull() ?: return false var changed = false val last = s.texturesLastSeen val now = System.nanoTime() - node.material.textures.forEachChanged(last) { (type, texture) -> + material.textures.forEachChanged(last) { (type, texture) -> changed = true logger.debug("Loading texture $texture for ${node.name}") @@ -2648,24 +2660,19 @@ open class OpenGLRenderer(hub: Hub, } /** - * Creates VAOs and VBO for a given [Node]'s vertices. - * - * @param[node] The [Node] to create the VAO/VBO for. + * Creates VAOs and VBO for a given [Geometry]'s vertices. */ - fun setVerticesAndCreateBufferForNode(node: Node) { - updateVertices(node) + fun Geometry.setVerticesAndCreateBufferForNode(s: OpenGLObjectState) { + updateVertices(s) } /** - * Updates a [Node]'s vertices. - * - * @param[node] The [Node] to update the vertices for. + * Updates a [Geometry]'s vertices. */ - fun updateVertices(node: Node) { - val s = getOpenGLObjectStateFromNode(node) - val pVertexBuffer: FloatBuffer = (node as HasGeometry).vertices.duplicate() + fun Geometry.updateVertices(s: OpenGLObjectState) { + val pVertexBuffer: FloatBuffer = vertices.duplicate() - s.mStoredPrimitiveCount = pVertexBuffer.remaining() / node.vertexSize + s.mStoredPrimitiveCount = pVertexBuffer.remaining() / vertexSize gl.glBindVertexArray(s.mVertexArrayObject[0]) gl.glBindBuffer(GL4.GL_ARRAY_BUFFER, s.mVertexBuffers[0]) @@ -2677,7 +2684,7 @@ open class OpenGLRenderer(hub: Hub, GL4.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(0, - node.vertexSize, + vertexSize, GL4.GL_FLOAT, false, 0, @@ -2688,13 +2695,10 @@ open class OpenGLRenderer(hub: Hub, } /** - * Creates VAOs and VBO for a given [Node]'s normals. - * - * @param[node] The [Node] to create the normals VBO for. + * Creates VAOs and VBO for a given [Geometry]'s normals. */ - fun setNormalsAndCreateBufferForNode(node: Node) { - val s = getOpenGLObjectStateFromNode(node) - val pNormalBuffer: FloatBuffer = (node as HasGeometry).normals.duplicate() + fun Geometry.setNormalsAndCreateBufferForNode(s: OpenGLObjectState) { + val pNormalBuffer: FloatBuffer = normals.duplicate() gl.glBindVertexArray(s.mVertexArrayObject[0]) gl.glBindBuffer(GL4.GL_ARRAY_BUFFER, s.mVertexBuffers[1]) @@ -2707,7 +2711,7 @@ open class OpenGLRenderer(hub: Hub, GL4.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(1, - node.vertexSize, + vertexSize, GL4.GL_FLOAT, false, 0, @@ -2719,13 +2723,10 @@ open class OpenGLRenderer(hub: Hub, } /** - * Updates a given [Node]'s normals. - * - * @param[node] The [Node] whose normals need updating. + * Updates a given [Geometry]'s normals. */ - fun updateNormals(node: Node) { - val s = getOpenGLObjectStateFromNode(node) - val pNormalBuffer: FloatBuffer = (node as HasGeometry).normals.duplicate() + fun Geometry.updateNormals(s: OpenGLObjectState) { + val pNormalBuffer: FloatBuffer = normals.duplicate() gl.glBindVertexArray(s.mVertexArrayObject[0]) gl.glBindBuffer(GL4.GL_ARRAY_BUFFER, s.mVertexBuffers[1]) @@ -2737,7 +2738,7 @@ open class OpenGLRenderer(hub: Hub, GL4.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(1, - node.vertexSize, + vertexSize, GL4.GL_FLOAT, false, 0, @@ -2748,22 +2749,17 @@ open class OpenGLRenderer(hub: Hub, } /** - * Creates VAOs and VBO for a given [Node]'s texcoords. - * - * @param[node] The [Node] to create the texcoord VBO for. + * Creates VAOs and VBO for a given [Geometry]'s texcoords. */ - fun setTextureCoordsAndCreateBufferForNode(node: Node) { - updateTextureCoords(node) + fun Geometry.setTextureCoordsAndCreateBufferForNode(s: OpenGLObjectState) { + updateTextureCoords(s) } /** - * Updates a given [Node]'s texcoords. - * - * @param[node] The [Node] whose texcoords need updating. + * Updates a given [Geometry]'s texcoords. */ - fun updateTextureCoords(node: Node) { - val s = getOpenGLObjectStateFromNode(node) - val pTextureCoordsBuffer: FloatBuffer = (node as HasGeometry).texcoords.duplicate() + fun Geometry.updateTextureCoords(s: OpenGLObjectState) { + val pTextureCoordsBuffer: FloatBuffer = texcoords.duplicate() gl.glBindVertexArray(s.mVertexArrayObject[0]) gl.glBindBuffer(GL4.GL_ARRAY_BUFFER, @@ -2776,7 +2772,7 @@ open class OpenGLRenderer(hub: Hub, GL4.GL_DYNAMIC_DRAW) gl.glVertexAttribPointer(2, - node.texcoordSize, + texcoordSize, GL4.GL_FLOAT, false, 0, @@ -2787,13 +2783,10 @@ open class OpenGLRenderer(hub: Hub, } /** - * Creates a index buffer for a given [Node]'s indices. - * - * @param[node] The [Node] to create the index buffer for. + * Creates a index buffer for a given [Geometry]'s indices. */ - fun setIndicesAndCreateBufferForNode(node: Node) { - val s = getOpenGLObjectStateFromNode(node) - val pIndexBuffer: IntBuffer = (node as HasGeometry).indices.duplicate() + fun Geometry.setIndicesAndCreateBufferForNode(s: OpenGLObjectState) { + val pIndexBuffer: IntBuffer = indices.duplicate() s.mStoredIndexCount = pIndexBuffer.remaining() @@ -2810,13 +2803,10 @@ open class OpenGLRenderer(hub: Hub, } /** - * Updates a given [Node]'s indices. - * - * @param[node] The [Node] whose indices need updating. + * Updates a given [Geometry]'s indices. */ - fun updateIndices(node: Node) { - val s = getOpenGLObjectStateFromNode(node) - val pIndexBuffer: IntBuffer = (node as HasGeometry).indices.duplicate() + fun Geometry.updateIndices(s: OpenGLObjectState) { + val pIndexBuffer: IntBuffer = indices.duplicate() s.mStoredIndexCount = pIndexBuffer.remaining() @@ -2839,29 +2829,33 @@ open class OpenGLRenderer(hub: Hub, * @param[offset] offset in the array or index buffer. */ fun drawNode(node: Node, offset: Int = 0, count: Int? = null) { - val s = getOpenGLObjectStateFromNode(node) + val renderable = node.renderableOrNull() ?: return + val s = getOpenGLObjectStateFromNode(renderable) if (s.mStoredIndexCount == 0 && s.mStoredPrimitiveCount == 0) { return } logger.trace("Drawing {} with {}, {} primitives, {} indices", node.name, s.shader?.modules?.entries?.joinToString(", "), s.mStoredPrimitiveCount, s.mStoredIndexCount) - gl.glBindVertexArray(s.mVertexArrayObject[0]) - if (s.mStoredIndexCount > 0) { - gl.glBindBuffer(GL4.GL_ELEMENT_ARRAY_BUFFER, - s.mIndexBuffer[0]) - gl.glDrawElements((node as HasGeometry).geometryType.toOpenGLType(), - count ?: s.mStoredIndexCount, - GL4.GL_UNSIGNED_INT, - offset.toLong()) + node.ifGeometry { + gl.glBindVertexArray(s.mVertexArrayObject[0]) - gl.glBindBuffer(GL4.GL_ELEMENT_ARRAY_BUFFER, 0) - } else { - gl.glDrawArrays((node as HasGeometry).geometryType.toOpenGLType(), offset, count ?: s.mStoredPrimitiveCount) - } + if (s.mStoredIndexCount > 0) { + gl.glBindBuffer(GL4.GL_ELEMENT_ARRAY_BUFFER, + s.mIndexBuffer[0]) + gl.glDrawElements(geometryType.toOpenGLType(), + count ?: s.mStoredIndexCount, + GL4.GL_UNSIGNED_INT, + offset.toLong()) + + gl.glBindBuffer(GL4.GL_ELEMENT_ARRAY_BUFFER, 0) + } else { + gl.glDrawArrays(geometryType.toOpenGLType(), offset, count ?: s.mStoredPrimitiveCount) + } // gl.glUseProgram(0) // gl.glBindVertexArray(0) + } } /** @@ -2871,26 +2865,29 @@ open class OpenGLRenderer(hub: Hub, * @param[offset] offset in the array or index buffer. */ protected fun drawNodeInstanced(node: Node, offset: Long = 0) { - val s = getOpenGLObjectStateFromNode(node) - - gl.glBindVertexArray(s.mVertexArrayObject[0]) - - if (s.mStoredIndexCount > 0) { - gl.glDrawElementsInstanced( - (node as HasGeometry).geometryType.toOpenGLType(), - s.mStoredIndexCount, - GL4.GL_UNSIGNED_INT, - offset, - s.instanceCount) - } else { - gl.glDrawArraysInstanced( - (node as HasGeometry).geometryType.toOpenGLType(), - 0, s.mStoredPrimitiveCount, s.instanceCount) + node.ifRenderable { + val s = getOpenGLObjectStateFromNode(this) + node.ifGeometry { + gl.glBindVertexArray(s.mVertexArrayObject[0]) + + if (s.mStoredIndexCount > 0) { + gl.glDrawElementsInstanced( + geometryType.toOpenGLType(), + s.mStoredIndexCount, + GL4.GL_UNSIGNED_INT, + offset, + s.instanceCount) + } else { + gl.glDrawArraysInstanced( + geometryType.toOpenGLType(), + 0, s.mStoredPrimitiveCount, s.instanceCount) - } + } // gl.glUseProgram(0) // gl.glBindVertexArray(0) + } + } } override fun screenshot(filename: String, overwrite: Boolean) { diff --git a/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLUBO.kt b/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLUBO.kt index 84aeaf1f1..5d8a5e00e 100644 --- a/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLUBO.kt +++ b/src/main/kotlin/graphics/scenery/backends/opengl/OpenGLUBO.kt @@ -1,5 +1,6 @@ package graphics.scenery.backends.opengl +import graphics.scenery.InstancedNode import graphics.scenery.Node import graphics.scenery.backends.UBO import java.nio.ByteBuffer @@ -59,7 +60,7 @@ class OpenGLUBO(val backingBuffer: OpenGLRenderer.OpenGLBuffer? = null) : UBO() /** * Creates this UBO's members from the instancedProperties of [node]. */ - fun fromInstance(node: Node) { + fun fromInstance(node: InstancedNode.Instance) { node.instancedProperties.forEach { members.putIfAbsent(it.key, it.value) } } diff --git a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanNodeHelpers.kt b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanNodeHelpers.kt index 5d4cc112b..ac25b1966 100644 --- a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanNodeHelpers.kt +++ b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanNodeHelpers.kt @@ -2,7 +2,8 @@ package graphics.scenery.backends.vulkan import graphics.scenery.* import graphics.scenery.backends.* -import graphics.scenery.geometry.HasGeometry +import graphics.scenery.attribute.renderable.Renderable +import graphics.scenery.attribute.material.Material import graphics.scenery.textures.Texture import graphics.scenery.textures.UpdatableTexture import graphics.scenery.utils.LazyLogger @@ -43,25 +44,25 @@ object VulkanNodeHelpers { commandPools: VulkanRenderer.CommandPools, queue: VkQueue ): VulkanObjectState { - val n = node as HasGeometry - val vertices = n.vertices.duplicate() - val normals = n.normals.duplicate() - var texcoords = n.texcoords.duplicate() - val indices = n.indices.duplicate() + val geometry = node.geometryOrNull() ?: return state + val vertices = geometry.vertices.duplicate() + val normals = geometry.normals.duplicate() + var texcoords = geometry.texcoords.duplicate() + val indices = geometry.indices.duplicate() if(vertices.remaining() == 0) { return state } - if (texcoords.remaining() == 0 && node.instances.size > 0) { - val buffer = JEmalloc.je_calloc(1, 4L * vertices.remaining() / n.vertexSize * n.texcoordSize) + if (texcoords.remaining() == 0 && node is InstancedNode) { + val buffer = JEmalloc.je_calloc(1, 4L * vertices.remaining() / geometry.vertexSize * geometry.texcoordSize) if(buffer == null) { - logger.error("Could not allocate texcoords buffer with ${4L * vertices.remaining() / n.vertexSize * n.texcoordSize} bytes for ${node.name}") + logger.error("Could not allocate texcoords buffer with ${4L * vertices.remaining() / geometry.vertexSize * geometry.texcoordSize} bytes for ${node.name}") return state } else { - n.texcoords = buffer.asFloatBuffer() - texcoords = n.texcoords.asReadOnlyBuffer() + geometry.texcoords = buffer.asFloatBuffer() + texcoords = geometry.texcoords.asReadOnlyBuffer() } } @@ -79,8 +80,8 @@ object VulkanNodeHelpers { val fb = stridedBuffer.asFloatBuffer() val ib = stridedBuffer.asIntBuffer() - state.vertexCount = vertices.remaining() / n.vertexSize - logger.trace("${node.name} has ${vertices.remaining()} floats and ${texcoords.remaining() / n.texcoordSize} remaining") + state.vertexCount = vertices.remaining() / geometry.vertexSize + logger.trace("${node.name} has ${vertices.remaining()} floats and ${texcoords.remaining() / geometry.texcoordSize} remaining") for (index in 0 until vertices.remaining() step 3) { fb.put(vertices.get()) @@ -145,7 +146,7 @@ object VulkanNodeHelpers { if(this != vertexBuffer) { close() } } state.indexOffset = vertexBuffer.bufferOffset + vertexAllocationBytes - state.indexCount = n.indices.remaining() + state.indexCount = geometry.indices.remaining() JEmalloc.je_free(stridedBuffer) stagingBuffer.close() @@ -154,18 +155,18 @@ object VulkanNodeHelpers { } /** - * Updates instance buffers for a given [parentNode] on [device]. Modifies the [parentNode]'s [state] - * and allocates necessary command buffers from [commandPools] and submits to [queue]. Returns the [parentNode]'s modified [VulkanObjectState]. + * Updates instance buffers for a given [node] on [device]. Modifies the [node]'s [state] + * and allocates necessary command buffers from [commandPools] and submits to [queue]. Returns the [node]'s modified [VulkanObjectState]. */ - fun updateInstanceBuffer(device: VulkanDevice, parentNode: Node, state: VulkanObjectState, commandPools: VulkanRenderer.CommandPools, queue: VkQueue): VulkanObjectState { - logger.trace("Updating instance buffer for ${parentNode.name}") + fun updateInstanceBuffer(device: VulkanDevice, node: InstancedNode, state: VulkanObjectState, commandPools: VulkanRenderer.CommandPools, queue: VkQueue): VulkanObjectState { + logger.trace("Updating instance buffer for ${node.name}") // parentNode.instances is a CopyOnWrite array list, and here we keep a reference to the original. // If it changes in the meantime, no problemo. - val instances = parentNode.instances + val instances = node.instances if (instances.isEmpty()) { - logger.debug("$parentNode has no child instances attached, returning.") + logger.debug("$node has no child instances attached, returning.") return state } @@ -194,12 +195,12 @@ object VulkanNodeHelpers { ubo.createUniformBuffer() val index = AtomicInteger(0) - instances.parallelStream().forEach { node -> - if(node.visible) { - node.updateWorld(true, false) + instances.parallelStream().forEach { instancedNode -> + if(instancedNode.visible) { + instancedNode.spatialOrNull()?.updateWorld(true, false) stagingBuffer.stagingBuffer.duplicate().order(ByteOrder.LITTLE_ENDIAN).run { - ubo.populateParallel(this, offset = index.getAndIncrement() * ubo.getSize() * 1L, elements = node.instancedProperties) + ubo.populateParallel(this, offset = index.getAndIncrement() * ubo.getSize() * 1L, elements = instancedNode.instancedProperties) } } } @@ -213,7 +214,7 @@ object VulkanNodeHelpers { && existingInstanceBuffer.size < 1.5*instanceBufferSize) { existingInstanceBuffer } else { - logger.debug("Instance buffer for ${parentNode.name} needs to be reallocated due to insufficient size ($instanceBufferSize vs ${state.vertexBuffers["instance"]?.size ?: ""})") + logger.debug("Instance buffer for ${node.name} needs to be reallocated due to insufficient size ($instanceBufferSize vs ${state.vertexBuffers["instance"]?.size ?: ""})") state.vertexBuffers["instance"]?.close() val buffer = VulkanBuffer(device, @@ -253,6 +254,7 @@ object VulkanNodeHelpers { * Returns a [Pair] of [Boolean], indicating whether contents or descriptor set have changed. */ fun loadTexturesForNode(device: VulkanDevice, node: Node, s: VulkanObjectState, defaultTextures: Map, textureCache: MutableMap, commandPools: VulkanRenderer.CommandPools, queue: VkQueue): Pair { + val material = node.materialOrNull() ?: return Pair(false, false) val defaultTexture = defaultTextures["DefaultTexture"] ?: throw IllegalStateException("Default fallback texture does not exist.") // if a node is not yet initialized, we'll definitely require a new DS var descriptorUpdated = !node.initialized @@ -260,7 +262,7 @@ object VulkanNodeHelpers { val last = s.texturesLastSeen val now = System.nanoTime() - node.material.textures.forEachChanged(last) { (type, texture) -> + material.textures.forEachChanged(last) { (type, texture) -> contentUpdated = true val slot = VulkanObjectState.textureTypeToSlot(type) val generateMipmaps = Texture.mipmappedObjectTextures.contains(type) @@ -313,7 +315,7 @@ object VulkanNodeHelpers { s.texturesLastSeen = now - val isCompute = node.material is ShaderMaterial && ((node.material as? ShaderMaterial)?.isCompute() ?: false) + val isCompute = material is ShaderMaterial && ((material as? ShaderMaterial)?.isCompute() ?: false) if(!isCompute) { Texture.objectTextures.forEach { if (!s.textures.containsKey(it)) { @@ -333,31 +335,33 @@ object VulkanNodeHelpers { * * Returns true if the node has been given a custom shader, and false if not. */ - fun initializeCustomShadersForNode(device: VulkanDevice, node: Node, addInitializer: Boolean = true, renderpasses: Map, lateResizeInitializers: MutableMap Any>, buffers: VulkanRenderer.DefaultBuffers): Boolean { + fun initializeCustomShadersForNode(device: VulkanDevice, node: Node, addInitializer: Boolean = true, renderpasses: Map, lateResizeInitializers: MutableMap Any>, buffers: VulkanRenderer.DefaultBuffers): Boolean { - if(!(node.material.blending.transparent || node.material is ShaderMaterial || node.material.cullingMode != Material.CullingMode.Back || node.material.wireframe)) { + val renderable = node.renderableOrNull() ?: return false + val material = node.materialOrNull() ?: return false + if(!(material.blending.transparent || material is ShaderMaterial || material.cullingMode != Material.CullingMode.Back || material.wireframe)) { logger.debug("Using default renderpass material for ${node.name}") renderpasses .filter { it.value.passConfig.type == RenderConfigReader.RenderpassType.geometry || it.value.passConfig.type == RenderConfigReader.RenderpassType.lights } .forEach { - it.value.removePipeline(node) + it.value.removePipeline(renderable) } - lateResizeInitializers.remove(node) + lateResizeInitializers.remove(renderable) return false } if(addInitializer) { - lateResizeInitializers.remove(node) + lateResizeInitializers.remove(renderable) } - node.rendererMetadata()?.let { s -> + renderable.rendererMetadata()?.let { s -> renderpasses.filter { it.value.passConfig.type == RenderConfigReader.RenderpassType.geometry || it.value.passConfig.type == RenderConfigReader.RenderpassType.lights } .map { pass -> val shaders = when { - node.material is ShaderMaterial -> { + material is ShaderMaterial -> { logger.debug("Initializing preferred pipeline for ${node.name} from ShaderMaterial") - (node.material as ShaderMaterial).shaders + material.shaders } else -> { @@ -383,16 +387,16 @@ object VulkanNodeHelpers { } pass.value.initializeInputAttachmentDescriptorSetLayouts(shaderModules) - pass.value.initializePipeline("preferred-${node.uuid}", + pass.value.initializePipeline("preferred-${renderable.getUuid()}", shaderModules, settings = { pipeline -> - when(node.material.cullingMode) { + when(material.cullingMode) { Material.CullingMode.None -> pipeline.rasterizationState.cullMode(VK10.VK_CULL_MODE_NONE) Material.CullingMode.Front -> pipeline.rasterizationState.cullMode(VK10.VK_CULL_MODE_FRONT_BIT) Material.CullingMode.Back -> pipeline.rasterizationState.cullMode(VK10.VK_CULL_MODE_BACK_BIT) Material.CullingMode.FrontAndBack -> pipeline.rasterizationState.cullMode(VK10.VK_CULL_MODE_FRONT_AND_BACK) } - when(node.material.depthTest) { + when(material.depthTest) { Material.DepthTest.Equal -> pipeline.depthStencilState.depthCompareOp(VK10.VK_COMPARE_OP_EQUAL) Material.DepthTest.Less -> pipeline.depthStencilState.depthCompareOp(VK10.VK_COMPARE_OP_LESS) Material.DepthTest.Greater -> pipeline.depthStencilState.depthCompareOp(VK10.VK_COMPARE_OP_GREATER) @@ -402,14 +406,14 @@ object VulkanNodeHelpers { Material.DepthTest.Never -> pipeline.depthStencilState.depthCompareOp(VK10.VK_COMPARE_OP_NEVER) } - if(node.material.wireframe) { + if(material.wireframe) { pipeline.rasterizationState.polygonMode(VK10.VK_POLYGON_MODE_LINE) } else { pipeline.rasterizationState.polygonMode(VK10.VK_POLYGON_MODE_FILL) } - if(node.material.blending.transparent) { - with(node.material.blending) { + if(material.blending.transparent) { + with(material.blending) { val blendStates = pipeline.colorBlendState.pAttachments() for (attachment in 0 until (blendStates?.capacity() ?: 0)) { val state = blendStates?.get(attachment) @@ -433,15 +437,15 @@ object VulkanNodeHelpers { } - if (node.needsShaderPropertyUBO()) { + if (renderable.needsShaderPropertyUBO()) { renderpasses.filter { (it.value.passConfig.type == RenderConfigReader.RenderpassType.geometry || it.value.passConfig.type == RenderConfigReader.RenderpassType.lights) && - it.value.passConfig.renderTransparent == node.material.blending.transparent + it.value.passConfig.renderTransparent == material.blending.transparent }.forEach { pass -> val dsl = pass.value.initializeShaderPropertyDescriptorSetLayout() logger.debug("Initializing shader properties for ${node.name} in pass ${pass.key}") - val order = pass.value.getShaderPropertyOrder(node) + val order = pass.value.getShaderPropertyOrder(renderable) val shaderPropertyUbo = VulkanUBO(device, backingBuffer = buffers.ShaderProperties) with(shaderPropertyUbo) { @@ -449,7 +453,7 @@ object VulkanNodeHelpers { order.forEach { (name, offset) -> // TODO: See whether returning 0 on non-found shader property has ill side effects - add(name, { node.getShaderProperty(name) ?: 0 }, offset) + add(name, { renderable.parent.getShaderProperty(name) ?: 0 }, offset) } val result = this.createUniformBuffer() @@ -468,13 +472,13 @@ object VulkanNodeHelpers { } if(addInitializer) { - lateResizeInitializers[node] = { + lateResizeInitializers[renderable] = { val reloaded = initializeCustomShadersForNode(device, node, addInitializer = false, renderpasses, lateResizeInitializers, buffers) if(reloaded) { - node.rendererMetadata()?.texturesToDescriptorSets(device, + renderable.rendererMetadata()?.texturesToDescriptorSets(device, renderpasses.filter { pass -> pass.value.passConfig.type != RenderConfigReader.RenderpassType.quad }, - node) + renderable) } } } @@ -488,7 +492,8 @@ object VulkanNodeHelpers { return false } - private fun Node.needsShaderPropertyUBO(): Boolean = this + private fun Renderable.needsShaderPropertyUBO(): Boolean = this + .parent .javaClass .kotlin .memberProperties @@ -511,7 +516,7 @@ object VulkanNodeHelpers { /** * Returns a node's [VulkanRenderer] metadata, [VulkanObjectState], if available. */ - fun Node.rendererMetadata(): VulkanObjectState? { + fun Renderable.rendererMetadata(): VulkanObjectState? { return this.metadata["VulkanRenderer"] as? VulkanObjectState } } diff --git a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanObjectState.kt b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanObjectState.kt index a861641ea..841e6ba5c 100644 --- a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanObjectState.kt +++ b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanObjectState.kt @@ -1,18 +1,17 @@ package graphics.scenery.backends.vulkan -import graphics.scenery.Node import graphics.scenery.textures.Texture import org.lwjgl.system.MemoryUtil.* import org.lwjgl.vulkan.* import graphics.scenery.NodeMetadata import graphics.scenery.backends.RenderConfigReader import graphics.scenery.backends.RendererFlags +import graphics.scenery.attribute.renderable.Renderable import graphics.scenery.utils.LazyLogger import java.util.* import java.util.concurrent.ConcurrentHashMap import kotlin.time.ExperimentalTime import kotlin.time.measureTime -import kotlin.time.measureTimedValue /** * Vulkan Object State class. Saves texture, UBO, pipeline and vertex buffer state. @@ -83,7 +82,7 @@ open class VulkanObjectState : NodeMetadata { * The set will be allocated from [descriptorPool]. */ @OptIn(ExperimentalTime::class) - fun texturesToDescriptorSets(device: VulkanDevice, passes: Map, node: Node) { + fun texturesToDescriptorSets(device: VulkanDevice, passes: Map, renderable: Renderable) { val updateDuration = measureTime { val textures = textures.entries.groupBy { Texture.objectTextures.contains(it.key) } val objectTextures = textures[true] @@ -98,7 +97,7 @@ open class VulkanObjectState : NodeMetadata { if (descriptorSetLayoutObjectTextures != null && objectTextures != null && objectTextures.isNotEmpty()) { textureDescriptorSets[pass.passConfig.type.name to "ObjectTextures"] = createOrUpdateTextureDescriptorSet( "ObjectTextures", - node, + renderable, pass, Texture.objectTextures.map { ot -> objectTextures.first { it.key == ot } }, descriptorSetLayoutObjectTextures, @@ -116,7 +115,7 @@ open class VulkanObjectState : NodeMetadata { } others?.mapNotNull { texture -> - pass.getDescriptorSetLayoutForTexture(texture.key, node) + pass.getDescriptorSetLayoutForTexture(texture.key, renderable) }?.groupBy { it.first }?.forEach { @@ -128,7 +127,7 @@ open class VulkanObjectState : NodeMetadata { val firstTextureName = textureNames.first() val texturesForSet = textureNames.mapNotNull { t -> others.firstOrNull() { it.key == t } } - val ds = createOrUpdateTextureDescriptorSet(firstTextureName, node, pass, texturesForSet, dsl, device) + val ds = createOrUpdateTextureDescriptorSet(firstTextureName, renderable, pass, texturesForSet, dsl, device) texturesForSet.forEach { (textureName, _) -> textureDescriptorSets[pass.passConfig.type.name to textureName] = ds @@ -144,10 +143,10 @@ open class VulkanObjectState : NodeMetadata { textureDescriptorSets.clear() } - private fun createOrUpdateTextureDescriptorSet(name: String, node: Node, pass: VulkanRenderpass, textures: List>, descriptorSetLayout: Long, device: VulkanDevice): Long { + private fun createOrUpdateTextureDescriptorSet(name: String, renderable: Renderable, pass: VulkanRenderpass, textures: List>, descriptorSetLayout: Long, device: VulkanDevice): Long { val cacheKey = TextureKey(device.vulkanDevice, descriptorSetLayout, textures) val passName = pass.passConfig.type.name - val pipeline = pass.getActivePipeline(node) + val pipeline = pass.getActivePipeline(renderable) val existing = cache[cacheKey] diff --git a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanPostprocessPass.kt b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanPostprocessPass.kt index e78326c95..77c802f9b 100644 --- a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanPostprocessPass.kt +++ b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanPostprocessPass.kt @@ -105,7 +105,7 @@ object VulkanPostprocessPass { name.startsWith("ShaderParameters") -> "ShaderParameters-${pass.name}" name.startsWith("Inputs") -> "input-${pass.name}-${spec.set}" name.startsWith("Matrices") -> { - val offsets = sceneUBOs.first().rendererMetadata()!!.UBOs["Matrices"]!!.second.offsets + val offsets = sceneUBOs.first().renderableOrNull()?.rendererMetadata()!!.UBOs["Matrices"]!!.second.offsets this.uboOffsets.put(offsets) requiredDynamicOffsets += 3 diff --git a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderer.kt b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderer.kt index 80d8b88a7..c7a3d311c 100644 --- a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderer.kt +++ b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderer.kt @@ -3,7 +3,9 @@ package graphics.scenery.backends.vulkan import graphics.scenery.* import graphics.scenery.backends.* import graphics.scenery.backends.vulkan.VulkanDevice.VulkanObjectType.* -import graphics.scenery.geometry.HasGeometry +import graphics.scenery.attribute.renderable.Renderable +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.DelegatesRenderable import graphics.scenery.spirvcrossj.Loader import graphics.scenery.spirvcrossj.libspirvcrossj import graphics.scenery.textures.Texture @@ -140,7 +142,7 @@ open class VulkanRenderer(hub: Hub, } } - private val lateResizeInitializers = ConcurrentHashMap Any>() + private val lateResizeInitializers = ConcurrentHashMap Any>() inner class SwapchainRecreator { var mustRecreate = true @@ -761,9 +763,20 @@ open class VulkanRenderer(hub: Hub, } fun updateNodeGeometry(node: Node) { - if (node is HasGeometry && node.vertices.remaining() > 0) { - node.rendererMetadata()?.let { s -> - VulkanNodeHelpers.createVertexBuffers(device, node, s, stagingPool, geometryPool, commandPools, queue) + val renderable = node.renderableOrNull() ?: return + node.ifGeometry { + if (vertices.remaining() > 0) { + renderable.rendererMetadata()?.let { s -> + VulkanNodeHelpers.createVertexBuffers( + device, + node, + s, + stagingPool, + geometryPool, + commandPools, + queue + ) + } } } } @@ -771,25 +784,25 @@ open class VulkanRenderer(hub: Hub, /** * Returns the material type flag for a Node, considering it's [Material]'s textures. */ - protected fun Node.materialTypeFromTextures(s: VulkanObjectState): Int { + protected fun Material.materialTypeFromTextures(s: VulkanObjectState): Int { var materialType = 0 - if (material.textures.containsKey("ambient") && !s.defaultTexturesFor.contains("ambient")) { + if (this.textures.containsKey("ambient") && !s.defaultTexturesFor.contains("ambient")) { materialType = materialType or MATERIAL_HAS_AMBIENT } - if (material.textures.containsKey("diffuse") && !s.defaultTexturesFor.contains("diffuse")) { + if (this.textures.containsKey("diffuse") && !s.defaultTexturesFor.contains("diffuse")) { materialType = materialType or MATERIAL_HAS_DIFFUSE } - if (material.textures.containsKey("specular") && !s.defaultTexturesFor.contains("specular")) { + if (this.textures.containsKey("specular") && !s.defaultTexturesFor.contains("specular")) { materialType = materialType or MATERIAL_HAS_SPECULAR } - if (material.textures.containsKey("normal") && !s.defaultTexturesFor.contains("normal")) { + if (this.textures.containsKey("normal") && !s.defaultTexturesFor.contains("normal")) { materialType = materialType or MATERIAL_HAS_NORMAL } - if (material.textures.containsKey("alphamask") && !s.defaultTexturesFor.contains("alphamask")) { + if (this.textures.containsKey("alphamask") && !s.defaultTexturesFor.contains("alphamask")) { materialType = materialType or MATERIAL_HAS_ALPHAMASK } @@ -799,25 +812,23 @@ open class VulkanRenderer(hub: Hub, /** * Initialises a given [Node] with the metadata required by the [VulkanRenderer]. */ - fun initializeNode(n: Node): Boolean { - val node = if(n is DelegatesRendering) { - val delegate = n.delegate ?: return false - - logger.debug("Initialising node $n with delegate $delegate (state=${delegate.state})") - delegate + fun initializeNode(node: Node): Boolean { + val renderable = node.renderableOrNull() ?: return false + val material = node.materialOrNull() ?: return false + if(node is DelegatesRenderable) { + logger.debug("Initialising node $node with delegate $renderable (state=${node.state})") } else { - logger.debug("Initialising node $n") - n + logger.debug("Initialising node $node") } - if(node.rendererMetadata() == null) { - node.metadata["VulkanRenderer"] = VulkanObjectState() + if(renderable.rendererMetadata() == null) { + renderable.metadata["VulkanRenderer"] = VulkanObjectState() } - var s: VulkanObjectState = node.rendererMetadata() ?: throw IllegalStateException("Node ${node.name} does not contain metadata object") + var s: VulkanObjectState = renderable.rendererMetadata() ?: throw IllegalStateException("Node ${node.name} does not contain metadata object") if(node.state != State.Ready) { - logger.info("Not initialising node $node because state=${node.state}") + logger.info("Not initialising Renderable $renderable because state=${node.state}") return false } @@ -825,18 +836,18 @@ open class VulkanRenderer(hub: Hub, s.flags.add(RendererFlags.Seen) - if(n is HasGeometry) { - logger.debug("Initializing geometry for ${node.name} (${(node as HasGeometry).vertices.remaining() / node.vertexSize} vertices/${node.indices.remaining()} indices)") + node.ifGeometry { + logger.debug("Initializing geometry for ${node.name} (${vertices.remaining() / vertexSize} vertices/${indices.remaining()} indices)") // determine vertex input type s.vertexInputType = when { - node.vertices.remaining() > 0 && node.normals.remaining() > 0 && node.texcoords.remaining() > 0 -> VertexDataKinds.PositionNormalTexcoord - node.vertices.remaining() > 0 && node.normals.remaining() > 0 && node.texcoords.remaining() == 0 -> VertexDataKinds.PositionNormal - node.vertices.remaining() > 0 && node.normals.remaining() == 0 && node.texcoords.remaining() > 0 -> VertexDataKinds.PositionTexcoords + vertices.remaining() > 0 && normals.remaining() > 0 && texcoords.remaining() > 0 -> VertexDataKinds.PositionNormalTexcoord + vertices.remaining() > 0 && normals.remaining() > 0 && texcoords.remaining() == 0 -> VertexDataKinds.PositionNormal + vertices.remaining() > 0 && normals.remaining() == 0 && texcoords.remaining() > 0 -> VertexDataKinds.PositionTexcoords else -> VertexDataKinds.PositionNormalTexcoord } // create custom vertex description if necessary, else use one of the defaults - s.vertexDescription = if (node.instances.size > 0 || node.instancedProperties.size > 0) { + s.vertexDescription = if (node is InstancedNode) { VulkanNodeHelpers.updateInstanceBuffer(device, node, s, commandPools, queue) // TODO: Rewrite shader in case it does not conform to coord/normal/texcoord vertex description s.vertexInputType = VertexDataKinds.PositionNormalTexcoord @@ -869,9 +880,11 @@ open class VulkanRenderer(hub: Hub, val matricesUbo = VulkanUBO(device, backingBuffer = buffers.UBOs) with(matricesUbo) { name = "Matrices" - add("ModelMatrix", { node.world }) - add("NormalMatrix", { Matrix4f(node.world).invert().transpose() }) - add("isBillboard", { node.isBillboard.toInt() }) + node.ifSpatial { + add("ModelMatrix", { world }) + add("NormalMatrix", { Matrix4f(world).invert().transpose() }) + } + add("isBillboard", { renderable.isBillboard.toInt() }) createUniformBuffer() sceneUBOs.add(node) @@ -894,21 +907,21 @@ open class VulkanRenderer(hub: Hub, if(descriptorUpdated) { s.texturesToDescriptorSets(device, renderpasses.filter { it.value.passConfig.type != RenderConfigReader.RenderpassType.quad }, - node) + renderable) } - s.materialHashCode = node.material.materialHashCode() + s.materialHashCode = material.materialHashCode() val materialUbo = VulkanUBO(device, backingBuffer = buffers.UBOs) with(materialUbo) { name = "MaterialProperties" - add("materialType", { node.materialTypeFromTextures(s) }) - add("Ka", { node.material.ambient }) - add("Kd", { node.material.diffuse }) - add("Ks", { node.material.specular }) - add("Roughness", { node.material.roughness}) - add("Metallic", { node.material.metallic}) - add("Opacity", { node.material.blending.opacity }) + add("materialType", { material.materialTypeFromTextures(s) }) + add("Ka", { material.ambient }) + add("Kd", { material.diffuse }) + add("Ks", { material.specular }) + add("Roughness", { material.roughness}) + add("Metallic", { material.metallic}) + add("Opacity", { material.blending.opacity }) createUniformBuffer() s.UBOs.put("MaterialProperties", materialPropertiesDescriptorSet.contents to this) @@ -917,29 +930,30 @@ open class VulkanRenderer(hub: Hub, s.initialized = true s.flags.add(RendererFlags.Initialised) node.initialized = true - node.metadata["VulkanRenderer"] = s + renderable.metadata["VulkanRenderer"] = s return true } fun destroyNode(node: Node) { logger.trace("Destroying node ${node.name}...") - if (!node.metadata.containsKey("VulkanRenderer")) { + val renderable = node.renderableOrNull() + if (!(renderable?.metadata?.containsKey("VulkanRenderer") ?: false)) { return } - lateResizeInitializers.remove(node) + lateResizeInitializers.remove(renderable) node.initialized = false - node.rendererMetadata()?.UBOs?.forEach { it.value.second.close() } + renderable?.rendererMetadata()?.UBOs?.forEach { it.value.second.close() } - if (node is HasGeometry) { - node.rendererMetadata()?.vertexBuffers?.forEach { + node.ifGeometry { + renderable?.rendererMetadata()?.vertexBuffers?.forEach { it.value.close() } } - node.metadata.remove("VulkanRenderer") + renderable?.metadata?.remove("VulkanRenderer") } protected fun prepareDefaultDescriptorSetLayouts(device: VulkanDevice): ConcurrentHashMap { @@ -1118,7 +1132,7 @@ open class VulkanRenderer(hub: Hub, } } - protected fun vertexDescriptionFromInstancedNode(node: Node, template: VertexDescription): VertexDescription { + protected fun vertexDescriptionFromInstancedNode(node: InstancedNode, template: VertexDescription): VertexDescription { logger.debug("Creating instanced vertex description for ${node.name}") if(template.attributeDescription == null || template.bindingDescription == null) { @@ -1548,63 +1562,61 @@ open class VulkanRenderer(hub: Hub, if (renderpasses.filter { it.value.passConfig.type != RenderConfigReader.RenderpassType.quad }.any()) { sceneNodes.forEach { node -> - val it = if(node is DelegatesRendering) { - node.delegate ?: return@forEach - } else { - node - } + val renderable = node.renderableOrNull() ?: return@forEach + val material = node.materialOrNull() ?: return@forEach // if a node is not initialized yet, it'll be initialized here and it's UBO updated // in the next round - if (it.rendererMetadata() == null || it.state == State.Created || it.rendererMetadata()?.initialized == false) { - logger.debug("${it.name} is not initialized, doing that now") - it.metadata["VulkanRenderer"] = VulkanObjectState() - initializeNode(it) + if (renderable.rendererMetadata() == null || node.state == State.Created || renderable.rendererMetadata()?.initialized == false) { + logger.debug("${node.name} is not initialized, doing that now") + renderable.metadata["VulkanRenderer"] = VulkanObjectState() + initializeNode(node) return@forEach } - if(!it.preDraw()) { - it.rendererMetadata()?.preDrawSkip = true + if(!renderable.preDraw()) { + renderable.rendererMetadata()?.preDrawSkip = true return@forEach } else { - it.rendererMetadata()?.preDrawSkip = false + renderable.rendererMetadata()?.preDrawSkip = false } // the current command buffer will be forced to be re-recorded if either geometry, blending or // texturing of a given node have changed, as these might change pipelines or descriptor sets, leading // to the original command buffer becoming obsolete. - it.rendererMetadata()?.let { metadata -> - if (it.dirty) { - logger.debug("Force command buffer re-recording, as geometry for {} has been updated", it.name) + renderable.rendererMetadata()?.let { metadata -> + node.ifGeometry { + if (dirty) { + logger.debug("Force command buffer re-recording, as geometry for {} has been updated", node.name) - it.preUpdate(this@VulkanRenderer, hub) - updateNodeGeometry(it) - it.dirty = false + renderable.preUpdate(this@VulkanRenderer, hub) + updateNodeGeometry(node) + dirty = false - rerecordingCauses.add(it.name) - forceRerecording = true + rerecordingCauses.add(node.name) + forceRerecording = true + } } // this covers cases where a master node is not given any instanced properties in the beginning // but only later, or when instancing is removed at some point. - if((!metadata.instanced && (it.instancedProperties.size > 0 && it.instances.size > 0)) || - metadata.instanced && it.instancedProperties.size == 0 && it.instances.size == 0) { + if((!metadata.instanced && node is InstancedNode) || + metadata.instanced && node !is InstancedNode) { metadata.initialized = false - initializeNode(it) + initializeNode(node) return@forEach } - val material = it.material val reloadTime = measureTimeMillis { - val (texturesUpdatedForNode, descriptorUpdated) = VulkanNodeHelpers.loadTexturesForNode(device, it, metadata, defaultTextures, textureCache, commandPools, queue) + val (texturesUpdatedForNode, descriptorUpdated) = VulkanNodeHelpers.loadTexturesForNode(device, node, metadata, defaultTextures, textureCache, commandPools, queue) if(descriptorUpdated) { metadata.texturesToDescriptorSets(device, renderpasses.filter { it.value.passConfig.type != RenderConfigReader.RenderpassType.quad }, - it) + renderable) - logger.trace("Force command buffer re-recording, as reloading textures for ${it.name}") - rerecordingCauses.add(it.name) + logger.trace("Force command buffer re-recording, as reloading textures for ${node.name}") + rerecordingCauses.add(node.name) forceRerecording = true } @@ -1616,18 +1628,18 @@ open class VulkanRenderer(hub: Hub, } if (material.materialHashCode() != metadata.materialHashCode || (material is ShaderMaterial && material.shaders.stale)) { - val reloaded = VulkanNodeHelpers.initializeCustomShadersForNode(device, it, true, renderpasses, lateResizeInitializers, buffers) + val reloaded = VulkanNodeHelpers.initializeCustomShadersForNode(device, node, true, renderpasses, lateResizeInitializers, buffers) logger.debug("{}: Material is stale, re-recording, reloaded={}", node.name, reloaded) - metadata.materialHashCode = it.material.materialHashCode() + metadata.materialHashCode = material.materialHashCode() // if we reloaded the node's shaders, we might need to recreate its texture descriptor sets if(reloaded) { - it.rendererMetadata()?.texturesToDescriptorSets(device, + renderable.rendererMetadata()?.texturesToDescriptorSets(device, renderpasses.filter { pass -> pass.value.passConfig.type != RenderConfigReader.RenderpassType.quad }, - it) + renderable) } - rerecordingCauses.add(it.name) + rerecordingCauses.add(node.name) forceRerecording = true (material as? ShaderMaterial)?.shaders?.stale = false @@ -1940,18 +1952,18 @@ open class VulkanRenderer(hub: Hub, wantAligned = true)) } - private fun Node.rendererMetadata(): VulkanObjectState? { + private fun Renderable.rendererMetadata(): VulkanObjectState? { return this.metadata["VulkanRenderer"] as? VulkanObjectState } private fun updateInstanceBuffers(sceneObjects: List) = runBlocking { - val instanceMasters = sceneObjects.filter { it.instances.size > 0 } + val instanceMasters = sceneObjects.filter { it is InstancedNode }.parallelMap { it as InstancedNode } instanceMasters.forEach { parent -> - val metadata = parent.rendererMetadata() + val metadata = parent.renderableOrNull()?.rendererMetadata() if(metadata != null && metadata.initialized) { - VulkanNodeHelpers.updateInstanceBuffer(device, parent, parent.rendererMetadata()!!, commandPools, queue) + VulkanNodeHelpers.updateInstanceBuffer(device, parent, metadata, commandPools, queue) } } @@ -1997,26 +2009,27 @@ open class VulkanRenderer(hub: Hub, } } - cam.view = cam.getTransformation() + val camSpatial = cam.spatial() + camSpatial.view = camSpatial.getTransformation() // cam.updateWorld(true, false) buffers.VRParameters.reset() val vrUbo = defaultUBOs["VRParameters"]!! vrUbo.add("projection0", { (hmd?.getEyeProjection(0, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection).applyVulkanCoordinateSystem() + ?: camSpatial.projection).applyVulkanCoordinateSystem() }) vrUbo.add("projection1", { (hmd?.getEyeProjection(1, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection).applyVulkanCoordinateSystem() + ?: camSpatial.projection).applyVulkanCoordinateSystem() }) vrUbo.add("inverseProjection0", { (hmd?.getEyeProjection(0, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection).applyVulkanCoordinateSystem().invert() + ?: camSpatial.projection).applyVulkanCoordinateSystem().invert() }) vrUbo.add("inverseProjection1", { (hmd?.getEyeProjection(1, cam.nearPlaneDistance, cam.farPlaneDistance) - ?: cam.projection).applyVulkanCoordinateSystem().invert() + ?: camSpatial.projection).applyVulkanCoordinateSystem().invert() }) vrUbo.add("headShift", { hmd?.getHeadToEyeTransform(0) ?: Matrix4f().identity() }) vrUbo.add("IPD", { hmd?.getIPD() ?: 0.05f }) @@ -2028,14 +2041,16 @@ open class VulkanRenderer(hub: Hub, buffers.ShaderProperties.reset() sceneUBOs.forEach { node -> + val renderable = node.renderableOrNull() ?: return@forEach + val spatial = node.spatialOrNull() node.lock.withLock { var nodeUpdated: Boolean by StickyBoolean(initial = false) - if (!node.metadata.containsKey("VulkanRenderer")) { + if (!renderable.metadata.containsKey("VulkanRenderer")) { return@forEach } - val s = node.rendererMetadata() ?: return@forEach + val s = renderable.rendererMetadata() ?: return@forEach val ubo = s.UBOs["Matrices"]!!.second @@ -2045,9 +2060,9 @@ open class VulkanRenderer(hub: Hub, ubo.offsets.put(0, bufferOffset) ubo.offsets.limit(1) -// node.projection.copyFrom(cam.projection.applyVulkanCoordinateSystem()) +// spatial.projection.copyFrom(cam.projection.applyVulkanCoordinateSystem()) - node.view.set(cam.view) + spatial?.view?.set(camSpatial.view) nodeUpdated = ubo.populate(offset = bufferOffset.toLong()) @@ -2079,13 +2094,13 @@ open class VulkanRenderer(hub: Hub, buffers.UBOs.copyFromStagingBuffer() val lightUbo = defaultUBOs["LightParameters"]!! - lightUbo.add("ViewMatrix0", { cam.getTransformationForEye(0) }) - lightUbo.add("ViewMatrix1", { cam.getTransformationForEye(1) }) - lightUbo.add("InverseViewMatrix0", { cam.getTransformationForEye(0).invert() }) - lightUbo.add("InverseViewMatrix1", { cam.getTransformationForEye(1).invert() }) - lightUbo.add("ProjectionMatrix", { cam.projection.applyVulkanCoordinateSystem() }) - lightUbo.add("InverseProjectionMatrix", { cam.projection.applyVulkanCoordinateSystem().invert() }) - lightUbo.add("CamPosition", { cam.position }) + lightUbo.add("ViewMatrix0", { camSpatial.getTransformationForEye(0) }) + lightUbo.add("ViewMatrix1", { camSpatial.getTransformationForEye(1) }) + lightUbo.add("InverseViewMatrix0", { camSpatial.getTransformationForEye(0).invert() }) + lightUbo.add("InverseViewMatrix1", { camSpatial.getTransformationForEye(1).invert() }) + lightUbo.add("ProjectionMatrix", { camSpatial.projection.applyVulkanCoordinateSystem() }) + lightUbo.add("InverseProjectionMatrix", { camSpatial.projection.applyVulkanCoordinateSystem().invert() }) + lightUbo.add("CamPosition", { camSpatial.position }) updated = lightUbo.populate() diff --git a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderpass.kt b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderpass.kt index e4a5334c1..72cb99d32 100644 --- a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderpass.kt +++ b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanRenderpass.kt @@ -3,6 +3,7 @@ package graphics.scenery.backends.vulkan import org.joml.Vector3f import graphics.scenery.geometry.GeometryType import graphics.scenery.Node +import graphics.scenery.attribute.renderable.Renderable import graphics.scenery.Settings import graphics.scenery.backends.* import graphics.scenery.utils.LazyLogger @@ -374,9 +375,9 @@ open class VulkanRenderpass(val name: String, var config: RenderConfigReader.Ren } } - fun getDescriptorSetLayoutForTexture(name: String, node: Node): Pair>? { + fun getDescriptorSetLayoutForTexture(name: String, renderable: Renderable): Pair>? { logger.debug("Looking for texture name $name in descriptor specs") - val set = getActivePipeline(node).descriptorSpecs.entries + val set = getActivePipeline(renderable).descriptorSpecs.entries .groupBy { it.value.set } .toSortedMap() .filter { it.value.any { spec -> spec.value.name == name } } @@ -393,10 +394,10 @@ open class VulkanRenderpass(val name: String, var config: RenderConfigReader.Ren /** * Returns the order of shader properties as a map for a given [node] as required by the shader file. */ - fun getShaderPropertyOrder(node: Node): Map { + fun getShaderPropertyOrder(renderable: Renderable): Map { // this creates a shader property UBO for items marked @ShaderProperty in node - logger.debug("specs: ${this.pipelines.getValue("preferred-${node.uuid}").descriptorSpecs}") - val shaderPropertiesSpec = this.pipelines.getValue("preferred-${node.uuid}").descriptorSpecs.filter { it.key == "ShaderProperties" }.map { it.value.members } + logger.debug("specs: ${this.pipelines.getValue("preferred-${renderable.getUuid()}").descriptorSpecs}") + val shaderPropertiesSpec = this.pipelines.getValue("preferred-${renderable.getUuid()}").descriptorSpecs.filter { it.key == "ShaderProperties" }.map { it.value.members } if(shaderPropertiesSpec.count() == 0) { logger.debug("Warning: Shader file uses no declared shader properties, despite the class declaring them.") @@ -648,18 +649,18 @@ open class VulkanRenderpass(val name: String, var config: RenderConfigReader.Ren fun getWritePosition() = commandBufferBacking.currentWritePosition - 1 /** - * Returns the active [VulkanPipeline] for [forNode], if it has a preferred pipeline, + * Returns the active [VulkanPipeline] for [forRenderable], if it has a preferred pipeline, * or the default one if not. */ - fun getActivePipeline(forNode: Node): VulkanPipeline { - return pipelines.getOrDefault("preferred-${forNode.uuid}", getDefaultPipeline()) + fun getActivePipeline(forRenderable: Renderable): VulkanPipeline { + return pipelines.getOrDefault("preferred-${forRenderable.getUuid()}", getDefaultPipeline()) } /** - * Removes any preferred [VulkanPipeline] for the node given in [forNode]. + * Removes any preferred [VulkanPipeline] for the node given in [forRenderable]. */ - fun removePipeline(forNode: Node): Boolean { - return pipelines.remove("preferred-${forNode.uuid}") != null + fun removePipeline(forRenderable: Renderable): Boolean { + return pipelines.remove("preferred-${forRenderable.getUuid()}") != null } /** diff --git a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanScenePass.kt b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanScenePass.kt index 3894b34dc..7e2e0f1a3 100644 --- a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanScenePass.kt +++ b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanScenePass.kt @@ -7,7 +7,9 @@ import graphics.scenery.backends.vulkan.VulkanNodeHelpers.rendererMetadata import graphics.scenery.compute.ComputeMetadata import graphics.scenery.compute.InvocationType import graphics.scenery.geometry.GeometryType -import graphics.scenery.geometry.HasGeometry +import graphics.scenery.attribute.DelegatesProperties +import graphics.scenery.attribute.DelegationType +import graphics.scenery.attribute.renderable.Renderable import graphics.scenery.textures.Texture import graphics.scenery.utils.LazyLogger import graphics.scenery.utils.Statistics @@ -16,7 +18,7 @@ import org.joml.Vector3i import org.lwjgl.system.MemoryStack import org.lwjgl.system.MemoryUtil import org.lwjgl.vulkan.* -import java.util.ArrayList +import java.util.* /** * Helper object for scene pass command buffer recording. @@ -64,36 +66,30 @@ object VulkanScenePass { // e.g. which have the same transparency settings as the pass, // and filter according to any custom filters applicable to this pass // (e.g. to discern geometry from lighting passes) - val seenDelegates = ArrayList(5) + val seenDelegates = ArrayList(5) sceneObjects.filter { customNodeFilter?.invoke(it) ?: true }.forEach { node -> - val n = if(node is DelegatesRendering) { - val delegate = node.delegate - if(node.delegationType == DelegationType.OncePerDelegate && delegate != null) { - if(delegate in seenDelegates) { - return@forEach - } else { - seenDelegates.add(delegate) - delegate - } + val renderable = node.renderableOrNull() ?: return@forEach + val material = node.materialOrNull() ?: return@forEach + if(node is DelegatesProperties && node.getDelegationType() == DelegationType.OncePerDelegate) { + if(seenDelegates.contains(renderable)) { + return@forEach } else { - node.delegate ?: return@forEach + seenDelegates.add(renderable) } - } else { - node } - if(n.state != State.Ready || n.rendererMetadata()?.preDrawSkip == true) { + if(node.state != State.Ready || renderable.rendererMetadata()?.preDrawSkip == true) { return@forEach } - if(n is RenderingOrder) { + if(node is RenderingOrder) { needsOrderSort = true } - n.rendererMetadata()?.let { - if (!((pass.passConfig.renderOpaque && n.material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) || - (pass.passConfig.renderTransparent && !n.material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent))) { - renderOrderList.add(n) + renderable.rendererMetadata()?.let { + if (!((pass.passConfig.renderOpaque && material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) || + (pass.passConfig.renderTransparent && !material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent))) { + renderOrderList.add(node) } else { return@let } @@ -101,7 +97,7 @@ object VulkanScenePass { } if(needsOrderSort) { - renderOrderList.sortBy { (it as? RenderingOrder)?.renderingOrder } + renderOrderList.sortBy { node -> (node.renderableOrNull() as? RenderingOrder)?.renderingOrder } } // if the pass' metadata does not contain a command buffer, // OR the cached command buffer does not contain the same nodes in the same order, @@ -153,14 +149,23 @@ object VulkanScenePass { } } - val computeNodesGraphicsNodes = renderOrderList.partition { pass.getActivePipeline(it).type == VulkanPipeline.PipelineType.Compute } + val computeNodesGraphicsNodes = renderOrderList.partition { + val renderable = it.renderableOrNull() + if(renderable != null) { + pass.getActivePipeline(renderable).type == VulkanPipeline.PipelineType.Compute + } else { + false + } + } computeNodesGraphicsNodes.first.forEach computeLoop@ { node -> - val s = node.rendererMetadata() ?: return@computeLoop + val renderable = node.renderableOrNull() ?: return@computeLoop + val material = node.materialOrNull() ?: return@computeLoop + val s = renderable.rendererMetadata() ?: return@computeLoop val metadata = node.metadata["ComputeMetadata"] as? ComputeMetadata ?: ComputeMetadata(Vector3i(pass.getOutput().width, pass.getOutput().height, 1)) - val pipeline = pass.getActivePipeline(node) + val pipeline = pass.getActivePipeline(renderable) val vulkanPipeline = pipeline.getPipelineForGeometryType(GeometryType.TRIANGLES) if (pass.vulkanMetadata.descriptorSets.capacity() != pipeline.descriptorSpecs.count()) { @@ -198,7 +203,7 @@ object VulkanScenePass { // (0..15).forEach { pass.vulkanMetadata.uboOffsets.put(it, 0) } val loadStoreTextures = - node.material.textures + material.textures .filter { it.value.usageType.contains(Texture.UsageType.LoadStoreImage)} val localSizes = pipeline.shaderStages.first().localSize @@ -280,7 +285,10 @@ object VulkanScenePass { var previousPipeline: VulkanRenderer.Pipeline? = null computeNodesGraphicsNodes.second.forEach drawLoop@ { node -> - val s = node.rendererMetadata() ?: return@drawLoop + val renderable = node.renderableOrNull() ?: return@drawLoop + val material = node.materialOrNull() ?: return@drawLoop + val geometry = node.geometryOrNull() ?: return@drawLoop + val s = renderable.rendererMetadata() ?: return@drawLoop // nodes that just have been initialised will also be skipped if(!s.flags.contains(RendererFlags.Updated)) { @@ -294,12 +302,12 @@ object VulkanScenePass { } // return if we are on a opaque pass, but the node requires transparency. - if(pass.passConfig.renderOpaque && node.material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { + if(pass.passConfig.renderOpaque && material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { return@drawLoop } // return if we are on a transparency pass, but the node is only opaque. - if(pass.passConfig.renderTransparent && !node.material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { + if(pass.passConfig.renderTransparent && !material.blending.transparent && pass.passConfig.renderOpaque != pass.passConfig.renderTransparent) { return@drawLoop } @@ -315,8 +323,8 @@ object VulkanScenePass { // if(rerecordingCauses.contains(node.name)) { // logger.debug("Using pipeline ${pass.getActivePipeline(node)} for re-recording") // } - val p = pass.getActivePipeline(node) - val pipeline = p.getPipelineForGeometryType((node as HasGeometry).geometryType) + val p = pass.getActivePipeline(renderable) + val pipeline = p.getPipelineForGeometryType(geometry.geometryType) val specs = p.orderedDescriptorSpecs() if(pipeline != previousPipeline) { @@ -338,7 +346,7 @@ object VulkanScenePass { pass.vulkanMetadata.vertexBufferOffsets.limit(1) pass.vulkanMetadata.vertexBuffers.limit(1) - if(node.instancedProperties.size > 0) { + if(node is InstancedNode) { if (node.instances.size > 0 && instanceBuffer != null) { pass.vulkanMetadata.vertexBuffers.limit(2) pass.vulkanMetadata.vertexBufferOffsets.limit(2) diff --git a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanUBO.kt b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanUBO.kt index 710464802..a515eef36 100644 --- a/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanUBO.kt +++ b/src/main/kotlin/graphics/scenery/backends/vulkan/VulkanUBO.kt @@ -1,6 +1,6 @@ package graphics.scenery.backends.vulkan -import graphics.scenery.Node +import graphics.scenery.InstancedNode import graphics.scenery.backends.UBO import org.lwjgl.system.MemoryUtil.* import org.lwjgl.vulkan.VK10.* @@ -103,7 +103,7 @@ open class VulkanUBO(val device: VulkanDevice, var backingBuffer: VulkanBuffer? /** * Creates this UBO's members from the instancedProperties of [node]. */ - fun fromInstance(node: Node) { + fun fromInstance(node: InstancedNode.Instance) { node.instancedProperties.forEach { members.putIfAbsent(it.key, it.value) } } diff --git a/src/main/kotlin/graphics/scenery/compute/EdgeBundler.kt b/src/main/kotlin/graphics/scenery/compute/EdgeBundler.kt index 29687edae..8ddfc73b2 100644 --- a/src/main/kotlin/graphics/scenery/compute/EdgeBundler.kt +++ b/src/main/kotlin/graphics/scenery/compute/EdgeBundler.kt @@ -4,6 +4,7 @@ import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.primitives.Line import graphics.scenery.primitives.LinePair +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.LazyLogger import org.jocl.cl_mem import java.io.File @@ -192,8 +193,12 @@ class EdgeBundler(override var hub: Hub?): Hubable { private fun makeLine(trackId: Int): Line { val line = Line(transparent = true, simple = false) line.name = trackId.toString() - line.material.blending.opacity = paramAlpha - line.position = Vector3f(0.0f, 0.0f, 0.0f) + line.material { + blending.opacity = paramAlpha + } + line.spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } line.edgeWidth = 0.01f return line } @@ -207,9 +212,13 @@ class EdgeBundler(override var hub: Hub?): Hubable { private fun makeLinePair(trackId: Int): LinePair { val line = LinePair(transparent = true) line.name = trackId.toString() - line.material.blending.opacity = paramAlpha - line.material.depthTest = Material.DepthTest.Always - line.position = Vector3f(0.0f, 0.0f, 0.0f) + line.material { + blending.opacity = paramAlpha + depthTest = Material.DepthTest.Always + } + line.spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } line.edgeWidth = 0.01f return line } @@ -610,19 +619,21 @@ class EdgeBundler(override var hub: Hub?): Hubable { private fun loadTrajectoriesFromLines(lines: List) { val trackSetTemp: ArrayList> = ArrayList() lines.forEach { line -> - val track = Array(line.vertices.limit() / 3) { _ -> PointWithMeta()} - line.vertices.rewind() - var i = 0 - while(line.vertices.hasRemaining()) { - val point = PointWithMeta(line.vertices.get(), - line.vertices.get(), - line.vertices.get()) - updateMinMax(point) - - track[i] = point - i++ + line.geometry { + val track = Array(vertices.limit() / 3) { _ -> PointWithMeta()} + vertices.rewind() + var i = 0 + while(vertices.hasRemaining()) { + val point = PointWithMeta(vertices.get(), + vertices.get(), + vertices.get()) + updateMinMax(point) + + track[i] = point + i++ + } + trackSetTemp.add(track) } - trackSetTemp.add(track) } this.trackSetBundled = Array(trackSetTemp.size) {i -> trackSetTemp[i]} diff --git a/src/main/kotlin/graphics/scenery/controls/OpenVRHMD.kt b/src/main/kotlin/graphics/scenery/controls/OpenVRHMD.kt index 418c635e4..be24ac973 100644 --- a/src/main/kotlin/graphics/scenery/controls/OpenVRHMD.kt +++ b/src/main/kotlin/graphics/scenery/controls/OpenVRHMD.kt @@ -917,9 +917,13 @@ open class OpenVRHMD(val seated: Boolean = false, val useCompositor: Boolean = t mesh.readFrom(path) if (type == TrackedDeviceType.Controller) { - mesh.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) + mesh.ifMaterial { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + } mesh.children.forEach { c -> - c.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) + c.ifMaterial { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + } } } } @@ -1035,17 +1039,19 @@ open class OpenVRHMD(val seated: Boolean = false, val useCompositor: Boolean = t this.getPose(TrackedDeviceType.Controller).firstOrNull { it.name == device.name }?.let { controller -> node.metadata["TrackedDevice"] = controller - node.wantsComposeModel = false - node.model.identity() - camera?.let { - node.model.translate(it.position) - } - node.model.mul(controller.pose) + node.ifSpatial { + wantsComposeModel = false + model.identity() + camera?.let { + model.translate(it.spatial().position) + } + model.mul(controller.pose) // logger.info("Updating pose of $controller, ${node.model}") - node.needsUpdate = false - node.needsUpdateWorld = true + needsUpdate = false + needsUpdateWorld = true + } } } } diff --git a/src/main/kotlin/graphics/scenery/controls/ScreenConfig.kt b/src/main/kotlin/graphics/scenery/controls/ScreenConfig.kt index f5beeebe0..34febc8c7 100644 --- a/src/main/kotlin/graphics/scenery/controls/ScreenConfig.kt +++ b/src/main/kotlin/graphics/scenery/controls/ScreenConfig.kt @@ -144,13 +144,13 @@ class ScreenConfig { val mapper = ObjectMapper(YAMLFactory()) mapper.registerModule(KotlinModule()) - var stream = this::class.java.getResourceAsStream(path) + var stream = ScreenConfig::class.java.getResourceAsStream(path) if (stream == null) { val p = Paths.get(path) return if (!Files.exists(p)) { - stream = this::class.java.getResourceAsStream("CAVEExample.yml") + stream = ScreenConfig::class.java.getResourceAsStream("CAVEExample.yml") logger.warn("Screen configuration not found at $path, returning default configuration.") mapper.readValue(stream, ScreenConfig.Config::class.java) } else { diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/ArcballCameraControl.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/ArcballCameraControl.kt index 9b7250f97..6e80332f8 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/ArcballCameraControl.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/ArcballCameraControl.kt @@ -39,7 +39,7 @@ open class ArcballCameraControl(private val name: String, camera: () -> Camera?, set(value) { field = value - cam?.let { node -> node.position = target.invoke() + node.forward * value * (-1.0f) } + cam?.let { node -> node.spatialOrNull()?.position = target.invoke() + node.forward * value * (-1.0f) } } /** multiplier for zooming in and out */ @@ -121,10 +121,12 @@ open class ArcballCameraControl(private val name: String, camera: () -> Camera?, val yawQ = Quaternionf().rotateXYZ(0.0f, frameYaw, 0.0f).normalize() val pitchQ = Quaternionf().rotateXYZ(framePitch, 0.0f, 0.0f).normalize() - distance = (target.invoke() - node.position).length() - node.target = target.invoke() - node.rotation = pitchQ.mul(node.rotation).mul(yawQ).normalize() - node.position = target.invoke() + node.forward * distance * (-1.0f) + node.ifSpatial { + distance = (target.invoke() - position).length() + node.target = target.invoke() + rotation = pitchQ.mul(rotation).mul(yawQ).normalize() + position = target.invoke() + node.forward * distance * (-1.0f) + } node.lock.unlock() } @@ -145,13 +147,13 @@ open class ArcballCameraControl(private val name: String, camera: () -> Camera?, return } - distance = (target.invoke() - cam!!.position).length() + distance = (target.invoke() - cam!!.spatial().position).length() distance += wheelRotation.toFloat() * scrollSpeedMultiplier if (distance >= maximumDistance) distance = maximumDistance if (distance <= minimumDistance) distance = minimumDistance - cam?.let { node -> node.position = target.invoke() + node.forward * distance * (-1.0f) } + cam?.let { node -> node.spatialOrNull()?.position = target.invoke() + node.forward * distance * (-1.0f) } } } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/ControllerDrag.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/ControllerDrag.kt index 4a7de8fcf..05ad49b69 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/ControllerDrag.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/ControllerDrag.kt @@ -51,12 +51,14 @@ open class ControllerDrag(val handedness: TrackerRole, if(last != null) { val node = draggedObjectFinder.invoke() ?: return - if(trackPosition) { - node.position = node.position + (current - last) - } + node.ifSpatial { + if(trackPosition) { + position += current - last + } - if(trackRotation) { - node.rotation = currentRotation + if(trackRotation) { + rotation = currentRotation + } } logger.debug("Node ${node.name} moved with $current - $last!") diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/FPSCameraControl.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/FPSCameraControl.kt index 05c4726d3..6dde74207 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/FPSCameraControl.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/FPSCameraControl.kt @@ -82,18 +82,22 @@ open class FPSCameraControl(private val name: String, private val n: () -> Camer return } - var xoffset: Float = (x - lastX).toFloat() * mouseSpeedMultiplier - var yoffset: Float = (y - lastY).toFloat() * mouseSpeedMultiplier + node?.ifSpatial { - lastX = x - lastY = y + var xoffset: Float = (x - lastX).toFloat() * mouseSpeedMultiplier + var yoffset: Float = (y - lastY).toFloat() * mouseSpeedMultiplier + + lastX = x + lastY = y - val frameYaw = xoffset - val framePitch = yoffset + val frameYaw = xoffset + val framePitch = yoffset + + val yawQ = Quaternionf().rotateXYZ(0.0f, frameYaw/180.0f*Math.PI.toFloat(), 0.0f) + val pitchQ = Quaternionf().rotateXYZ(framePitch/180.0f*Math.PI.toFloat(), 0.0f, 0.0f) + rotation = pitchQ.mul(rotation).mul(yawQ).normalize() + } - val yawQ = Quaternionf().rotateXYZ(0.0f, frameYaw/180.0f*Math.PI.toFloat(), 0.0f) - val pitchQ = Quaternionf().rotateXYZ(framePitch/180.0f*Math.PI.toFloat(), 0.0f, 0.0f) - node?.rotation = pitchQ.mul(node?.rotation).mul(yawQ).normalize() node?.lock?.unlock() } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadCameraControl.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadCameraControl.kt index 14a8b25ad..5d2b9d85d 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadCameraControl.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadCameraControl.kt @@ -110,9 +110,10 @@ open class GamepadCameraControl(private val name: String, // node?.forward = forward.normalized logger.trace("Pitch={} Yaw={}", pitch, yaw) - val yawQ = Quaternionf().rotateXYZ(0.0f, yaw, 0.0f) - val pitchQ = Quaternionf().rotateXYZ(pitch, 0.0f, 0.0f) - - n.rotation = pitchQ.mul(n.rotation).mul(yawQ).normalize() + n.ifSpatial { + val yawQ = Quaternionf().rotateXYZ(0.0f, yaw, 0.0f) + val pitchQ = Quaternionf().rotateXYZ(pitch, 0.0f, 0.0f) + rotation = pitchQ.mul(rotation).mul(yawQ).normalize() + } } } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadMovementControl.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadMovementControl.kt index 2ca255139..abc2c9576 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadMovementControl.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/GamepadMovementControl.kt @@ -19,17 +19,17 @@ import kotlin.reflect.KProperty */ open class GamepadMovementControl(private val name: String, override val axis: List, - private val camera: () -> Node?) : GamepadBehaviour { + private val camera: () -> Camera?) : GamepadBehaviour { /** Speed multiplier for camera movement */ var speedMultiplier = 0.04f /** Threshold below which the behaviour does not trigger */ var threshold = 0.05f - private val cam: Node? by NodeDelegate() + private val cam: Camera? by CameraDelegate() - protected inner class NodeDelegate { + protected inner class CameraDelegate { /** Returns the [graphics.scenery.Camera] resulting from the evaluation of [camera] */ - operator fun getValue(thisRef: Any?, property: KProperty<*>): Node? { + operator fun getValue(thisRef: Any?, property: KProperty<*>): Camera? { return camera.invoke() } @@ -54,28 +54,30 @@ open class GamepadMovementControl(private val name: String, return } - if(cam is Camera) { - when (axis) { - Component.Identifier.Axis.Y -> { - cam.position = cam.position + cam.forward * -1.0f * value * speedMultiplier + cam.spatial { + if(cam is Camera) { + when (axis) { + Component.Identifier.Axis.Y -> { + position += cam.forward * -1.0f * value * speedMultiplier + } + Component.Identifier.Axis.X -> { + position += Vector3f(cam.forward).cross(cam.up).normalize() * value * speedMultiplier + } + Component.Identifier.Axis.Z -> { + position += cam.up * value * speedMultiplier + } } - Component.Identifier.Axis.X -> { - cam.position = cam.position + Vector3f(cam.forward).cross(cam.up).normalize() * value * speedMultiplier - } - Component.Identifier.Axis.Z -> { - cam.position = cam.position + cam.up * value * speedMultiplier - } - } - } else { - when (axis) { - Component.Identifier.Axis.Y -> { - cam.position = cam.position + Vector3f(0.0f, 0.0f, -1.0f) * -1.0f * value * speedMultiplier - } - Component.Identifier.Axis.X -> { - cam.position = cam.position + Vector3f(1.0f, 0.0f, 0.0f) * value * speedMultiplier - } - Component.Identifier.Axis.Z -> { - cam.position = cam.position + Vector3f(0.0f, 1.0f, 0.0f) * value * speedMultiplier + } else { + when (axis) { + Component.Identifier.Axis.Y -> { + position += Vector3f(0.0f, 0.0f, -1.0f) * -1.0f * value * speedMultiplier + } + Component.Identifier.Axis.X -> { + position += Vector3f(1.0f, 0.0f, 0.0f) * value * speedMultiplier + } + Component.Identifier.Axis.Z -> { + position += Vector3f(0.0f, 1.0f, 0.0f) * value * speedMultiplier + } } } } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/MeshAdder.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/MeshAdder.kt index c8f384018..8584321a9 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/MeshAdder.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/MeshAdder.kt @@ -51,10 +51,10 @@ open class MeshAdder constructor(protected val name: String, val posX = (x - width / 2.0f) / (width / 2.0f) val posY = -1.0f * (y - height / 2.0f) / (height / 2.0f) mesh.parent = scene - val mousePosition = cam!!.viewportToView(Vector2f(posX, posY)) - val position4D = cam!!.viewToWorld(mousePosition) + val mousePosition = cam!!.spatial().viewportToView(Vector2f(posX, posY)) + val position4D = cam!!.spatial().viewToWorld(mousePosition) val position = Vector3f(position4D.x(), position4D.y(), position4D.z()) - mesh.position = position + mesh.spatial().position = position scene.addChild(mesh) } } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragPlane.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragPlane.kt index 0f982dd0e..bd803fc0d 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragPlane.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragPlane.kt @@ -87,11 +87,13 @@ open class MouseDragPlane( cam?.let { if (targetedNode == null || !targetedNode.lock.tryLock()) return - it.right.mul((x - lastX) * fpsSpeedSlow() * mouseSpeed(), dragPosUpdater) - targetedNode.position.add(dragPosUpdater) - it.up.mul((lastY - y) * fpsSpeedSlow() * mouseSpeed(), dragPosUpdater) - targetedNode.position.add(dragPosUpdater) - targetedNode.needsUpdate = true + targetedNode.ifSpatial { + it.right.mul((x - lastX) * fpsSpeedSlow() * mouseSpeed(), dragPosUpdater) + position.add(dragPosUpdater) + it.up.mul((lastY - y) * fpsSpeedSlow() * mouseSpeed(), dragPosUpdater) + position.add(dragPosUpdater) + needsUpdate = true + } targetedNode.lock.unlock() @@ -114,8 +116,10 @@ open class MouseDragPlane( wheelRotation.toFloat() * fpsSpeedSlow() * mouseSpeed(), scrollPosUpdater ) - targetedNode.position.add(scrollPosUpdater) - targetedNode.needsUpdate = true + targetedNode.ifSpatial { + position.add(scrollPosUpdater) + needsUpdate = true + } targetedNode.lock.unlock() } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragSphere.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragSphere.kt index 961ed3073..1f205cc36 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragSphere.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/MouseDragSphere.kt @@ -53,10 +53,12 @@ open class MouseDragSphere( val movement = newHit - currentHit - val newPos = it.position + movement / it.worldScale() + it.ifSpatial { + val newPos = position + movement / worldScale() - currentNode?.position = newPos - currentHit = newHit + currentNode?.spatialOrNull()?.position = newPos + currentHit = newHit + } } } } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/MouseRotate.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/MouseRotate.kt index 65d366276..bd37800d0 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/MouseRotate.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/MouseRotate.kt @@ -60,13 +60,15 @@ open class MouseRotate( val frameYaw = mouseSpeed() * (x - lastX) * 0.0174533f // 0.017 = PI/180 val framePitch = mouseSpeed() * (y - lastY) * 0.0174533f - Quaternionf().rotateAxis(frameYaw, it.up) - .mul(targetedNode.rotation, targetedNode.rotation) - .normalize() - Quaternionf().rotateAxis(framePitch, it.right) - .mul(targetedNode.rotation, targetedNode.rotation) - .normalize() - targetedNode.needsUpdate = true + targetedNode.ifSpatial { + Quaternionf().rotateAxis(frameYaw, it.up) + .mul(rotation, rotation) + .normalize() + Quaternionf().rotateAxis(framePitch, it.right) + .mul(rotation, rotation) + .normalize() + needsUpdate = true + } targetedNode.lock.unlock() diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/MovementCommand.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/MovementCommand.kt index 66230c32f..0bb0dbd84 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/MovementCommand.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/MovementCommand.kt @@ -61,14 +61,16 @@ open class MovementCommand(private val name: String, private val direction: Stri val axisProvider = node as? Camera ?: node?.getScene()?.findObserver() ?: return node?.let { node -> - if (node.lock.tryLock()) { - when (direction) { - "forward" -> node.position = node.position + axisProvider.forward * speed * axisProvider.deltaT - "back" -> node.position = node.position - axisProvider.forward * speed * axisProvider.deltaT - "left" -> node.position = node.position - axisProvider.right * speed * axisProvider.deltaT - "right" -> node.position = node.position + axisProvider.right * speed * axisProvider.deltaT - "up" -> node.position = node.position + axisProvider.up * speed * axisProvider.deltaT - "down" -> node.position = node.position - axisProvider.up * speed * axisProvider.deltaT + if (node.lock.tryLock() != false) { + node.ifSpatial { + when (direction) { + "forward" -> position += axisProvider.forward * speed * axisProvider.deltaT + "back" -> position -= axisProvider.forward * speed * axisProvider.deltaT + "left" -> position -= axisProvider.right * speed * axisProvider.deltaT + "right" -> position += axisProvider.right * speed * axisProvider.deltaT + "up" -> position += axisProvider.up * speed * axisProvider.deltaT + "down" -> position -= axisProvider.up * speed * axisProvider.deltaT + } } node.lock.unlock() diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/RollingBallCameraControl.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/RollingBallCameraControl.kt index fd38b6875..4ba7dc34c 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/RollingBallCameraControl.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/RollingBallCameraControl.kt @@ -10,7 +10,6 @@ import org.joml.* import org.scijava.ui.behaviour.DragBehaviour import org.scijava.ui.behaviour.ScrollBehaviour import java.util.function.Supplier -import kotlin.reflect.KProperty /** * Targeted ArcBall control @@ -41,7 +40,7 @@ open class RollingBallCameraControl(private val name: String, camera: () -> Came set(value) { field = value - cam?.let { node -> node.position = target.invoke() + node.forward * value * (-1.0f) } + cam?.let { node -> node.spatial().position = target.invoke() + node.forward * value * (-1.0f) } } /** multiplier for zooming in and out */ @@ -114,61 +113,65 @@ open class RollingBallCameraControl(private val name: String, camera: () -> Came return } - val R = 1.0f - val dx: Float = (x - lastX).toFloat()/w - val dy: Float = (lastY - y).toFloat()/h - - lastX = x - lastY = y - - val dr = sqrt(dx*dx + dy*dy) - val cosTheta = R/sqrt(R*R + dr*dr) - val sinTheta = dr/sqrt(R*R + dr*dr) - - val m = Matrix3f( - cosTheta + (dx/dr)*(dx/dr)*(1.0f - cosTheta), -(dx/dr)*(dy/dr)*(1.0f-cosTheta), (dx/dr)*sinTheta, - -(dx/dr)*(dy/dr)*(1-cosTheta), cosTheta + (dx/dr)*(dx/dr)*(1-cosTheta), (dy/dr) * sinTheta, - -(dx/dr)*sinTheta, -(dy/dr)*sinTheta, cosTheta - ) - - val tr = m.m00 + m.m11 + m.m22 - - val q = with(m) { - if (tr > 0) { - val s = sqrt(tr + 1.0f) * 2; // S=4*qw - Quaternionf( - (m21 - m12) / s, - (m02 - m20) / s, - (m10 - m01) / s, - 0.25f * s) - } else if ((m.m00 > m.m11) && (m00 > m22)) { - val s = sqrt (1.0f + m00 - m11 - m22) * 2; // S=4*qx - Quaternionf( - 0.25f * s, - (m01 + m10) / s, - (m02 + m20) / s, - (m21 - m12) / s) - } else if (m11 > m22) { - val s = sqrt (1.0f + m11 - m00 - m22) * 2; // S=4*qy - Quaternionf( - (m01 + m10) / s, - 0.25f * s, - (m12 + m21) / s, - (m02 - m20) / s) - } else { - val s = sqrt (1.0f + m22 - m00 - m11) * 2; // S=4*qz - Quaternionf( - (m02 + m20) / s, - (m12 + m21) / s, - 0.25f * s, - (m10 - m01) / s) + node.ifSpatial { + + val R = 1.0f + val dx: Float = (x - lastX).toFloat()/w + val dy: Float = (lastY - y).toFloat()/h + + lastX = x + lastY = y + + val dr = sqrt(dx*dx + dy*dy) + val cosTheta = R/sqrt(R*R + dr*dr) + val sinTheta = dr/sqrt(R*R + dr*dr) + + val m = Matrix3f( + cosTheta + (dx/dr)*(dx/dr)*(1.0f - cosTheta), -(dx/dr)*(dy/dr)*(1.0f-cosTheta), (dx/dr)*sinTheta, + -(dx/dr)*(dy/dr)*(1-cosTheta), cosTheta + (dx/dr)*(dx/dr)*(1-cosTheta), (dy/dr) * sinTheta, + -(dx/dr)*sinTheta, -(dy/dr)*sinTheta, cosTheta + ) + + val tr = m.m00 + m.m11 + m.m22 + + val q = with(m) { + if (tr > 0) { + val s = sqrt(tr + 1.0f) * 2; // S=4*qw + Quaternionf( + (m21 - m12) / s, + (m02 - m20) / s, + (m10 - m01) / s, + 0.25f * s) + } else if ((m.m00 > m.m11) && (m00 > m22)) { + val s = sqrt (1.0f + m00 - m11 - m22) * 2; // S=4*qx + Quaternionf( + 0.25f * s, + (m01 + m10) / s, + (m02 + m20) / s, + (m21 - m12) / s) + } else if (m11 > m22) { + val s = sqrt (1.0f + m11 - m00 - m22) * 2; // S=4*qy + Quaternionf( + (m01 + m10) / s, + 0.25f * s, + (m12 + m21) / s, + (m02 - m20) / s) + } else { + val s = sqrt (1.0f + m22 - m00 - m11) * 2; // S=4*qz + Quaternionf( + (m02 + m20) / s, + (m12 + m21) / s, + 0.25f * s, + (m10 - m01) / s) + } } + + distance = (target.invoke() - position).length() + node.target = target.invoke() + rotation = q.mul(rotation) + position = target.invoke() + node.forward * distance * (-1.0f) } - distance = (target.invoke() - node.position).length() - node.target = target.invoke() - node.rotation = q.mul(node.rotation) - node.position = target.invoke() + node.forward * distance * (-1.0f) node.lock.unlock() } @@ -194,7 +197,7 @@ open class RollingBallCameraControl(private val name: String, camera: () -> Came if (distance >= maximumDistance) distance = maximumDistance if (distance <= minimumDistance) distance = minimumDistance - cam?.let { node -> node.position = target.invoke() + node.forward * distance * (-1.0f) } + cam?.let { node -> node.spatialOrNull()?.position = target.invoke() + node.forward * distance * (-1.0f) } } } diff --git a/src/main/kotlin/graphics/scenery/controls/behaviours/Ruler.kt b/src/main/kotlin/graphics/scenery/controls/behaviours/Ruler.kt index 22ce98fd7..0d4707a21 100644 --- a/src/main/kotlin/graphics/scenery/controls/behaviours/Ruler.kt +++ b/src/main/kotlin/graphics/scenery/controls/behaviours/Ruler.kt @@ -58,8 +58,10 @@ class Ruler(private val name: String, private val camera: () -> Camera?, private board.backgroundColor = Vector4f(100f, 100f, 100f, 1.0f) val boardPosition = Vector3f() origin.add(endPosition, boardPosition).mul(0.5f) - board.position = boardPosition.mul(0.5f) - board.scale = Vector3f(0.5f, 0.5f, 0.5f) + board.spatial { + position = boardPosition.mul(0.5f) + scale = Vector3f(0.5f, 0.5f, 0.5f) + } scene.addChild(board) } @@ -69,8 +71,8 @@ class Ruler(private val name: String, private val camera: () -> Camera?, private val height = cam.height val posX = (p0 - width / 2.0f) / (width / 2.0f) val posY = -1.0f * (p1 - height / 2.0f) / (height / 2.0f) - val mousePosition = cam.viewportToView(Vector2f(posX, posY)) - val position4D = cam.viewToWorld(mousePosition) + val mousePosition = cam.spatial().viewportToView(Vector2f(posX, posY)) + val position4D = cam.spatial().viewToWorld(mousePosition) return Vector3f(position4D.x(), position4D.y(), position4D.z()) } } diff --git a/src/main/kotlin/graphics/scenery/controls/eyetracking/CircleScreenSpaceCalibrationPointGenerator.kt b/src/main/kotlin/graphics/scenery/controls/eyetracking/CircleScreenSpaceCalibrationPointGenerator.kt index 45d23157c..fbeab8cf1 100644 --- a/src/main/kotlin/graphics/scenery/controls/eyetracking/CircleScreenSpaceCalibrationPointGenerator.kt +++ b/src/main/kotlin/graphics/scenery/controls/eyetracking/CircleScreenSpaceCalibrationPointGenerator.kt @@ -29,6 +29,6 @@ class CircleScreenSpaceCalibrationPointGenerator : CalibrationPointGenerator { origin + radius * sin(2 * PI.toFloat() * index.toFloat()/totalPointCount), 0.0f) } - return CalibrationPointGenerator.CalibrationPoint(v, cam.viewportToWorld(Vector2f(v.x()*2.0f-1.0f, v.y()*2.0f-1.0f))) + return CalibrationPointGenerator.CalibrationPoint(v, cam.spatial().viewportToWorld(Vector2f(v.x()*2.0f-1.0f, v.y()*2.0f-1.0f))) } } diff --git a/src/main/kotlin/graphics/scenery/controls/eyetracking/EquidistributedScreenSpaceCalibrationPointGenerator.kt b/src/main/kotlin/graphics/scenery/controls/eyetracking/EquidistributedScreenSpaceCalibrationPointGenerator.kt index 651e47e8b..59c6f08c5 100644 --- a/src/main/kotlin/graphics/scenery/controls/eyetracking/EquidistributedScreenSpaceCalibrationPointGenerator.kt +++ b/src/main/kotlin/graphics/scenery/controls/eyetracking/EquidistributedScreenSpaceCalibrationPointGenerator.kt @@ -33,6 +33,6 @@ class EquidistributedScreenSpaceCalibrationPointGenerator : CalibrationPointGene 0.5f + 0.3f * points[index % (points.size - 1)].y(), cam.nearPlaneDistance + 0.5f) - return CalibrationPointGenerator.CalibrationPoint(v, cam.viewportToWorld(Vector2f(v.x() * 2.0f - 1.0f, v.y() * 2.0f - 1.0f))) + return CalibrationPointGenerator.CalibrationPoint(v, cam.spatial().viewportToWorld(Vector2f(v.x() * 2.0f - 1.0f, v.y() * 2.0f - 1.0f))) } } diff --git a/src/main/kotlin/graphics/scenery/controls/eyetracking/PupilEyeTracker.kt b/src/main/kotlin/graphics/scenery/controls/eyetracking/PupilEyeTracker.kt index 8c93e7c02..274746d06 100644 --- a/src/main/kotlin/graphics/scenery/controls/eyetracking/PupilEyeTracker.kt +++ b/src/main/kotlin/graphics/scenery/controls/eyetracking/PupilEyeTracker.kt @@ -462,11 +462,11 @@ class PupilEyeTracker(val calibrationType: CalibrationType, val host: String = " val position = Vector3f(normalizedScreenPos.world) val calibrationPosition = if(calibrationType == CalibrationType.ScreenSpace) { - calibrationTarget?.position = position + cam.forward * 0.15f + calibrationTarget?.spatialOrNull()?.position = position + cam.forward * 0.15f val l = normalizedScreenPos.local floatArrayOf(l.x, l.y, l.z) } else { - calibrationTarget?.position = position + calibrationTarget?.spatialOrNull()?.position = position val p = Vector3f(normalizedScreenPos.local) * pupilToSceneryRatio p.x = p.get(0) * 1.0f p.y = p.get(1) * -1.0f * cam.aspectRatio() @@ -474,10 +474,12 @@ class PupilEyeTracker(val calibrationType: CalibrationType, val host: String = " floatArrayOf(p.x, p.y, p.z) } - if(normalizedScreenPos.local.x() == 0.5f && normalizedScreenPos.local.y() == 0.5f) { - calibrationTarget?.material?.diffuse = Vector3f(1.0f, 1.0f, 0.0f) - } else { - calibrationTarget?.material?.diffuse = Vector3f(1.0f, 1.0f, 1.0f) + calibrationTarget?.ifMaterial { + if(normalizedScreenPos.local.x() == 0.5f && normalizedScreenPos.local.y() == 0.5f) { + diffuse = Vector3f(1.0f, 1.0f, 0.0f) + } else { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + } } (0 until samplesPerPoint).forEach { diff --git a/src/main/kotlin/graphics/scenery/effectors/LineRestrictionEffector.kt b/src/main/kotlin/graphics/scenery/effectors/LineRestrictionEffector.kt index 741168abf..9ecf868cf 100644 --- a/src/main/kotlin/graphics/scenery/effectors/LineRestrictionEffector.kt +++ b/src/main/kotlin/graphics/scenery/effectors/LineRestrictionEffector.kt @@ -21,28 +21,29 @@ open class LineRestrictionEffector(val target: Node, var from: () -> Vector3f, v if (!active){ return@add } - val it = target - val a = from() - val b = to() + target.ifSpatial { + val a = from() + val b = to() - val p = it.position + val p = position - val ab = b - a - val ap = p - a + val ab = b - a + val ap = p - a - val dot = ap.dot(ab) + val dot = ap.dot(ab) - if (dot <= 0) { - it.position = a - return@add - } + if (dot <= 0) { + position = a + return@ifSpatial + } - val pDotDir = ab * (dot / ab.lengthSquared()) + val pDotDir = ab * (dot / ab.lengthSquared()) - if (pDotDir.lengthSquared() > ab.lengthSquared()) { - it.position = b - } else { - it.position = a + pDotDir + if (pDotDir.lengthSquared() > ab.lengthSquared()) { + position = b + } else { + position = a + pDotDir + } } } } diff --git a/src/main/kotlin/graphics/scenery/effectors/VolumeEffector.kt b/src/main/kotlin/graphics/scenery/effectors/VolumeEffector.kt index 26d44270d..618aeb35a 100644 --- a/src/main/kotlin/graphics/scenery/effectors/VolumeEffector.kt +++ b/src/main/kotlin/graphics/scenery/effectors/VolumeEffector.kt @@ -1,6 +1,9 @@ package graphics.scenery.effectors -import graphics.scenery.Node +import graphics.scenery.* +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.spatial.HasSpatial import graphics.scenery.volumes.Volume /** @@ -8,7 +11,7 @@ import graphics.scenery.volumes.Volume * * @author Ulrik Guenther */ -open class VolumeEffector : Node("VolumeEffector") { +open class VolumeEffector : DefaultNode("VolumeEffector"), HasRenderable, HasMaterial, HasSpatial { /** Whether this effector node is currently active */ var active: Boolean = false private set @@ -18,13 +21,16 @@ open class VolumeEffector : Node("VolumeEffector") { private set /** Proxy node to display e.g. auxiliary geometry. */ - open var proxy = Node() + open var proxy : Node = DefaultNode("proxy") init { + addRenderable() + addMaterial() + addSpatial() update.add { getScene()?.let { it.discover(it, { it is Volume }).forEach { node -> - if (proxy.intersects(node)) { + if (proxy.spatialOrNull()?.intersects(node) == true) { activeVolume = node } } diff --git a/src/main/kotlin/graphics/scenery/fonts/SDFFontAtlas.kt b/src/main/kotlin/graphics/scenery/fonts/SDFFontAtlas.kt index 856270408..6a545e250 100644 --- a/src/main/kotlin/graphics/scenery/fonts/SDFFontAtlas.kt +++ b/src/main/kotlin/graphics/scenery/fonts/SDFFontAtlas.kt @@ -366,56 +366,58 @@ open class SDFFontAtlas(var hub: Hub, val fontName: String, val distanceFieldSiz @Suppress("UNCHECKED_CAST") fun createMeshForString(text: String): Mesh { val m = Mesh() - m.geometryType = GeometryType.TRIANGLES + m.geometry { + geometryType = GeometryType.TRIANGLES - val vertices = ArrayList() - val normals = ArrayList() - val texcoords = ArrayList() - val indices = ArrayList() + val vertices = ArrayList() + val normals = ArrayList() + val texcoords = ArrayList() + val indices = ArrayList() - var basex = 0.0f - var basei = 0 + var basex = 0.0f + var basei = 0 - text.toCharArray().forEachIndexed { _, char -> - val glyphWidth = fontMap[char]!!.first + text.toCharArray().forEachIndexed { _, char -> + val glyphWidth = fontMap[char]!!.first - vertices.addAll(listOf( + vertices.addAll(listOf( basex + 0.0f, 0.0f, 0.0f, basex + glyphWidth, 0.0f, 0.0f, basex + glyphWidth, 1.0f, 0.0f, basex + 0.0f, 1.0f, 0.0f - )) + )) - normals.addAll(listOf( + normals.addAll(listOf( 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f - )) + )) - indices.addAll(listOf( + indices.addAll(listOf( basei + 0, basei + 1, basei + 2, basei + 0, basei + 2, basei + 3 - )) + )) - val glyphTexCoords = getTexcoordsForGlyph(char) + val glyphTexCoords = getTexcoordsForGlyph(char) - texcoords.addAll(listOf( + texcoords.addAll(listOf( glyphTexCoords.x(), glyphTexCoords.w(), glyphTexCoords.z(), glyphTexCoords.w(), glyphTexCoords.z(), glyphTexCoords.y(), glyphTexCoords.x(), glyphTexCoords.y() - )) + )) - // add font width as new base size - basex += glyphWidth - basei += 4 - } + // add font width as new base size + basex += glyphWidth + basei += 4 + } - m.vertices = BufferUtils.allocateFloatAndPut(vertices.toFloatArray()) - m.normals = BufferUtils.allocateFloatAndPut(normals.toFloatArray()) - m.texcoords = BufferUtils.allocateFloatAndPut(texcoords.toFloatArray()) - m.indices = BufferUtils.allocateIntAndPut(indices.toIntArray()) + this.vertices = BufferUtils.allocateFloatAndPut(vertices.toFloatArray()) + this.normals = BufferUtils.allocateFloatAndPut(normals.toFloatArray()) + this.texcoords = BufferUtils.allocateFloatAndPut(texcoords.toFloatArray()) + this.indices = BufferUtils.allocateIntAndPut(indices.toIntArray()) + } return m } diff --git a/src/main/kotlin/graphics/scenery/geometry/Curve.kt b/src/main/kotlin/graphics/scenery/geometry/Curve.kt index 33cc73a75..6a133f68d 100644 --- a/src/main/kotlin/graphics/scenery/geometry/Curve.kt +++ b/src/main/kotlin/graphics/scenery/geometry/Curve.kt @@ -21,7 +21,7 @@ import kotlin.math.acos * @param [firstPerpendicularVector] vector to which the first frenet tangent shall be perpendicular to. */ class Curve(spline: Spline, private val firstPerpendicularVector: Vector3f = Vector3f(0f, 0f, 0f), - baseShape: () -> List>): Mesh("CurveGeometry"), HasGeometry { + baseShape: () -> List>): Mesh("CurveGeometry") { private val chain = spline.splinePoints() private val sectionVertices = spline.verticesCountPerSection() private val countList = ArrayList(50).toMutableList() @@ -308,15 +308,17 @@ class Curve(spline: Spline, private val firstPerpendicularVector: Vector3f = Vec * Each children of the curve must be, per definition, another Mesh. Therefore this class turns a List of * vertices into a Mesh. */ - class PartialCurve(verticesVectors: ArrayList) : Mesh("PartialCurve"), HasGeometry { + class PartialCurve(verticesVectors: ArrayList) : Mesh("PartialCurve") { init { - vertices = BufferUtils.allocateFloat(verticesVectors.size * 3) - verticesVectors.forEach { - vertices.put(it.toFloatArray()) + geometry { + vertices = BufferUtils.allocateFloat(verticesVectors.size * 3) + verticesVectors.forEach { + vertices.put(it.toFloatArray()) + } + vertices.flip() + texcoords = BufferUtils.allocateFloat(verticesVectors.size * 2) + recalculateNormals() } - vertices.flip() - texcoords = BufferUtils.allocateFloat(verticesVectors.size * 2) - recalculateNormals() } } diff --git a/src/main/kotlin/graphics/scenery/net/NodeSubscriber.kt b/src/main/kotlin/graphics/scenery/net/NodeSubscriber.kt index 5d3f8b84d..53e7c33b1 100644 --- a/src/main/kotlin/graphics/scenery/net/NodeSubscriber.kt +++ b/src/main/kotlin/graphics/scenery/net/NodeSubscriber.kt @@ -1,7 +1,5 @@ package graphics.scenery.net -import org.joml.Matrix4f -import org.joml.Vector3f import com.esotericsoftware.kryo.Kryo import com.esotericsoftware.kryo.io.Input import com.jogamp.opengl.math.Quaternion @@ -10,13 +8,14 @@ import graphics.scenery.geometry.GeometryType import graphics.scenery.primitives.Arrow import graphics.scenery.primitives.Cylinder import graphics.scenery.primitives.Line -import graphics.scenery.Mesh import graphics.scenery.proteins.Protein import graphics.scenery.proteins.RibbonDiagram import graphics.scenery.utils.LazyLogger import graphics.scenery.utils.Statistics import graphics.scenery.volumes.TransferFunction import graphics.scenery.volumes.Volume +import org.joml.Matrix4f +import org.joml.Vector3f import org.objenesis.strategy.StdInstantiatorStrategy import org.zeromq.ZContext import org.zeromq.ZMQ @@ -86,12 +85,17 @@ class NodeSubscriber(override var hub: Hub?, val address: String = "udp://localh val input = Input(bin) val o = kryo.readClassAndObject(input) as? Node ?: return@let - node.position = o.position - node.rotation = o.rotation - node.scale = o.scale + val oSpatial = o.spatialOrNull() + if (oSpatial != null) { + node.ifSpatial { + position = oSpatial.position + rotation = oSpatial.rotation + scale = oSpatial.scale + } + } node.visible = o.visible - if (o is Volume && node is Volume && node.initialized) { + if (o is Volume && node is Volume && node.initialized == true) { TODO("Reimplement changes for synchronising volumes") } diff --git a/src/main/kotlin/graphics/scenery/primitives/Arrow.kt b/src/main/kotlin/graphics/scenery/primitives/Arrow.kt index 51f101d2c..686a1957b 100644 --- a/src/main/kotlin/graphics/scenery/primitives/Arrow.kt +++ b/src/main/kotlin/graphics/scenery/primitives/Arrow.kt @@ -3,6 +3,8 @@ package graphics.scenery.primitives import graphics.scenery.* import graphics.scenery.backends.ShaderType import graphics.scenery.geometry.GeometryType +import graphics.scenery.attribute.geometry.Geometry +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.minus import graphics.scenery.utils.extensions.plus import graphics.scenery.utils.extensions.times @@ -59,22 +61,26 @@ class Arrow(var vector: Vector3f = Vector3f(0.0f)) : Mesh("Arrow") { private val zeroGLvec = Vector3f(0.0f, 0.0f, 0.0f) init { - /** Geometry type -- Default for Line is [GeometryType.LINE] */ - geometryType = GeometryType.LINE_STRIP_ADJACENCY - /** Vertex buffer */ - vertices = BufferUtils.allocateFloat(30) - /** Normal buffer */ - normals = BufferUtils.allocateFloat(30) - /** Texcoord buffer */ - texcoords = BufferUtils.allocateFloat(20) - /** Index buffer */ - indices = IntBuffer.wrap(intArrayOf()) - - material = ShaderMaterial.fromClass( + geometry { + /** Geometry type -- Default for Line is [GeometryType.LINE] */ + geometryType = GeometryType.LINE_STRIP_ADJACENCY + /** Vertex buffer */ + vertices = BufferUtils.allocateFloat(30) + /** Normal buffer */ + normals = BufferUtils.allocateFloat(30) + /** Texcoord buffer */ + texcoords = BufferUtils.allocateFloat(20) + /** Index buffer */ + indices = IntBuffer.wrap(intArrayOf()) + + } + setMaterial(ShaderMaterial.fromClass( Line::class.java, listOf(ShaderType.VertexShader, ShaderType.GeometryShader, ShaderType.FragmentShader) - ) - material.cullingMode = Material.CullingMode.None + )) { + cullingMode = Material.CullingMode.None + } + reshape(vector) } @@ -85,49 +91,52 @@ class Arrow(var vector: Vector3f = Vector3f(0.0f)) : Mesh("Arrow") { * @param vector The vector defining the shape of the arrow */ fun reshape(vector: Vector3f) { - //init the data structures - clearPoints() - - /** create the vector shape */ - //first of the two mandatory surrounding fake points that are never displayed - addPoint(zeroGLvec) - - //the main "vertical" segment of the vector - addPoint(zeroGLvec) - addPoint(vector) - - //the "horizontal" base segment of the "arrow head" triangles - var base = if (vector.x() == 0.0f && vector.y() == 0.0f) { - //the input 'vector' must be parallel to the z-axis, - //we can use this particular 'base' then - Vector3f(0.0f, 1.0f, 0.0f) - } - else { - //vector 'base' is perpendicular to the input 'vector' - Vector3f(-vector.y(), vector.x(), 0.0f).normalize() + geometry { + //init the data structures + clearPoints() + + /** create the vector shape */ + //first of the two mandatory surrounding fake points that are never displayed + addPoint(zeroGLvec) + + //the main "vertical" segment of the vector + addPoint(zeroGLvec) + addPoint(vector) + + //the "horizontal" base segment of the "arrow head" triangles + var base = if (vector.x() == 0.0f && vector.y() == 0.0f) { + //the input 'vector' must be parallel to the z-axis, + //we can use this particular 'base' then + Vector3f(0.0f, 1.0f, 0.0f) + } + else { + //vector 'base' is perpendicular to the input 'vector' + Vector3f(-vector.y(), vector.x(), 0.0f).normalize() + } + + //the width of the "arrow head" triangle + val v = 0.1f * vector.length() + + //the first triangle: + base = base * v + addPoint(vector.times(0.8f).plus(base)) + addPoint(vector.times(0.8f).minus(base)) + addPoint(vector) + //NB: the 0.8f defines the height (1-0.8) of the "arrow head" triangle + + //the second triangle: + base = base.cross(vector).normalize().times(v) + addPoint(vector.times(0.8f).plus(base)) + addPoint(vector.times(0.8f).minus(base)) + addPoint(vector) + + //second of the two mandatory surrounding fake points that are never displayed + addPoint(vector) } - - //the width of the "arrow head" triangle - val v = 0.1f * vector.length() - - //the first triangle: - base = base * v - addPoint(vector.times(0.8f).plus(base)) - addPoint(vector.times(0.8f).minus(base)) - addPoint(vector) - //NB: the 0.8f defines the height (1-0.8) of the "arrow head" triangle - - //the second triangle: - base = base.cross(vector).normalize().times(v) - addPoint(vector.times(0.8f).plus(base)) - addPoint(vector.times(0.8f).minus(base)) - addPoint(vector) - - //second of the two mandatory surrounding fake points that are never displayed - addPoint(vector) + boundingBox = generateBoundingBox() } - private fun addPoint(p: Vector3f) { + private fun Geometry.addPoint(p: Vector3f) { vertices.position(vertices.limit()) vertices.limit(vertices.limit() + 3) p.get(vertices) @@ -148,11 +157,9 @@ class Arrow(var vector: Vector3f = Vector3f(0.0f)) : Mesh("Arrow") { dirty = true vertexCount = vertices.limit()/vertexSize - - boundingBox = generateBoundingBox() } - private fun clearPoints() { + private fun Geometry.clearPoints() { vertices.clear() normals.clear() texcoords.clear() diff --git a/src/main/kotlin/graphics/scenery/primitives/Cone.kt b/src/main/kotlin/graphics/scenery/primitives/Cone.kt index c8d5376d8..68d0058d1 100644 --- a/src/main/kotlin/graphics/scenery/primitives/Cone.kt +++ b/src/main/kotlin/graphics/scenery/primitives/Cone.kt @@ -27,66 +27,68 @@ class Cone(val radius: Float, val height: Float, val segments: Int, axis: Vector val axis: Vector3f = Vector3f(axis).normalize() init { - vertices = BufferUtils.allocateFloat(2 * 3 * segments * 3) - normals = BufferUtils.allocateFloat(2 * 3 * segments * 3) - texcoords = BufferUtils.allocateFloat(2 * 2 * segments * 3) - - val vbuffer = ArrayList(segments * segments * 2 * 3) - val nbuffer = ArrayList(segments * segments * 2 * 3) - val tbuffer = ArrayList(segments * segments * 2 * 2) - - val apex = axis * height - val center = apex - axis * height - - val e0 = perp(axis) - val e1 = Vector3f(e0).cross(axis) - - // cone is split into [segments] sections - val delta = 2.0f/segments * PI.toFloat() - - // draw cone by creating triangles between adjacent points on the - // base and connecting one triangle to the apex, and one to the center - for (i in 0 until segments) { - val rad = delta * i - val rad2 = delta * (i + 1) - val v1 = center + (e0 * cos(rad) + e1 * sin(rad)) * radius - val v2 = center + (e0 * cos(rad2) + e1 * sin(rad2)) * radius - - vbuffer.add(v1) - vbuffer.add(apex) - vbuffer.add(v2) - - vbuffer.add(v2) - vbuffer.add(center) - vbuffer.add(v1) - - val normalSide = (apex - v2).cross(v2 - v1).normalize() - val normalBottom = axis * (-1.0f) - nbuffer.add(normalSide) - nbuffer.add(normalSide) - nbuffer.add(normalSide) - - nbuffer.add(normalBottom) - nbuffer.add(normalBottom) - nbuffer.add(normalBottom) - - tbuffer.add(Vector2f(cos(rad) * 0.5f + 0.5f, sin(rad) * 0.5f + 0.5f)) - tbuffer.add(Vector2f(0.5f, 0.5f)) - tbuffer.add(Vector2f(cos(rad2) * 0.5f + 0.5f, sin(rad2) * 0.5f + 0.5f)) - - tbuffer.add(Vector2f(cos(rad2) * 0.5f + 0.5f, sin(rad2) * 0.5f + 0.5f)) - tbuffer.add(Vector2f(0.5f, 0.5f)) - tbuffer.add(Vector2f(cos(rad) * 0.5f + 0.5f, sin(rad) * 0.5f + 0.5f)) + geometry { + vertices = BufferUtils.allocateFloat(2 * 3 * segments * 3) + normals = BufferUtils.allocateFloat(2 * 3 * segments * 3) + texcoords = BufferUtils.allocateFloat(2 * 2 * segments * 3) + + val vbuffer = ArrayList(segments * segments * 2 * 3) + val nbuffer = ArrayList(segments * segments * 2 * 3) + val tbuffer = ArrayList(segments * segments * 2 * 2) + + val apex = axis * height + val center = apex - axis * height + + val e0 = perp(axis) + val e1 = Vector3f(e0).cross(axis) + + // cone is split into [segments] sections + val delta = 2.0f/segments * PI.toFloat() + + // draw cone by creating triangles between adjacent points on the + // base and connecting one triangle to the apex, and one to the center + for (i in 0 until segments) { + val rad = delta * i + val rad2 = delta * (i + 1) + val v1 = center + (e0 * cos(rad) + e1 * sin(rad)) * radius + val v2 = center + (e0 * cos(rad2) + e1 * sin(rad2)) * radius + + vbuffer.add(v1) + vbuffer.add(apex) + vbuffer.add(v2) + + vbuffer.add(v2) + vbuffer.add(center) + vbuffer.add(v1) + + val normalSide = (apex - v2).cross(v2 - v1).normalize() + val normalBottom = axis * (-1.0f) + nbuffer.add(normalSide) + nbuffer.add(normalSide) + nbuffer.add(normalSide) + + nbuffer.add(normalBottom) + nbuffer.add(normalBottom) + nbuffer.add(normalBottom) + + tbuffer.add(Vector2f(cos(rad) * 0.5f + 0.5f, sin(rad) * 0.5f + 0.5f)) + tbuffer.add(Vector2f(0.5f, 0.5f)) + tbuffer.add(Vector2f(cos(rad2) * 0.5f + 0.5f, sin(rad2) * 0.5f + 0.5f)) + + tbuffer.add(Vector2f(cos(rad2) * 0.5f + 0.5f, sin(rad2) * 0.5f + 0.5f)) + tbuffer.add(Vector2f(0.5f, 0.5f)) + tbuffer.add(Vector2f(cos(rad) * 0.5f + 0.5f, sin(rad) * 0.5f + 0.5f)) + } + + vbuffer.forEach { v -> v.get(vertices).position(vertices.position() + 3) } + nbuffer.forEach { n -> n.get(normals).position(normals.position() + 3) } + tbuffer.forEach { uv -> uv.get(texcoords).position(texcoords.position() + 2) } + + vertices.flip() + normals.flip() + texcoords.flip() } - vbuffer.forEach { v -> v.get(vertices).position(vertices.position() + 3) } - nbuffer.forEach { n -> n.get(normals).position(normals.position() + 3) } - tbuffer.forEach { uv -> uv.get(texcoords).position(texcoords.position() + 2) } - - vertices.flip() - normals.flip() - texcoords.flip() - boundingBox = generateBoundingBox() } diff --git a/src/main/kotlin/graphics/scenery/primitives/Cylinder.kt b/src/main/kotlin/graphics/scenery/primitives/Cylinder.kt index 19d18d0a4..e1d0a9296 100644 --- a/src/main/kotlin/graphics/scenery/primitives/Cylinder.kt +++ b/src/main/kotlin/graphics/scenery/primitives/Cylinder.kt @@ -20,55 +20,57 @@ import kotlin.math.sqrt class Cylinder(var radius: Float, var height: Float, var segments: Int) : Mesh("cylinder") { init { - geometryType = GeometryType.TRIANGLE_STRIP + geometry { + geometryType = GeometryType.TRIANGLE_STRIP - val vbuffer = ArrayList(segments * segments * 2 * 3) - val nbuffer = ArrayList(segments * segments * 2 * 3) - val tbuffer = ArrayList(segments * segments * 2 * 2) + val vbuffer = ArrayList(segments * segments * 2 * 3) + val nbuffer = ArrayList(segments * segments * 2 * 3) + val tbuffer = ArrayList(segments * segments * 2 * 2) - val delta = 2.0f * PI.toFloat() / segments.toFloat() - val c = cos(delta * 1.0).toFloat() - val s = sin(delta * 1.0).toFloat() + val delta = 2.0f * PI.toFloat() / segments.toFloat() + val c = cos(delta * 1.0).toFloat() + val s = sin(delta * 1.0).toFloat() - var x2 = radius - var z2 = 0.0f + var x2 = radius + var z2 = 0.0f - for (i: Int in 0..segments) { - val texcoord = i / segments.toFloat() - val normal = 1.0f / sqrt(x2 * x2 * 1.0 + z2 * z2 * 1.0).toFloat() - val xn = x2 * normal - val zn = z2 * normal + for (i: Int in 0..segments) { + val texcoord = i / segments.toFloat() + val normal = 1.0f / sqrt(x2 * x2 * 1.0 + z2 * z2 * 1.0).toFloat() + val xn = x2 * normal + val zn = z2 * normal - nbuffer.add(xn) - nbuffer.add(0.0f) - nbuffer.add(zn) + nbuffer.add(xn) + nbuffer.add(0.0f) + nbuffer.add(zn) - tbuffer.add(texcoord) - tbuffer.add(0.0f) + tbuffer.add(texcoord) + tbuffer.add(0.0f) - vbuffer.add(0.0f + x2) - vbuffer.add(0.0f) - vbuffer.add(0.0f + z2) + vbuffer.add(0.0f + x2) + vbuffer.add(0.0f) + vbuffer.add(0.0f + z2) - nbuffer.add(xn) - nbuffer.add(0.0f) - nbuffer.add(zn) + nbuffer.add(xn) + nbuffer.add(0.0f) + nbuffer.add(zn) - tbuffer.add(texcoord) - tbuffer.add(1.0f) + tbuffer.add(texcoord) + tbuffer.add(1.0f) - vbuffer.add(0.0f + x2) - vbuffer.add(0.0f + height) - vbuffer.add(0.0f + z2) + vbuffer.add(0.0f + x2) + vbuffer.add(0.0f + height) + vbuffer.add(0.0f + z2) - val x3 = x2 - x2 = c * x2 - s * z2 - z2 = s * x3 + c * z2 - } + val x3 = x2 + x2 = c * x2 - s * z2 + z2 = s * x3 + c * z2 + } - vertices = BufferUtils.allocateFloatAndPut(vbuffer.toFloatArray()) - normals = BufferUtils.allocateFloatAndPut(nbuffer.toFloatArray()) - texcoords = BufferUtils.allocateFloatAndPut(tbuffer.toFloatArray()) + vertices = BufferUtils.allocateFloatAndPut(vbuffer.toFloatArray()) + normals = BufferUtils.allocateFloatAndPut(nbuffer.toFloatArray()) + texcoords = BufferUtils.allocateFloatAndPut(tbuffer.toFloatArray()) + } boundingBox = generateBoundingBox() } @@ -76,7 +78,9 @@ class Cylinder(var radius: Float, var height: Float, var segments: Int) : Mesh(" companion object { @JvmStatic fun betweenPoints(p1: Vector3f, p2: Vector3f, radius: Float = 0.02f, height: Float = 1.0f, segments: Int = 16): Cylinder { val cylinder = Cylinder(radius, height, segments) - cylinder.orientBetweenPoints(p1, p2, rescale = true, reposition = true) + cylinder.spatial { + orientBetweenPoints(p1, p2, rescale = true, reposition = true) + } return cylinder } } diff --git a/src/main/kotlin/graphics/scenery/primitives/InfinitePlane.kt b/src/main/kotlin/graphics/scenery/primitives/InfinitePlane.kt index a2948e1f7..c7e703c60 100644 --- a/src/main/kotlin/graphics/scenery/primitives/InfinitePlane.kt +++ b/src/main/kotlin/graphics/scenery/primitives/InfinitePlane.kt @@ -2,6 +2,7 @@ package graphics.scenery.primitives import graphics.scenery.* import graphics.scenery.backends.Shaders +import graphics.scenery.attribute.material.Material /** * Class for rendering infinite planes. @@ -26,13 +27,16 @@ class InfinitePlane : Mesh("InfinitePlane"), DisableFrustumCulling, RenderingOrd override var renderingOrder = Int.MAX_VALUE init { - vertices = BufferUtils.allocateFloatAndPut(FloatArray(6 * 3)) - normals = BufferUtils.allocateFloatAndPut(FloatArray(6 * 3)) - texcoords = BufferUtils.allocateFloatAndPut(FloatArray(6 * 2)) + geometry { + vertices = BufferUtils.allocateFloatAndPut(FloatArray(6 * 3)) + normals = BufferUtils.allocateFloatAndPut(FloatArray(6 * 3)) + texcoords = BufferUtils.allocateFloatAndPut(FloatArray(6 * 2)) + } - material = ShaderMaterial(Shaders.ShadersFromFiles(arrayOf("InfiniteGrid.vert", "InfiniteGrid.frag"))) - material.blending.transparent = true - material.blending.setOverlayBlending() - material.cullingMode = Material.CullingMode.None + setMaterial(ShaderMaterial(Shaders.ShadersFromFiles(arrayOf("InfiniteGrid.vert", "InfiniteGrid.frag")))) { + blending.transparent = true + blending.setOverlayBlending() + cullingMode = Material.CullingMode.None + } } } diff --git a/src/main/kotlin/graphics/scenery/primitives/Line.kt b/src/main/kotlin/graphics/scenery/primitives/Line.kt index 31e1ab0e3..5d9684590 100644 --- a/src/main/kotlin/graphics/scenery/primitives/Line.kt +++ b/src/main/kotlin/graphics/scenery/primitives/Line.kt @@ -4,33 +4,22 @@ import graphics.scenery.* import org.joml.Vector3f import graphics.scenery.backends.ShaderType import graphics.scenery.geometry.GeometryType -import graphics.scenery.geometry.HasGeometry import graphics.scenery.numerics.Random +import graphics.scenery.attribute.* +import graphics.scenery.attribute.geometry.HasGeometry +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.spatial.HasSpatial import org.joml.Vector4f -import java.nio.FloatBuffer -import java.nio.IntBuffer /** * Class for creating 3D lines, derived from [Node] and using [HasGeometry] * * @author Ulrik Günther */ -class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolean = false, val simple: Boolean = false) : Node("Line"), - HasGeometry { - /** Size of one vertex (e.g. 3 in 3D) */ - override val vertexSize: Int = 3 - /** Size of one texcoord (e.g. 2 in 3D) */ - override val texcoordSize: Int = 2 - /** Geometry type -- Default for Line is [GeometryType.LINE] */ - override var geometryType: GeometryType = GeometryType.LINE_STRIP_ADJACENCY - /** Vertex buffer */ - override var vertices: FloatBuffer = BufferUtils.allocateFloat(3 * capacity) - /** Normal buffer */ - override var normals: FloatBuffer = BufferUtils.allocateFloat(3 * capacity) - /** Texcoord buffer */ - override var texcoords: FloatBuffer = BufferUtils.allocateFloat(2 * capacity) - /** Index buffer */ - override var indices: IntBuffer = IntBuffer.wrap(intArrayOf()) +class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolean = false, val simple: Boolean = false) : DefaultNode("Line"), + HasRenderable, HasMaterial, HasSpatial, HasGeometry { /** Whether the line should be rendered as transparent or not. */ var transparent: Boolean = transparent @@ -65,17 +54,26 @@ class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolea var edgeWidth = 2.0f init { - if(simple) { - geometryType = GeometryType.LINE - } else { - activateTransparency(transparent) + addGeometry { + if(simple) { + geometryType = GeometryType.LINE + } else { + geometryType = GeometryType.LINE_STRIP_ADJACENCY + activateTransparency(transparent) + } + + vertices.limit(0) + normals.limit(0) + texcoords.limit(0) + } + + addMaterial { + cullingMode = Material.CullingMode.None } - vertices.limit(0) - normals.limit(0) - texcoords.limit(0) + addRenderable() - material.cullingMode = Material.CullingMode.None + addSpatial() } protected fun activateTransparency(transparent: Boolean) { @@ -83,8 +81,9 @@ class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolea return } + val newMaterial: Material if(transparent) { - val newMaterial = ShaderMaterial.fromFiles( + newMaterial = ShaderMaterial.fromFiles( "${this::class.java.simpleName}.vert", "${this::class.java.simpleName}.geom", "${this::class.java.simpleName}Forward.frag" @@ -92,30 +91,23 @@ class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolea newMaterial.blending.opacity = 1.0f newMaterial.blending.setOverlayBlending() - newMaterial.diffuse = material.diffuse - newMaterial.specular = material.specular - newMaterial.ambient = material.ambient - newMaterial.metallic = material.metallic - newMaterial.roughness = material.roughness - - material = newMaterial } else { - val newMaterial = ShaderMaterial.fromClass( + newMaterial = ShaderMaterial.fromClass( this::class.java, listOf(ShaderType.VertexShader, ShaderType.GeometryShader, ShaderType.FragmentShader) ) - - newMaterial.diffuse = material.diffuse - newMaterial.specular = material.specular - newMaterial.ambient = material.ambient - newMaterial.metallic = material.metallic - newMaterial.roughness = material.roughness - - material = newMaterial } - - material.blending.transparent = transparent - material.cullingMode = Material.CullingMode.None + material { + newMaterial.diffuse = diffuse + newMaterial.specular = specular + newMaterial.ambient = ambient + newMaterial.metallic = metallic + newMaterial.roughness = roughness + } + setMaterial(newMaterial) { + blending.transparent = transparent + cullingMode = Material.CullingMode.None + } } /** @@ -127,58 +119,60 @@ class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolea * @param points The vector containing the points */ fun addPoints(vararg points: Vector3f) { - if(vertices.limit() + 3 * points.size >= vertices.capacity()) { - val newVertices = BufferUtils.allocateFloat(vertices.capacity() + points.size * 3 + 3 * capacity) - vertices.position(0) - vertices.limit(vertices.capacity()) - newVertices.put(vertices) - newVertices.limit(vertices.limit()) + geometry { + if(vertices.limit() + 3 * points.size >= vertices.capacity()) { + val newVertices = BufferUtils.allocateFloat(vertices.capacity() + points.size * 3 + 3 * capacity) + vertices.position(0) + vertices.limit(vertices.capacity()) + newVertices.put(vertices) + newVertices.limit(vertices.limit()) - vertices = newVertices + vertices = newVertices - val newNormals = BufferUtils.allocateFloat(vertices.capacity() + points.size * 3 + 3 * capacity) - normals.position(0) - normals.limit(normals.capacity()) - newNormals.put(normals) - newNormals.limit(normals.limit()) + val newNormals = BufferUtils.allocateFloat(vertices.capacity() + points.size * 3 + 3 * capacity) + normals.position(0) + normals.limit(normals.capacity()) + newNormals.put(normals) + newNormals.limit(normals.limit()) - normals = newNormals + normals = newNormals - val newTexcoords = BufferUtils.allocateFloat(vertices.capacity() + points.size * 2 + 2 * capacity) - texcoords.position(0) - texcoords.limit(texcoords.capacity()) - newTexcoords.put(texcoords) - newTexcoords.limit(texcoords.limit()) + val newTexcoords = BufferUtils.allocateFloat(vertices.capacity() + points.size * 2 + 2 * capacity) + texcoords.position(0) + texcoords.limit(texcoords.capacity()) + newTexcoords.put(texcoords) + newTexcoords.limit(texcoords.limit()) - texcoords = newTexcoords + texcoords = newTexcoords - capacity = vertices.capacity()/3 - } + capacity = vertices.capacity()/3 + } - vertices.position(vertices.limit()) - vertices.limit(vertices.limit() + points.size * 3) - points.forEach { v -> v.get(vertices) } - vertices.position(vertices.limit()) - vertices.flip() - - normals.position(normals.limit()) - normals.limit(normals.limit() + points.size * 3) - points.forEach { v -> v.get(normals) } - normals.position(normals.limit()) - normals.flip() - - texcoords.position(texcoords.limit()) - texcoords.limit(texcoords.limit() + points.size * 2) - points.forEach { _ -> - texcoords.put(0.0f) - texcoords.put(0.0f) - } - texcoords.position(texcoords.limit()) - texcoords.flip() + vertices.position(vertices.limit()) + vertices.limit(vertices.limit() + points.size * 3) + points.forEach { v -> v.get(vertices) } + vertices.position(vertices.limit()) + vertices.flip() + + normals.position(normals.limit()) + normals.limit(normals.limit() + points.size * 3) + points.forEach { v -> v.get(normals) } + normals.position(normals.limit()) + normals.flip() + + texcoords.position(texcoords.limit()) + texcoords.limit(texcoords.limit() + points.size * 2) + points.forEach { _ -> + texcoords.put(0.0f) + texcoords.put(0.0f) + } + texcoords.position(texcoords.limit()) + texcoords.flip() - dirty = true - vertexCount = vertices.limit()/vertexSize + dirty = true + vertexCount = vertices.limit()/vertexSize + } boundingBox = generateBoundingBox() } @@ -205,13 +199,15 @@ class Line @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolea * Fully clears the line. */ fun clearPoints() { - vertices.clear() - normals.clear() - texcoords.clear() - - vertices.limit(0) - normals.limit(0) - texcoords.limit(0) + geometry { + vertices.clear() + normals.clear() + texcoords.clear() + + vertices.limit(0) + normals.limit(0) + texcoords.limit(0) + } } companion object { diff --git a/src/main/kotlin/graphics/scenery/primitives/LinePair.kt b/src/main/kotlin/graphics/scenery/primitives/LinePair.kt index 14b8ef478..854cac708 100644 --- a/src/main/kotlin/graphics/scenery/primitives/LinePair.kt +++ b/src/main/kotlin/graphics/scenery/primitives/LinePair.kt @@ -5,26 +5,18 @@ import org.joml.Vector3f import org.joml.Vector4f import graphics.scenery.backends.ShaderType import graphics.scenery.geometry.GeometryType -import graphics.scenery.geometry.HasGeometry +import graphics.scenery.attribute.geometry.Geometry +import graphics.scenery.attribute.geometry.DefaultGeometry +import graphics.scenery.attribute.geometry.HasGeometry +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.spatial.HasSpatial import java.nio.FloatBuffer import java.nio.IntBuffer -class LinePair @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolean = false, val simple: Boolean = false) : Node("Line"), - HasGeometry { - /** Size of one vertex (e.g. 3 in 3D) */ - override val vertexSize: Int = 3 - /** Size of one texcoord (e.g. 2 in 3D) */ - override val texcoordSize: Int = 2 - /** Geometry type -- Default for Line is [GeometryType.LINE] */ - override var geometryType: GeometryType = GeometryType.LINE_STRIP_ADJACENCY - /** Vertex buffer */ - override var vertices: FloatBuffer = BufferUtils.allocateFloat(3 * capacity) - /** Normal buffer */ - override var normals: FloatBuffer = BufferUtils.allocateFloat(3 * capacity) - /** Texcoord buffer */ - override var texcoords: FloatBuffer = BufferUtils.allocateFloat(2 * capacity) - /** Index buffer */ - override var indices: IntBuffer = IntBuffer.wrap(intArrayOf()) +class LinePair @JvmOverloads constructor(var capacity: Int = 50, transparent: Boolean = false, val simple: Boolean = false) : DefaultNode("Line"), + HasSpatial, HasRenderable, HasMaterial, HasGeometry { /** Whether the line should be rendered as transparent or not. */ var transparent: Boolean = transparent @@ -63,17 +55,35 @@ class LinePair @JvmOverloads constructor(var capacity: Int = 50, transparent: Bo var interpolationState = 0.5f init { - if(simple) { - geometryType = GeometryType.LINE - } else { - activateTransparency(transparent) + addGeometry { + if(simple) { + geometryType = GeometryType.LINE + } else { + geometryType = GeometryType.LINE_STRIP_ADJACENCY + activateTransparency(transparent) + } + + vertices.limit(0) + normals.limit(0) + texcoords.limit(0) + } + + addMaterial { + cullingMode = Material.CullingMode.None } - vertices.limit(0) - normals.limit(0) - texcoords.limit(0) + addRenderable() - material.cullingMode = Material.CullingMode.None + addSpatial() + } + + override fun createGeometry(): Geometry { + return object: DefaultGeometry(this) { + override var vertices: FloatBuffer = BufferUtils.allocateFloat(3 * capacity) + override var normals: FloatBuffer = BufferUtils.allocateFloat(3 * capacity) + override var texcoords: FloatBuffer = BufferUtils.allocateFloat(2 * capacity) + override var indices: IntBuffer = IntBuffer.wrap(intArrayOf()) + } } protected fun activateTransparency(transparent: Boolean) { @@ -81,8 +91,10 @@ class LinePair @JvmOverloads constructor(var capacity: Int = 50, transparent: Bo return } + val newMaterial: Material + if(transparent) { - val newMaterial = ShaderMaterial.fromFiles( + newMaterial = ShaderMaterial.fromFiles( "${this::class.java.simpleName}.vert", "${this::class.java.simpleName}.geom", "${this::class.java.simpleName}Forward.frag" @@ -90,30 +102,24 @@ class LinePair @JvmOverloads constructor(var capacity: Int = 50, transparent: Bo newMaterial.blending.opacity = 1.0f newMaterial.blending.setOverlayBlending() - newMaterial.diffuse = material.diffuse - newMaterial.specular = material.specular - newMaterial.ambient = material.ambient - newMaterial.metallic = material.metallic - newMaterial.roughness = material.roughness - - material = newMaterial } else { - val newMaterial = ShaderMaterial.fromClass( + newMaterial = ShaderMaterial.fromClass( this::class.java, listOf(ShaderType.VertexShader, ShaderType.GeometryShader, ShaderType.FragmentShader) ) - - newMaterial.diffuse = material.diffuse - newMaterial.specular = material.specular - newMaterial.ambient = material.ambient - newMaterial.metallic = material.metallic - newMaterial.roughness = material.roughness - - material = newMaterial } - - material.blending.transparent = transparent - material.cullingMode = Material.CullingMode.None + material { + newMaterial.diffuse = diffuse + newMaterial.specular = specular + newMaterial.ambient = ambient + newMaterial.metallic = metallic + newMaterial.roughness = roughness + + setMaterial(newMaterial) { + blending.transparent = transparent + cullingMode = Material.CullingMode.None + } + } } /** @@ -122,61 +128,63 @@ class LinePair @JvmOverloads constructor(var capacity: Int = 50, transparent: Bo * @param points2 Bundled points */ fun addPointPairs(points1: Array, points2: Array) { - if(vertices.limit() + 3 * points1.size >= vertices.capacity()) { - val newVertices = BufferUtils.allocateFloat(vertices.capacity() + points1.size * 3 + 3 * capacity) - vertices.position(0) - vertices.limit(vertices.capacity()) - newVertices.put(vertices) - newVertices.limit(vertices.limit()) - - vertices = newVertices - - val newNormals = BufferUtils.allocateFloat(vertices.capacity() + points2.size * 3 + 3 * capacity) - normals.position(0) - normals.limit(normals.capacity()) - newNormals.put(normals) - newNormals.limit(normals.limit()) - - normals = newNormals - - val newTexcoords = BufferUtils.allocateFloat(vertices.capacity() + points1.size * 2 + 2 * capacity) - texcoords.position(0) - texcoords.limit(texcoords.capacity()) - newTexcoords.put(texcoords) - newTexcoords.limit(texcoords.limit()) - - texcoords = newTexcoords - - capacity = vertices.capacity()/3 + geometry { + if(vertices.limit() + 3 * points1.size >= vertices.capacity()) { + val newVertices = BufferUtils.allocateFloat(vertices.capacity() + points1.size * 3 + 3 * capacity) + vertices.position(0) + vertices.limit(vertices.capacity()) + newVertices.put(vertices) + newVertices.limit(vertices.limit()) + + vertices = newVertices + + val newNormals = BufferUtils.allocateFloat(vertices.capacity() + points2.size * 3 + 3 * capacity) + normals.position(0) + normals.limit(normals.capacity()) + newNormals.put(normals) + newNormals.limit(normals.limit()) + + normals = newNormals + + val newTexcoords = BufferUtils.allocateFloat(vertices.capacity() + points1.size * 2 + 2 * capacity) + texcoords.position(0) + texcoords.limit(texcoords.capacity()) + newTexcoords.put(texcoords) + newTexcoords.limit(texcoords.limit()) + + texcoords = newTexcoords + + capacity = vertices.capacity()/3 + } + + vertices.position(vertices.limit()) + vertices.limit(vertices.limit() + points1.size * 3) + points1.forEach { v -> + v.get(vertices) + vertices.position(vertices.position() + 3) + } + vertices.flip() + + normals.position(normals.limit()) + normals.limit(normals.limit() + points2.size * 3) + points2.forEach { v -> + v.get(normals) + normals.position(normals.position() + 3) + } + normals.flip() + + texcoords.position(texcoords.limit()) + texcoords.limit(texcoords.limit() + points1.size * 2) + points1.forEach { _ -> + texcoords.put(0.0f) + texcoords.put(0.0f) + } + texcoords.flip() + + dirty = true + vertexCount = vertices.limit()/vertexSize } - vertices.position(vertices.limit()) - vertices.limit(vertices.limit() + points1.size * 3) - points1.forEach { v -> - v.get(vertices) - vertices.position(vertices.position() + 3) - } - vertices.flip() - - normals.position(normals.limit()) - normals.limit(normals.limit() + points2.size * 3) - points2.forEach { v -> - v.get(normals) - normals.position(normals.position() + 3) - } - normals.flip() - - texcoords.position(texcoords.limit()) - texcoords.limit(texcoords.limit() + points1.size * 2) - points1.forEach { _ -> - texcoords.put(0.0f) - texcoords.put(0.0f) - } - texcoords.flip() - - dirty = true - vertexCount = vertices.limit()/vertexSize - boundingBox = generateBoundingBox() } diff --git a/src/main/kotlin/graphics/scenery/primitives/Plane.kt b/src/main/kotlin/graphics/scenery/primitives/Plane.kt index 77739414a..aff35c3c8 100644 --- a/src/main/kotlin/graphics/scenery/primitives/Plane.kt +++ b/src/main/kotlin/graphics/scenery/primitives/Plane.kt @@ -13,45 +13,48 @@ import org.joml.Vector3f */ open class Plane(sizes: Vector3f) : Mesh() { init { - this.scale = sizes + spatial { + this.scale = sizes + } this.name = "plane" val side = 2.0f val side2 = side / 2.0f - vertices = BufferUtils.allocateFloatAndPut( - floatArrayOf( - // Front - -side2, -side2, side2, - side2, -side2, side2, - side2, side2, side2, - -side2, side2, side2 + geometry { + vertices = BufferUtils.allocateFloatAndPut( + floatArrayOf( + // Front + -side2, -side2, side2, + side2, -side2, side2, + side2, side2, side2, + -side2, side2, side2 + ) ) - ) - normals = BufferUtils.allocateFloatAndPut( - floatArrayOf( - // Front - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f + normals = BufferUtils.allocateFloatAndPut( + floatArrayOf( + // Front + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f + ) ) - ) - - indices = BufferUtils.allocateIntAndPut( - intArrayOf( - 0, 1, 2, 0, 2, 3 + indices = BufferUtils.allocateIntAndPut( + intArrayOf( + 0, 1, 2, 0, 2, 3 + ) ) - ) - texcoords = BufferUtils.allocateFloatAndPut( - floatArrayOf( - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f + texcoords = BufferUtils.allocateFloatAndPut( + floatArrayOf( + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + ) ) - ) + } boundingBox = generateBoundingBox() } @@ -61,32 +64,34 @@ open class Plane(sizes: Vector3f) : Mesh() { val vu = upperLeft - lowerLeft val vn = Vector3f(vr).cross(vu).normalize() - vertices = BufferUtils.allocateFloatAndPut(floatArrayOf( - // Front - lowerLeft.x, lowerLeft.y, lowerLeft.z, - lowerRight.x, lowerRight.y, lowerRight.z, - upperRight.x, upperRight.y, upperRight.z, - upperLeft.x, upperLeft.y, upperLeft.z - )) + geometry { + vertices = BufferUtils.allocateFloatAndPut(floatArrayOf( + // Front + lowerLeft.x, lowerLeft.y, lowerLeft.z, + lowerRight.x, lowerRight.y, lowerRight.z, + upperRight.x, upperRight.y, upperRight.z, + upperLeft.x, upperLeft.y, upperLeft.z + )) - normals = BufferUtils.allocateFloatAndPut(floatArrayOf( - // Front - vn.x, vn.y, vn.z, - vn.x, vn.y, vn.z, - vn.x, vn.y, vn.z, - vn.x, vn.y, vn.z - )) + normals = BufferUtils.allocateFloatAndPut(floatArrayOf( + // Front + vn.x, vn.y, vn.z, + vn.x, vn.y, vn.z, + vn.x, vn.y, vn.z, + vn.x, vn.y, vn.z + )) - indices = BufferUtils.allocateIntAndPut(intArrayOf( - 0,1,2,0,2,3 - )) + indices = BufferUtils.allocateIntAndPut(intArrayOf( + 0,1,2,0,2,3 + )) - texcoords = BufferUtils.allocateFloatAndPut(floatArrayOf( - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f - )) + texcoords = BufferUtils.allocateFloatAndPut(floatArrayOf( + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + )) + } boundingBox = generateBoundingBox() } diff --git a/src/main/kotlin/graphics/scenery/primitives/PointCloud.kt b/src/main/kotlin/graphics/scenery/primitives/PointCloud.kt index 3d3485231..f07823e38 100644 --- a/src/main/kotlin/graphics/scenery/primitives/PointCloud.kt +++ b/src/main/kotlin/graphics/scenery/primitives/PointCloud.kt @@ -17,34 +17,40 @@ import java.nio.file.Files * @author Ulrik Günther */ open class PointCloud(var pointRadius: Float = 1.0f, override var name: String = "PointCloud") : Mesh(name) { - /** [PointClouds] do not get billboarded. */ - override var isBillboard = false init { - /** [PointCloud]s are rendered as point geometry. */ - geometryType = GeometryType.POINTS - - // we are going to use shader files whose name is derived from the class name. - // -> PointCloud.vert, PointCloud.frag - material = ShaderMaterial.fromClass(this::class.java) - material.blending.transparent = true + geometry { + /** [PointCloud]s are rendered as point geometry. */ + geometryType = GeometryType.POINTS + } + renderable { + // PointClouds do not get billboarded. + isBillboard = false + // we are going to use shader files whose name is derived from the class name. + // -> PointCloud.vert, PointCloud.frag + } + setMaterial(ShaderMaterial.fromClass(this::class.java)) { + blending.transparent = true + } } /** * Sets up normal and texcoord buffers from the vertex buffers. */ fun setupPointCloud() { - if( this.texcoords.limit() == 0 ) {// Only preinitialize if texcoords has not been preinialized - this.texcoords = BufferUtils.allocateFloat(vertices.limit() / 3 * 2) - var i = 0 - while (i < this.texcoords.limit() - 1) { - this.texcoords.put(i, this.pointRadius) - this.texcoords.put(i + 1, this.pointRadius) - i += 2 + geometry { + if( this.texcoords.limit() == 0 ) {// Only preinitialize if texcoords has not been preinialized + this.texcoords = BufferUtils.allocateFloat(vertices.limit() / 3 * 2) + var i = 0 + while (i < this.texcoords.limit() - 1) { + this.texcoords.put(i, pointRadius) + this.texcoords.put(i + 1, pointRadius) + i += 2 + } + } + if( this.normals.limit() == 0 ) {// Only preinitialize if need be + this.normals = BufferUtils.allocateFloatAndPut(FloatArray(vertices.limit(), { 1.0f })) } - } - if( this.normals.limit() == 0 ) {// Only preinitialize if need be - this.normals = BufferUtils.allocateFloatAndPut(FloatArray(vertices.limit(), { 1.0f })) } } @@ -56,67 +62,69 @@ open class PointCloud(var pointRadius: Float = 1.0f, override var name: String = * comma (,), semicolon (;), and tab as separators. */ fun readFromPALM(filename: String) { + geometry { + val count = Files.lines(FileSystems.getDefault().getPath(filename)).count() + this.vertices = BufferUtils.allocateFloat(count.toInt() * 3) + this.normals = BufferUtils.allocateFloat(count.toInt() * 3) + this.texcoords = BufferUtils.allocateFloat(count.toInt() * 2) + + logger.info("Reading ${count/3} locations from $filename...") + + val boundingBoxCoords = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) + var separator = "" + + Files.lines(FileSystems.getDefault().getPath(filename)).forEach { + // try to figure out separator char + if(separator == "") { + separator = when { + it.split(",").size >= 6 -> "," + it.split("\t").size >= 6 -> "\t" + it.split(";").size >= 6 -> ";" + else -> throw IllegalStateException("Could not determine separator char for file $filename.") + } + + logger.debug("Determined <$separator> as separator char for $filename.") + } - val count = Files.lines(FileSystems.getDefault().getPath(filename)).count() - this.vertices = BufferUtils.allocateFloat(count.toInt() * 3) - this.normals = BufferUtils.allocateFloat(count.toInt() * 3) - this.texcoords = BufferUtils.allocateFloat(count.toInt() * 2) + val arr = it.split(separator) + if (!arr[0].startsWith("\"")) { + this.vertices.put(arr[1].toFloat()) + this.vertices.put(arr[2].toFloat()) + this.vertices.put(arr[3].toFloat()) - logger.info("Reading ${count/3} locations from $filename...") + boundingBoxCoords[0] = minOf(arr[1].toFloat(), boundingBoxCoords[0]) + boundingBoxCoords[2] = minOf(arr[2].toFloat(), boundingBoxCoords[2]) + boundingBoxCoords[4] = minOf(arr[3].toFloat(), boundingBoxCoords[4]) - val boundingBoxCoords = floatArrayOf(0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f) - var separator = "" + boundingBoxCoords[1] = maxOf(arr[1].toFloat(), boundingBoxCoords[1]) + boundingBoxCoords[3] = maxOf(arr[2].toFloat(), boundingBoxCoords[3]) + boundingBoxCoords[5] = maxOf(arr[3].toFloat(), boundingBoxCoords[5]) - Files.lines(FileSystems.getDefault().getPath(filename)).forEach { - // try to figure out separator char - if(separator == "") { - separator = when { - it.split(",").size >= 6 -> "," - it.split("\t").size >= 6 -> "\t" - it.split(";").size >= 6 -> ";" - else -> throw IllegalStateException("Could not determine separator char for file $filename.") - } + this.normals.put(arr[1].toFloat()) + this.normals.put(arr[2].toFloat()) + this.normals.put(arr[3].toFloat()) - logger.debug("Determined <$separator> as separator char for $filename.") + this.texcoords.put(arr[4].toFloat()) + this.texcoords.put(arr[5].toFloat()) + } } - val arr = it.split(separator) - if (!arr[0].startsWith("\"")) { - this.vertices.put(arr[1].toFloat()) - this.vertices.put(arr[2].toFloat()) - this.vertices.put(arr[3].toFloat()) - - boundingBoxCoords[0] = minOf(arr[1].toFloat(), boundingBoxCoords[0]) - boundingBoxCoords[2] = minOf(arr[2].toFloat(), boundingBoxCoords[2]) - boundingBoxCoords[4] = minOf(arr[3].toFloat(), boundingBoxCoords[4]) - - boundingBoxCoords[1] = maxOf(arr[1].toFloat(), boundingBoxCoords[1]) - boundingBoxCoords[3] = maxOf(arr[2].toFloat(), boundingBoxCoords[3]) - boundingBoxCoords[5] = maxOf(arr[3].toFloat(), boundingBoxCoords[5]) + logger.info("Finished reading. Found ${vertices.capacity()/3} locations.") - this.normals.put(arr[1].toFloat()) - this.normals.put(arr[2].toFloat()) - this.normals.put(arr[3].toFloat()) + this.vertices.flip() + this.normals.flip() + this.texcoords.flip() - this.texcoords.put(arr[4].toFloat()) - this.texcoords.put(arr[5].toFloat()) - } + boundingBox = OrientedBoundingBox(this@PointCloud, boundingBoxCoords) } - logger.info("Finished reading. Found ${vertices.capacity()/3} locations.") - - this.vertices.flip() - this.normals.flip() - this.texcoords.flip() - - boundingBox = OrientedBoundingBox(this, boundingBoxCoords) } /** * Sets the [color] for all vertices. */ fun setColor(color: Vector3f) { - val colorBuffer = FloatBuffer.allocate(vertices.capacity()) + val colorBuffer = FloatBuffer.allocate(geometry().vertices.capacity()) while(colorBuffer.hasRemaining()) { color.get(colorBuffer) } @@ -128,7 +136,7 @@ open class PointCloud(var pointRadius: Float = 1.0f, override var name: String = */ fun fromArray(array: FloatArray): PointCloud { val p = PointCloud() - p.vertices = FloatBuffer.wrap(array) + p.geometry().vertices = FloatBuffer.wrap(array) p.setupPointCloud() return p @@ -139,7 +147,7 @@ open class PointCloud(var pointRadius: Float = 1.0f, override var name: String = */ fun fromBuffer(buffer: FloatBuffer): PointCloud { val p = PointCloud() - p.vertices = buffer.duplicate() + p.geometry().vertices = buffer.duplicate() p.setupPointCloud() return p diff --git a/src/main/kotlin/graphics/scenery/primitives/Skybox.kt b/src/main/kotlin/graphics/scenery/primitives/Skybox.kt index 215c9497a..4dbd5b30d 100644 --- a/src/main/kotlin/graphics/scenery/primitives/Skybox.kt +++ b/src/main/kotlin/graphics/scenery/primitives/Skybox.kt @@ -14,6 +14,6 @@ import org.joml.Vector3f */ open class Skybox : Box(Vector3f(50.0f, 50.0f, 50.0f)) { init { - material = ShaderMaterial.fromFiles("Skybox.vert", "DefaultDeferred.frag") + setMaterial(ShaderMaterial.fromFiles("Skybox.vert", "DefaultDeferred.frag")) } } diff --git a/src/main/kotlin/graphics/scenery/primitives/TextBoard.kt b/src/main/kotlin/graphics/scenery/primitives/TextBoard.kt index 49f8a9c62..b9f9a1184 100644 --- a/src/main/kotlin/graphics/scenery/primitives/TextBoard.kt +++ b/src/main/kotlin/graphics/scenery/primitives/TextBoard.kt @@ -3,6 +3,9 @@ package graphics.scenery.primitives import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.fonts.SDFFontAtlas +import graphics.scenery.attribute.renderable.DefaultRenderable +import graphics.scenery.attribute.renderable.Renderable +import graphics.scenery.attribute.material.Material import graphics.scenery.textures.Texture import graphics.scenery.textures.Texture.RepeatMode import org.joml.Vector2i @@ -19,7 +22,7 @@ import org.joml.Vector4f * * @constructor Returns a TextBoard instance, with [fontFamily] and a declared [ShaderMaterial] */ -class TextBoard(font: String = "SourceSansPro-Regular.ttf", override var isBillboard: Boolean = false) : Mesh(), +class TextBoard(font: String = "SourceSansPro-Regular.ttf", isBillboard: Boolean = false) : Mesh(), DisableFrustumCulling { /** The text displayed on this font board */ @@ -29,7 +32,7 @@ class TextBoard(font: String = "SourceSansPro-Regular.ttf", override var isBillb field = value needsPreUpdate = true - dirty = true + geometry().dirty = true } } @@ -45,7 +48,7 @@ class TextBoard(font: String = "SourceSansPro-Regular.ttf", override var isBillb field = value needsPreUpdate = true - dirty = true + geometry().dirty = true } } @@ -69,55 +72,68 @@ class TextBoard(font: String = "SourceSansPro-Regular.ttf", override var isBillb init { name = "TextBoard" fontFamily = font - material = ShaderMaterial.fromFiles("DefaultForward.vert", "TextBoard.frag") - material.blending.transparent = true - material.blending.sourceColorBlendFactor = Blending.BlendFactor.SrcAlpha - material.blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha - material.blending.sourceAlphaBlendFactor = Blending.BlendFactor.One - material.blending.destinationAlphaBlendFactor = Blending.BlendFactor.Zero - material.blending.colorBlending = Blending.BlendOp.add - material.blending.alphaBlending = Blending.BlendOp.add - material.cullingMode = Material.CullingMode.None - - needsPreUpdate = true - } - - override fun preUpdate(renderer: Renderer, hub: Hub?) { - if(!needsPreUpdate || hub == null) { - return + renderable { + this.isBillboard = isBillboard } - - sdfCache.getOrPut(fontFamily, - { SDFFontAtlas(hub, fontFamily, - maxDistance = hub.get(SceneryElement.Settings)?.get("sdf.MaxDistance") ?: 12) }).apply { - - - logger.debug("Updating mesh for text board {} to '{}'...", name, text) - val m = this.createMeshForString(text) - - vertices = m.vertices - normals = m.normals - indices = m.indices - texcoords = m.texcoords - atlasSize = Vector2i(this.atlasWidth, this.atlasHeight) - - material.textures["diffuse"] = Texture( - Vector3i(atlasSize.x(), atlasSize.y(), 1), - channels = 1, contents = this.getAtlas(), - repeatUVW = RepeatMode.ClampToBorder.all(), - normalized = true, - mipmap = true) - - needsPreUpdate = false + setMaterial(ShaderMaterial.fromFiles("DefaultForward.vert", "TextBoard.frag")) { + blending.transparent = true + blending.sourceColorBlendFactor = Blending.BlendFactor.SrcAlpha + blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha + blending.sourceAlphaBlendFactor = Blending.BlendFactor.One + blending.destinationAlphaBlendFactor = Blending.BlendFactor.Zero + blending.colorBlending = Blending.BlendOp.add + blending.alphaBlending = Blending.BlendOp.add + cullingMode = Material.CullingMode.None } - } - /** Stringify the font board. Returns [fontFamily] used as well as the [text]. */ - override fun toString(): String { - return "TextBoard ($fontFamily): $text" + needsPreUpdate = true } + override fun createRenderable(): Renderable { + return object: DefaultRenderable(this) { + + val sdfCache = HashMap() + + override fun preUpdate(renderer: Renderer, hub: Hub?) { + if (!needsPreUpdate || hub == null) { + return + } + + sdfCache.getOrPut(fontFamily, + { + SDFFontAtlas( + hub, fontFamily, + maxDistance = hub.get(SceneryElement.Settings)?.get("sdf.MaxDistance") ?: 12 + ) + }).apply { + + + logger.debug("Updating mesh for text board {} to '{}'...", name, text) + val m = this.createMeshForString(text).geometry() + + geometry { + vertices = m.vertices + normals = m.normals + indices = m.indices + texcoords = m.texcoords + } + atlasSize = Vector2i(this.atlasWidth, this.atlasHeight) + + material().textures["diffuse"] = Texture( + Vector3i(atlasSize.x(), atlasSize.y(), 1), + channels = 1, contents = this.getAtlas(), + repeatUVW = RepeatMode.ClampToBorder.all(), + normalized = true, + mipmap = true + ) + + needsPreUpdate = false + } + } - companion object { - val sdfCache = HashMap() + /** Stringify the font board. Returns [fontFamily] used as well as the [text]. */ + override fun toString(): String { + return "TextBoard ($fontFamily): $text" + } + } } } diff --git a/src/main/kotlin/graphics/scenery/proteins/Rainbow.kt b/src/main/kotlin/graphics/scenery/proteins/Rainbow.kt index 7f3e1ee88..d9b3639de 100644 --- a/src/main/kotlin/graphics/scenery/proteins/Rainbow.kt +++ b/src/main/kotlin/graphics/scenery/proteins/Rainbow.kt @@ -58,7 +58,9 @@ class Rainbow { subProtein.children.forEach { ss -> ss.children.forEach { partialCurve -> partialCurve.children.forEach { - it.material.diffuse = colorList[listIndex] + it.ifMaterial { + diffuse = colorList[listIndex] + } listIndex++ } } diff --git a/src/main/kotlin/graphics/scenery/volumes/Colormap.kt b/src/main/kotlin/graphics/scenery/volumes/Colormap.kt index 097e124d0..31f1c1b3d 100644 --- a/src/main/kotlin/graphics/scenery/volumes/Colormap.kt +++ b/src/main/kotlin/graphics/scenery/volumes/Colormap.kt @@ -140,7 +140,7 @@ class Colormap(val buffer: ByteBuffer, val width: Int, val height: Int) { } catch(e: IOException) { logger.debug("LUT $name not found as ImageJ colormap, trying stream") logger.info("Using colormap $name from stream") - val resource = this::class.java.getResourceAsStream("colormap-$name.png") + val resource = Colormap::class.java.getResourceAsStream("colormap-$name.png") ?: throw FileNotFoundException("Could not find color map for name $name (colormap-$name.png)") return fromStream(resource, "png") diff --git a/src/main/kotlin/graphics/scenery/volumes/OrthoView.kt b/src/main/kotlin/graphics/scenery/volumes/OrthoView.kt index 396d82d16..fc93b1bde 100644 --- a/src/main/kotlin/graphics/scenery/volumes/OrthoView.kt +++ b/src/main/kotlin/graphics/scenery/volumes/OrthoView.kt @@ -1,8 +1,6 @@ package graphics.scenery.volumes -import graphics.scenery.BoundingGrid -import graphics.scenery.Box -import graphics.scenery.Node +import graphics.scenery.* import graphics.scenery.controls.InputHandler import graphics.scenery.controls.behaviours.MouseDragSphere import graphics.scenery.effectors.LineRestrictionEffector @@ -24,8 +22,8 @@ fun createOrthoView(volume: Volume) { val sliceXY = SlicingPlane() val sliceYZ = SlicingPlane() - sliceXY.rotation = sliceXY.rotation.rotateX((Math.PI / 2).toFloat()) - sliceYZ.rotation = sliceYZ.rotation.rotateZ((Math.PI / 2).toFloat()) + sliceXY.spatial().rotation = sliceXY.spatial().rotation.rotateX((Math.PI / 2).toFloat()) + sliceYZ.spatial().rotation = sliceYZ.spatial().rotation.rotateZ((Math.PI / 2).toFloat()) sliceXZ.addTargetVolume(volume) sliceXY.addTargetVolume(volume) @@ -39,17 +37,19 @@ fun createOrthoView(volume: Volume) { val planeXY = Box(Vector3f(boundingBox.max.x, boundingBox.max.y, 1f)) val planeYZ = Box(Vector3f(1f, boundingBox.max.y, boundingBox.max.z)) - planeXZ.position = center - planeXY.position = center - planeYZ.position = center + planeXZ.spatial().position = center + planeXY.spatial().position = center + planeYZ.spatial().position = center // make transparent - planeXZ.material.blending.setOverlayBlending() - planeXZ.material.blending.transparent = true - planeXZ.material.blending.opacity = 0f + planeXZ.material { + blending.setOverlayBlending() + blending.transparent = true + blending.opacity = 0f + } //planeXZ.material.wireframe = true - planeXY.material = planeXZ.material - planeYZ.material = planeXZ.material + planeXY.setMaterial(planeXZ.material()) + planeYZ.setMaterial(planeXZ.material()) planeXZ.addChild(sliceXZ) planeXY.addChild(sliceXY) @@ -59,35 +59,35 @@ fun createOrthoView(volume: Volume) { volume.addChild(planeXY) volume.addChild(planeYZ) - val yTop = Node() - yTop.position = Vector3f(center.x, boundingBox.max.y, center.z) + val yTop = RichNode() + yTop.spatial().position = Vector3f(center.x, boundingBox.max.y, center.z) volume.addChild(yTop) - val yBottom = Node() - yBottom.position = Vector3f(center.x, boundingBox.min.y, center.z) + val yBottom = RichNode() + yBottom.spatial().position = Vector3f(center.x, boundingBox.min.y, center.z) volume.addChild(yBottom) - LineRestrictionEffector(planeXZ, { yTop.position }, { yBottom.position }) + LineRestrictionEffector(planeXZ, { yTop.spatial().position }, { yBottom.spatial().position }) - val zTop = Node() - zTop.position = Vector3f(center.x, center.y, boundingBox.max.z) + val zTop = RichNode() + zTop.spatial().position = Vector3f(center.x, center.y, boundingBox.max.z) volume.addChild(/*z*/zTop) - val zBottom = Node() - zBottom.position = Vector3f(center.x, center.y, boundingBox.min.z) + val zBottom = RichNode() + zBottom.spatial().position = Vector3f(center.x, center.y, boundingBox.min.z) volume.addChild(zBottom) - LineRestrictionEffector(planeXY, { zTop.position }, { zBottom.position }) + LineRestrictionEffector(planeXY, { zTop.spatial().position }, { zBottom.spatial().position }) - val xTop = Node() - xTop.position = Vector3f(boundingBox.max.x, center.y, center.z) + val xTop = RichNode() + xTop.spatial().position = Vector3f(boundingBox.max.x, center.y, center.z) volume.addChild(xTop) - val xBottom = Node() - xBottom.position = Vector3f(boundingBox.min.x, center.y, center.z) + val xBottom = RichNode() + xBottom.spatial().position = Vector3f(boundingBox.min.x, center.y, center.z) volume.addChild(xBottom) - LineRestrictionEffector(planeYZ, { xTop.position }, { xBottom.position }) + LineRestrictionEffector(planeYZ, { xTop.spatial().position }, { xBottom.spatial().position }) } } diff --git a/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt b/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt index 0ea8081d0..58f4eb4c6 100644 --- a/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt +++ b/src/main/kotlin/graphics/scenery/volumes/RAIVolume.kt @@ -22,7 +22,7 @@ class RAIVolume(val ds: VolumeDataSource.RAISource<*>, options: VolumeViewerOpti boundingBox = generateBoundingBox() } - override fun generateBoundingBox(): OrientedBoundingBox? { + override fun generateBoundingBox(): OrientedBoundingBox { val source = ds.sources.firstOrNull() val sizes = if(source != null) { @@ -58,26 +58,30 @@ class RAIVolume(val ds: VolumeDataSource.RAISource<*>, options: VolumeViewerOpti ) } - override fun composeModel() { - @Suppress("SENSELESS_COMPARISON") - if(position != null && rotation != null && scale != null) { - val source = ds.sources.firstOrNull() + override fun createSpatial(): VolumeSpatial { + return object: VolumeSpatial(this) { + override fun composeModel() { + @Suppress("SENSELESS_COMPARISON") + if (position != null && rotation != null && scale != null) { + val source = ds.sources.firstOrNull() - val shift = if(source != null) { - val s = source.spimSource.getSource(0, 0) - val min = Vector3f(s.min(0).toFloat(), s.min(1).toFloat(), s.min(2).toFloat()) - val max = Vector3f(s.max(0).toFloat(), s.max(1).toFloat(), s.max(2).toFloat()) - (max - min) * (-0.5f) - } else { - Vector3f(0.0f, 0.0f, 0.0f) - } + val shift = if (source != null) { + val s = source.spimSource.getSource(0, 0) + val min = Vector3f(s.min(0).toFloat(), s.min(1).toFloat(), s.min(2).toFloat()) + val max = Vector3f(s.max(0).toFloat(), s.max(1).toFloat(), s.max(2).toFloat()) + (max - min) * (-0.5f) + } else { + Vector3f(0.0f, 0.0f, 0.0f) + } - model.translation(position) - model.mul(Matrix4f().set(this.rotation)) - model.scale(scale) - model.scale(localScale()) - if(origin == Origin.Center) { - model.translate(shift) + model.translation(position) + model.mul(Matrix4f().set(this.rotation)) + model.scale(scale) + model.scale(localScale()) + if (origin == Origin.Center) { + model.translate(shift) + } + } } } } diff --git a/src/main/kotlin/graphics/scenery/volumes/SceneryContext.kt b/src/main/kotlin/graphics/scenery/volumes/SceneryContext.kt index 8666ef8d5..8176b2b2c 100644 --- a/src/main/kotlin/graphics/scenery/volumes/SceneryContext.kt +++ b/src/main/kotlin/graphics/scenery/volumes/SceneryContext.kt @@ -352,8 +352,9 @@ open class SceneryContext(val node: VolumeManager, val useCompute: Boolean = fal else -> throw UnsupportedOperationException("Unknown wrapping mode: ${texture.texWrap()}") } + val material = node.material() if (texture is TextureCache) { - if(currentlyBoundCache != null && node.material.textures["volumeCache"] == currentlyBoundCache && dimensionsMatch(texture, node.material.textures["volumeCache"])) { + if(currentlyBoundCache != null && material.textures["volumeCache"] == currentlyBoundCache && dimensionsMatch(texture, material.textures["volumeCache"])) { return 0 } @@ -370,7 +371,7 @@ open class SceneryContext(val node: VolumeManager, val useCompute: Boolean = fal minFilter = Texture.FilteringMode.Linear, maxFilter = Texture.FilteringMode.Linear) - node.material.textures["volumeCache"] = gt + material.textures["volumeCache"] = gt currentlyBoundCache = gt } else { @@ -383,9 +384,9 @@ open class SceneryContext(val node: VolumeManager, val useCompute: Boolean = fal && currentlyBoundLuts[lut] != null && node.material.textures[lut] == currentlyBoundLuts[lut])) { */ - if (!(node.material.textures[name] != null + if (!(material.textures[name] != null && currentlyBoundTextures[name] != null - && node.material.textures[name] == currentlyBoundTextures[name])) { + && material.textures[name] == currentlyBoundTextures[name])) { val contents = when(texture) { is LookupTextureARGB -> null is VolumeManager.SimpleTexture2D -> texture.data @@ -409,7 +410,7 @@ open class SceneryContext(val node: VolumeManager, val useCompute: Boolean = fal minFilter = filterLinear, maxFilter = filterLinear) - node.material.textures[name] = gt + material.textures[name] = gt currentlyBoundTextures[name] = gt } @@ -541,7 +542,8 @@ open class SceneryContext(val node: VolumeManager, val useCompute: Boolean = fal val name = texture?.uniformName if(texture != null && name != null) { - val gt = node.material.textures[name] as? UpdatableTexture ?: throw IllegalStateException("Texture for $name is null or not updateable") + val material = node.material() + val gt = material.textures[name] as? UpdatableTexture ?: throw IllegalStateException("Texture for $name is null or not updateable") logger.debug("Running ${updates.size} texture updates for $texture") updates.forEach { update -> @@ -559,7 +561,7 @@ open class SceneryContext(val node: VolumeManager, val useCompute: Boolean = fal gt.normalized = false } - node.material.textures[name] = gt + material.textures[name] = gt } val textureUpdate = TextureUpdate( diff --git a/src/main/kotlin/graphics/scenery/volumes/SlicingPlane.kt b/src/main/kotlin/graphics/scenery/volumes/SlicingPlane.kt index 0aa5c1972..4a97f6f67 100644 --- a/src/main/kotlin/graphics/scenery/volumes/SlicingPlane.kt +++ b/src/main/kotlin/graphics/scenery/volumes/SlicingPlane.kt @@ -1,6 +1,7 @@ package graphics.scenery.volumes -import graphics.scenery.Node +import graphics.scenery.DefaultNode +import graphics.scenery.attribute.spatial.HasSpatial import graphics.scenery.utils.extensions.minus import graphics.scenery.utils.extensions.times import graphics.scenery.utils.extensions.xyz @@ -11,18 +12,19 @@ import org.joml.Vector4f * Non-geometry node to handle slicing plane behavior. * Default position is the XZ-plane through the origin. */ -class SlicingPlane(override var name: String = "Slicing Plane") : Node(name) { +class SlicingPlane(override var name: String = "Slicing Plane") : DefaultNode(name), HasSpatial { private var slicedVolumes = listOf() init { + addSpatial() postUpdate.add { if (slicedVolumes.isEmpty()) { return@add } // calculate closest point to origin in plane and use it as base for the plane equation - val pn = world.transform(Vector4f(0f, 1f, 0f, 0f)).xyz().normalize() - val p0 = worldPosition(Vector3f(0f)) + val pn = spatial().world.transform(Vector4f(0f, 1f, 0f, 0f)).xyz().normalize() + val p0 = spatial().worldPosition(Vector3f(0f)) val nul = Vector3f(0f) val projectedNull = nul - (pn.dot(nul - p0)) * pn diff --git a/src/main/kotlin/graphics/scenery/volumes/TransformedBufferedSimpleStack3D.kt b/src/main/kotlin/graphics/scenery/volumes/TransformedBufferedSimpleStack3D.kt index 0399ce8c8..71de4c23d 100644 --- a/src/main/kotlin/graphics/scenery/volumes/TransformedBufferedSimpleStack3D.kt +++ b/src/main/kotlin/graphics/scenery/volumes/TransformedBufferedSimpleStack3D.kt @@ -22,7 +22,7 @@ class TransformedBufferedSimpleStack3D(val stack: SimpleStack3D, backingBu override fun getSourceTransform(): AffineTransform3D { val w = AffineTransform3D() val arr = FloatArray(16) - Matrix4f(node.world).transpose().get(arr) + Matrix4f(node.spatialOrNull()?.world).transpose().get(arr) w.set(*arr.map { it.toDouble() }.toDoubleArray()) return w.concatenate(actualSourceTransform) diff --git a/src/main/kotlin/graphics/scenery/volumes/TransformedMultiResolutionStack3D.kt b/src/main/kotlin/graphics/scenery/volumes/TransformedMultiResolutionStack3D.kt index 0cf8a2778..100749c47 100644 --- a/src/main/kotlin/graphics/scenery/volumes/TransformedMultiResolutionStack3D.kt +++ b/src/main/kotlin/graphics/scenery/volumes/TransformedMultiResolutionStack3D.kt @@ -20,7 +20,7 @@ class TransformedMultiResolutionStack3D(val stack: MultiResolutionStack3D, override fun getSourceTransform(): AffineTransform3D { val w = AffineTransform3D() val arr = FloatArray(16) - Matrix4f(node.world).transpose().get(arr) + Matrix4f(node.spatialOrNull()?.world).transpose().get(arr) w.set(*arr.map { it.toDouble() }.toDoubleArray()) return w.concatenate(actualSourceTransform) diff --git a/src/main/kotlin/graphics/scenery/volumes/TransformedSimpleStack3D.kt b/src/main/kotlin/graphics/scenery/volumes/TransformedSimpleStack3D.kt index 8d10f226f..69d1f6849 100644 --- a/src/main/kotlin/graphics/scenery/volumes/TransformedSimpleStack3D.kt +++ b/src/main/kotlin/graphics/scenery/volumes/TransformedSimpleStack3D.kt @@ -21,7 +21,7 @@ class TransformedSimpleStack3D(val stack: SimpleStack3D, val node: Node, v override fun getSourceTransform(): AffineTransform3D { val w = AffineTransform3D() val arr = FloatArray(16) - Matrix4f(node.world).transpose().get(arr) + Matrix4f(node.spatialOrNull()?.world).transpose().get(arr) w.set(*arr.map { it.toDouble() }.toDoubleArray()) return w.concatenate(actualSourceTransform) diff --git a/src/main/kotlin/graphics/scenery/volumes/Volume.kt b/src/main/kotlin/graphics/scenery/volumes/Volume.kt index 9a6692ceb..a03a76863 100644 --- a/src/main/kotlin/graphics/scenery/volumes/Volume.kt +++ b/src/main/kotlin/graphics/scenery/volumes/Volume.kt @@ -19,12 +19,19 @@ import bdv.viewer.DisplayMode import bdv.viewer.Source import bdv.viewer.SourceAndConverter import bdv.viewer.state.ViewerState -import org.joml.Vector3f import graphics.scenery.* -import graphics.scenery.geometry.GeometryType -import graphics.scenery.geometry.HasGeometry import graphics.scenery.numerics.OpenSimplexNoise import graphics.scenery.numerics.Random +import graphics.scenery.attribute.DelegatesProperties +import graphics.scenery.attribute.DelegationType +import graphics.scenery.attribute.geometry.DelegatesGeometry +import graphics.scenery.attribute.geometry.Geometry +import graphics.scenery.attribute.material.DelegatesMaterial +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.DelegatesRenderable +import graphics.scenery.attribute.renderable.Renderable +import graphics.scenery.attribute.spatial.DefaultSpatial +import graphics.scenery.attribute.spatial.HasCustomSpatial import graphics.scenery.utils.LazyLogger import graphics.scenery.volumes.Volume.VolumeDataSource.SpimDataMinimalSource import io.scif.SCIFIO @@ -39,6 +46,7 @@ import net.imglib2.type.numeric.NumericType import net.imglib2.type.numeric.integer.* import net.imglib2.type.numeric.real.FloatType import org.joml.Matrix4f +import org.joml.Vector3f import org.joml.Vector3i import org.joml.Vector4f import org.lwjgl.system.MemoryUtil @@ -46,34 +54,54 @@ import org.scijava.io.location.FileLocation import tpietzsch.example2.VolumeViewerOptions import java.io.FileInputStream import java.nio.ByteBuffer -import java.nio.FloatBuffer -import java.nio.IntBuffer import java.nio.file.Files import java.nio.file.Path import java.util.* import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.atomic.AtomicInteger -import kotlin.math.* +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min +import kotlin.math.sqrt import kotlin.properties.Delegates import kotlin.streams.toList @Suppress("DEPRECATION") -open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOptions, val hub: Hub) : DelegatesRendering(), - HasGeometry, DisableFrustumCulling { - /** How many elements does a vertex store? */ - override val vertexSize : Int = 3 - /** How many elements does a texture coordinate store? */ - override val texcoordSize : Int = 2 - /** The [GeometryType] of the [Node] */ - override var geometryType : GeometryType = GeometryType.TRIANGLES - /** Array of the vertices. This buffer is _required_, but may empty. */ - override var vertices : FloatBuffer = FloatBuffer.allocate(0) - /** Array of the normals. This buffer is _required_, and may _only_ be empty if [vertices] is empty as well. */ - override var normals : FloatBuffer = FloatBuffer.allocate(0) - /** Array of the texture coordinates. Texture coordinates are optional. */ - override var texcoords : FloatBuffer = FloatBuffer.allocate(0) - /** Array of the indices to create an indexed mesh. Optional, but advisable to use to minimize the number of submitted vertices. */ - override var indices : IntBuffer = IntBuffer.allocate(0) +open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOptions, val hub: Hub) : DefaultNode("Volume"), + DelegatesProperties, DelegatesRenderable, DelegatesGeometry, DelegatesMaterial, DisableFrustumCulling, HasCustomSpatial { + + private val delegationType: DelegationType = DelegationType.OncePerDelegate + override fun getDelegationType(): DelegationType { + return delegationType + } + + private var delegateRenderable: Renderable? + private var delegateMaterial: Material? + private var delegateGeometry: Geometry? + + override fun getDelegateRenderable(): Renderable? { + return delegateRenderable + } + + fun setDelegateRenderable(delegate: Renderable?) { + delegateRenderable = delegate + } + + override fun getDelegateGeometry(): Geometry? { + return delegateGeometry + } + + fun setDelegateGeometry(delegate: Geometry?) { + delegateGeometry = delegate + } + + override fun getDelegateMaterial(): Material? { + return delegateMaterial + } + + fun setDelegateMaterial(delegate: Material?) { + delegateMaterial = delegate + } val converterSetups = ArrayList() var timepointCount: Int @@ -90,7 +118,7 @@ open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOpt } /** Pixel-to-world scaling ratio. Default: 1 px = 1mm in world space*/ - var pixelToWorldRatio: Float by Delegates.observable(0.001f) { property, old, new -> propertyChanged(property, old, new, "pixelToWorldRatio") } + var pixelToWorldRatio: Float by Delegates.observable(0.001f) { property, old, new -> spatial().propertyChanged(property, old, new, "pixelToWorldRatio") } /** What to use as the volume's origin, scenery's default is [Origin.Center], BVV's default is [Origin.FrontBottomLeft]. **/ var origin = Origin.Center @@ -153,6 +181,8 @@ open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOpt init { name = "Volume" + addSpatial() + when(dataSource) { is SpimDataMinimalSource -> { val spimData = dataSource.spimData @@ -202,10 +232,18 @@ open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOpt volumeManager.add(this) volumes.forEach { volumeManager.add(it) - it.delegate = volumeManager + it.delegateRenderable = volumeManager.renderable() + it.delegateGeometry = volumeManager.geometry() + it.delegateMaterial = volumeManager.material() it.volumeManager = volumeManager } - delegate = volumeManager + delegateRenderable = volumeManager.renderable() + delegateGeometry = volumeManager.geometry() + delegateMaterial = volumeManager.material() + } + + override fun createSpatial(): VolumeSpatial { + return VolumeSpatial(this) } /** @@ -300,23 +338,6 @@ open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOpt ) } - /** - * Composes the world matrix for this volume node, taken voxel size and [pixelToWorldRatio] - * into account. - */ - override fun composeModel() { - @Suppress("SENSELESS_COMPARISON") - if(position != null && rotation != null && scale != null) { - model.translation(position) - model.mul(Matrix4f().set(this.rotation)) - if(origin == Origin.Center) { - model.translate(-2.0f, -2.0f, -2.0f) - } - model.scale(scale) - model.scale(localScale()) - } - } - /** * Samples a point from the currently used volume, [uv] is the texture coordinate of the volume, [0.0, 1.0] for * all of the components. @@ -341,7 +362,6 @@ open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOpt companion object { val setupId = AtomicInteger(0) val scifio: SCIFIO = SCIFIO() - private val logger by LazyLogger() @JvmStatic @JvmOverloads fun fromSpimData( @@ -616,10 +636,10 @@ open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOpt * Returns the new volume. */ @JvmStatic fun fromPathRaw(file: Path, hub: Hub): BufferedVolume { - + val infoFile: Path val volumeFiles: List - + if(Files.isDirectory(file)) { volumeFiles = Files.list(file).filter { it.toString().endsWith(".raw") && Files.isRegularFile(it) && Files.isReadable(it) }.toList() infoFile = file.resolve("stacks.info") @@ -668,5 +688,25 @@ open class Volume(val dataSource: VolumeDataSource, val options: VolumeViewerOpt /** Amount of supported slicing planes per volume, see also sampling shader segments */ private const val MAX_SUPPORTED_SLICING_PLANES = 16 + + } + + open class VolumeSpatial(val volume: Volume): DefaultSpatial(volume) { + /** + * Composes the world matrix for this volume node, taken voxel size and [pixelToWorldRatio] + * into account. + */ + override fun composeModel() { + @Suppress("SENSELESS_COMPARISON") + if(position != null && rotation != null && scale != null) { + model.translation(position) + model.mul(Matrix4f().set(this.rotation)) + if(volume.origin == Origin.Center) { + model.translate(-2.0f, -2.0f, -2.0f) + } + model.scale(scale) + model.scale(volume.localScale()) + } + } } } diff --git a/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt b/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt index ba5a30ea7..deaa234d9 100644 --- a/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt +++ b/src/main/kotlin/graphics/scenery/volumes/VolumeManager.kt @@ -8,7 +8,14 @@ import bdv.viewer.RequestRepaint import bdv.viewer.state.SourceState import graphics.scenery.* import graphics.scenery.geometry.GeometryType -import graphics.scenery.geometry.HasGeometry +import graphics.scenery.attribute.geometry.Geometry +import graphics.scenery.attribute.geometry.DefaultGeometry +import graphics.scenery.attribute.geometry.HasGeometry +import graphics.scenery.attribute.material.HasMaterial +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.renderable.DefaultRenderable +import graphics.scenery.attribute.renderable.HasRenderable +import graphics.scenery.attribute.renderable.Renderable import net.imglib2.realtransform.AffineTransform3D import net.imglib2.type.numeric.ARGBType import net.imglib2.type.numeric.integer.UnsignedByteType @@ -53,50 +60,8 @@ class VolumeManager( val useCompute: Boolean = false, val customSegments: Map? = null, val customBindings: BiConsumer, Map>? = null -) : Node(), Hubable, HasGeometry, RequestRepaint { - /** How many elements does a vertex store? */ - override val vertexSize: Int = 3 - - /** How many elements does a texture coordinate store? */ - override val texcoordSize: Int = 2 - - /** The [GeometryType] of the [Node] */ - override var geometryType: GeometryType = GeometryType.TRIANGLES - - /** Array of the vertices. This buffer is _required_, but may empty. */ - override var vertices: FloatBuffer = BufferUtils.allocateFloatAndPut( - floatArrayOf( - -1.0f, -1.0f, 0.0f, - 1.0f, -1.0f, 0.0f, - 1.0f, 1.0f, 0.0f, - -1.0f, 1.0f, 0.0f - ) - ) - - /** Array of the normals. This buffer is _required_, and may _only_ be empty if [vertices] is empty as well. */ - override var normals: FloatBuffer = BufferUtils.allocateFloatAndPut( - floatArrayOf( - 1.0f, 0.0f, 0.0f, - 0.0f, 1.0f, 0.0f, - 0.0f, 0.0f, 1.0f, - 0.0f, 0.0f, 1.0f - ) - ) +) : DefaultNode("VolumeManager"), HasGeometry, HasRenderable, HasMaterial, Hubable, RequestRepaint { - /** Array of the texture coordinates. Texture coordinates are optional. */ - override var texcoords: FloatBuffer = BufferUtils.allocateFloatAndPut( - floatArrayOf( - 0.0f, 0.0f, - 1.0f, 0.0f, - 1.0f, 1.0f, - 0.0f, 1.0f - ) - ) - - /** Array of the indices to create an indexed mesh. Optional, but advisable to use to minimize the number of submitted vertices. */ - override var indices: IntBuffer = BufferUtils.allocateIntAndPut( - intArrayOf(0, 1, 2, 0, 2, 3) - ) /** * The rendering method used in the shader, can be * @@ -164,12 +129,16 @@ class VolumeManager( var renderingMethod = Volume.RenderingMethod.AlphaBlending init { - state = State.Created - name = "VolumeManager" - // fake geometry + addRenderable { + state = State.Created + } + // fake geometry + addGeometry { + this.geometryType = GeometryType.TRIANGLES + } - this.geometryType = GeometryType.TRIANGLES + addMaterial() currentVolumeCount = 0 to 0 @@ -191,7 +160,9 @@ class VolumeManager( updateProgram(context) } - preDraw() + renderable { + preDraw() + } } @Synchronized @@ -200,17 +171,17 @@ class VolumeManager( shaderProperties["transform"] = Matrix4f() shaderProperties["viewportSize"] = Vector2f() shaderProperties["dsp"] = Vector2f() - material.textures.clear() - - material = ShaderMaterial(context.factory) - material.cullingMode = Material.CullingMode.None - material.blending.transparent = true - material.blending.sourceColorBlendFactor = Blending.BlendFactor.One - material.blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha - material.blending.sourceAlphaBlendFactor = Blending.BlendFactor.One - material.blending.destinationAlphaBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha - material.blending.colorBlending = Blending.BlendOp.add - material.blending.alphaBlending = Blending.BlendOp.add + material().textures.clear() + setMaterial(ShaderMaterial(context.factory)) { + cullingMode = Material.CullingMode.None + blending.transparent = true + blending.sourceColorBlendFactor = Blending.BlendFactor.One + blending.destinationColorBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha + blending.sourceAlphaBlendFactor = Blending.BlendFactor.One + blending.destinationAlphaBlendFactor = Blending.BlendFactor.OneMinusSrcAlpha + blending.colorBlending = Blending.BlendOp.add + blending.alphaBlending = Blending.BlendOp.add + } } @Synchronized @@ -363,12 +334,12 @@ class VolumeManager( } private fun clearKeysAndTextures() { - val oldKeys = this.material.textures.keys() + val oldKeys = material().textures.keys() .asSequence() .filter { it.contains("_") } logger.info("Removing texture keys ${oldKeys.joinToString(",")}") oldKeys.map { - this.material.textures.remove(it) + material().textures.remove(it) } val oldProps = this.shaderProperties.keys @@ -401,10 +372,10 @@ class VolumeManager( val hmd = hub?.getWorkingHMDDisplay()?.wantsVR(settings) val mvp = if(hmd != null) { Matrix4f(hmd.getEyeProjection(0, cam.nearPlaneDistance, cam.farPlaneDistance)) - .mul(cam.getTransformation()) + .mul(cam.spatial().getTransformation()) } else { - Matrix4f(cam.projection) - .mul(cam.getTransformation()) + Matrix4f(cam.spatial().projection) + .mul(cam.spatial().getTransformation()) } // TODO: original might result in NULL, is this intended? @@ -577,9 +548,9 @@ class VolumeManager( val multiResCount = renderStacksStates.count { it.stack is MultiResolutionStack3D } val regularCount = renderStacksStates.count { it.stack is SimpleStack3D } - val multiResMatch = material.textures.count { it.key.startsWith("volumeCache") } == 1 - && material.textures.count { it.key.startsWith("lutSampler_") } >= multiResCount - val regularMatch = material.textures.count { it.key.startsWith("volume_") } >= regularCount + val multiResMatch = material().textures.count { it.key.startsWith("volumeCache") } == 1 + && material().textures.count { it.key.startsWith("lutSampler_") } >= multiResCount + val regularMatch = material().textures.count { it.key.startsWith("volume_") } >= regularCount val counts = listOf("sourcemax", "offset", "scale", "im").map { key -> key to shaderProperties.keys.count { it.contains("${key}_x_") @@ -597,7 +568,7 @@ class VolumeManager( logger.debug( "ReadyToRender: $multiResCount->$multiResMatch/$regularCount->$regularMatch\n " + " * ShaderProperties: ${shaderProperties.keys.joinToString(",")}\n " + - " * Textures: ${material.textures.keys.joinToString(",")}\n " + + " * Textures: ${material().textures.keys.joinToString(",")}\n " + " * Counts: ${counts.joinToString(",") { "${it.first}=${it.second}" }}" ) } @@ -613,47 +584,83 @@ class VolumeManager( } - /** - * Pre-draw routine to be called by the rendered just before drawing. - * Updates texture cache and used blocks. - */ - override fun preDraw(): Boolean { - logger.debug("Running predraw") - context.bindTexture(textureCache) - - if (nodes.any { it.transferFunction.stale }) { - transferFunctionTextures.clear() - val keys = material.textures.filter { it.key.startsWith("transferFunction") }.keys - keys.forEach { material.textures.remove(it) } - renderStateUpdated = true - } + override fun createRenderable(): Renderable { + return object: DefaultRenderable(this) { + /** + * Pre-draw routine to be called by the rendered just before drawing. + * Updates texture cache and used blocks. + */ + override fun preDraw(): Boolean { + logger.debug("Running predraw") + context.bindTexture(textureCache) + + if (nodes.any { it.transferFunction.stale }) { + transferFunctionTextures.clear() + val keys = material().textures.filter { it.key.startsWith("transferFunction") }.keys + keys.forEach { material().textures.remove(it) } + renderStateUpdated = true + } - if (renderStateUpdated) { - updateRenderState() - needAtLeastNumVolumes(renderStacksStates.size) - renderStateUpdated = false - } + if (renderStateUpdated) { + updateRenderState() + needAtLeastNumVolumes(renderStacksStates.size) + renderStateUpdated = false + } - var repaint = true - val blockUpdateDuration = measureTimeMillis { - if (!freezeRequiredBlocks) { - try { - updateBlocks(context) - } catch (e: RuntimeException) { - logger.warn("Probably ran out of data, corrupt BDV file? $e") - e.printStackTrace() + var repaint = true + val blockUpdateDuration = measureTimeMillis { + if (!freezeRequiredBlocks) { + try { + updateBlocks(context) + } catch (e: RuntimeException) { + logger.warn("Probably ran out of data, corrupt BDV file? $e") + e.printStackTrace() + } + } } - } - } - logger.debug("Block updates took {}ms", blockUpdateDuration) + logger.debug("Block updates took {}ms", blockUpdateDuration) - context.runDeferredBindings() - if (repaint) { - context.runTextureUpdates() + context.runDeferredBindings() + if (repaint) { + context.runTextureUpdates() + } + + return readyToRender() + } } + } - return readyToRender() + override fun createGeometry(): Geometry { + return object : DefaultGeometry(this) { + override var vertices: FloatBuffer = BufferUtils.allocateFloatAndPut( + floatArrayOf( + -1.0f, -1.0f, 0.0f, + 1.0f, -1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + -1.0f, 1.0f, 0.0f + ) + ) + override var normals: FloatBuffer = BufferUtils.allocateFloatAndPut( + floatArrayOf( + 1.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 1.0f, + 0.0f, 0.0f, 1.0f + ) + ) + override var texcoords: FloatBuffer = BufferUtils.allocateFloatAndPut( + floatArrayOf( + 0.0f, 0.0f, + 1.0f, 0.0f, + 1.0f, 1.0f, + 0.0f, 1.0f + ) + ) + override var indices: IntBuffer = BufferUtils.allocateIntAndPut( + intArrayOf(0, 1, 2, 0, 2, 3) + ) + } } /** @@ -744,7 +751,8 @@ class VolumeManager( @Synchronized fun remove(node: Volume) { logger.debug("Removing $node to OOC nodes") - node.delegate = null + node.setDelegateRenderable(null) + node.setDelegateGeometry(null) nodes.remove(node) val volumes = nodes.toMutableList() @@ -753,7 +761,8 @@ class VolumeManager( val vm = VolumeManager(hub, useCompute) volumes.forEach { vm.add(it) - it.delegate = vm + it.setDelegateRenderable(vm.renderable()) + it.setDelegateGeometry(vm.geometry()) } hub?.add(vm) @@ -792,7 +801,7 @@ class VolumeManager( prog.clear() // updateRenderState() // needAtLeastNumVolumes(renderStacks.size) - preDraw() + renderable().preDraw() } fun removeCachedColormapFor(node: Volume) { diff --git a/src/test/java/graphics/scenery/tests/examples/basic/TexturedCubeJavaExample.java b/src/test/java/graphics/scenery/tests/examples/basic/TexturedCubeJavaExample.java index 702b6686e..68723f2d5 100644 --- a/src/test/java/graphics/scenery/tests/examples/basic/TexturedCubeJavaExample.java +++ b/src/test/java/graphics/scenery/tests/examples/basic/TexturedCubeJavaExample.java @@ -2,6 +2,7 @@ import graphics.scenery.*; import graphics.scenery.backends.Renderer; +import graphics.scenery.attribute.material.Material; import graphics.scenery.textures.Texture; import graphics.scenery.utils.Image; import org.joml.Vector3f; @@ -24,33 +25,31 @@ public void init() { setRenderer( Renderer.createRenderer(getHub(), getApplicationName(), getScene(), getWindowWidth(), getWindowHeight())); getHub().add(SceneryElement.Renderer, getRenderer()); - Material boxmaterial = new Material(); - boxmaterial.setAmbient( new Vector3f(1.0f, 0.0f, 0.0f) ); - boxmaterial.setDiffuse( new Vector3f(0.0f, 1.0f, 0.0f) ); - boxmaterial.setSpecular( new Vector3f(1.0f, 1.0f, 1.0f) ); - boxmaterial.getTextures().put("diffuse", Texture.fromImage(Image.fromResource("textures/helix.png", this.getClass()))); - final Box box = new Box(new Vector3f(1.0f, 1.0f, 1.0f), false); - box.setMaterial( boxmaterial ); - box.setPosition( new Vector3f(0.0f, 0.0f, 0.0f) ); + Material material = box.material(); + material.setAmbient( new Vector3f(1.0f, 0.0f, 0.0f) ); + material.setDiffuse( new Vector3f(0.0f, 1.0f, 0.0f) ); + material.setSpecular( new Vector3f(1.0f, 1.0f, 1.0f) ); + material.getTextures().put("diffuse", Texture.fromImage(Image.fromResource("textures/helix.png", this.getClass()))); + box.spatial().setPosition( new Vector3f(0.0f, 0.0f, 0.0f) ); getScene().addChild(box); PointLight light = new PointLight(15.0f); - light.setPosition(new Vector3f(0.0f, 0.0f, 2.0f)); + light.spatial().setPosition(new Vector3f(0.0f, 0.0f, 2.0f)); light.setIntensity(5.0f); light.setEmissionColor(new Vector3f(1.0f, 1.0f, 1.0f)); getScene().addChild(light); Camera cam = new DetachedHeadCamera(); - cam.setPosition( new Vector3f(0.0f, 0.0f, 5.0f) ); + cam.spatial().setPosition( new Vector3f(0.0f, 0.0f, 5.0f) ); cam.perspectiveCamera(50.0f, getRenderer().getWindow().getWidth(), getRenderer().getWindow().getHeight(), 0.1f, 1000.0f); getScene().addChild(cam); Thread rotator = new Thread(() -> { while (true) { - box.getRotation().rotateY(0.01f); - box.setNeedsUpdate(true); + box.spatial().getRotation().rotateY(0.01f); + box.spatial().setNeedsUpdate(true); try { Thread.sleep(20); diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/ARExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/ARExample.kt index a9d1e947c..781343f1d 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/ARExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/ARExample.kt @@ -31,27 +31,37 @@ class ARExample: SceneryBase("AR Volume Rendering example", 1280, 720) { val cam: Camera = DetachedHeadCamera(hololens) with(cam) { - position = Vector3f(-0.2f, 0.0f, 1.0f) + spatial { + position = Vector3f(-0.2f, 0.0f, 1.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } val volume = Volume.fromBuffer(emptyList(), 64, 64, 64, UnsignedShortType(), hub) - volume.name = "volume" - volume.colormap = Colormap.get("plasma") - volume.scale = Vector3f(0.02f, 0.02f, 0.02f) - scene.addChild(volume) + with(volume) { + name = "volume" + colormap = Colormap.get("plasma") + spatial { + scale = Vector3f(0.02f, 0.02f, 0.02f) + } + scene.addChild(this) + } val lights = (0 until 3).map { PointLight(radius = 15.0f) } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) - light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) - light.intensity = 50.0f - scene.addChild(light) + with(light) { + spatial { + position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + } + emissionColor = Vector3f(1.0f, 1.0f, 1.0f) + intensity = 50.0f + scene.addChild(this) + } } thread { diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/AttributesExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/AttributesExample.kt new file mode 100644 index 000000000..f58cc57f2 --- /dev/null +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/AttributesExample.kt @@ -0,0 +1,138 @@ +package graphics.scenery.tests.examples.advanced + +import org.joml.Vector3f +import graphics.scenery.* +import graphics.scenery.geometry.GeometryType +import org.joml.Quaternionf + +class AttributesExample : SceneryBase("AttributesExample") { + + override fun init() { + + val mesh = Mesh() + + // access default attributes (spatial, renderable, material, geometry) of a node that has these attributes + + with(mesh) { + spatial { + position = Vector3f(0.1f, 0.2f, 0.3f) + scale = Vector3f(2f, 2f, 3f) + rotation = Quaternionf(0f, 0.1f, 0.1f, 1f) + } + renderable { + isBillboard = true + } + material { + ambient = Vector3f(0f, 0.1f, 0.1f) + diffuse = Vector3f(0f, 0.1f, 0.1f) + specular = Vector3f(0f, 0.1f, 0.1f) + metallic = 0.5f + } + geometry { + geometryType = GeometryType.POINTS + } + } + + // access attributes of a node of unknown type / with an unknown set of attributes + + val anyNode = mesh as Node + + anyNode.ifSpatial { + position = Vector3f(3f, 4f, 5f) + } + + // assign custom attributes + anyNode.addAttribute(SuperPowers::class.java, DefaultSuperPowers()) { + canTimeTravel = true + } + + // create object of custom node class with custom attributes attached by default + val myNode = SuperGirlNode() + + // access custom attributes.. + + myNode.superPowers { + canFly = true + } + + // .. when you are not sure whether the node has these attributes: + mesh.ifHasAttribute(SuperPowers::class.java) { + canFly = true + } + + val flashNode = FlashNode() + + flashNode.superPowers { + speed = 200 + } + + this.close() + + } + + interface SuperPowers { + var canFly: Boolean + var canTimeTravel: Boolean + } + + class DefaultSuperPowers: SuperPowers { + override var canFly: Boolean = false + override var canTimeTravel: Boolean = false + } + + class FlashPowers: SuperPowers { + override var canFly: Boolean = true + override var canTimeTravel: Boolean = true + var speed: Int = 10 + } + + class SuperGirlNode: DefaultNode(), HasSuperPowers { + init { + addSuperPowers { + canFly = true + } + } + } + + class FlashNode: DefaultNode(), HasCustomSuperPowers { + override fun createSuperPowers(): FlashPowers { + return FlashPowers() + } + init { + addSuperPowers() + } + } + + interface HasSuperPowers: HasCustomSuperPowers { + override fun createSuperPowers(): SuperPowers { + return DefaultSuperPowers() + } + } + + interface HasCustomSuperPowers: Node { + + fun createSuperPowers(): T + + fun addSuperPowers() { + addAttribute(SuperPowers::class.java, createSuperPowers()) + } + + fun addSuperPowers(block: T.() -> Unit) { + addAttribute(SuperPowers::class.java, createSuperPowers()) + } + + fun superPowers(block: T.() -> Unit): T { + return getAttribute(SuperPowers::class.java, block) + } + fun superPowers(): T { + return superPowers {} + } + } + + companion object { + @JvmStatic + fun main(args: Array) { + AttributesExample().main() + } + } +} diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/BetaStrandExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/BetaStrandExample.kt index e393ca612..3f6d5f6e3 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/BetaStrandExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/BetaStrandExample.kt @@ -5,6 +5,7 @@ import graphics.scenery.backends.Renderer import graphics.scenery.geometry.CatmullRomSpline import graphics.scenery.geometry.Curve import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import org.joml.Vector3f /** @@ -64,18 +65,22 @@ class BetaStrandExample: SceneryBase("BetaStrandExample", windowWidth = 1280, wi val lightbox = Box(Vector3f(25.0f, 25.0f, 25.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 20.0f) - l.position = Vector3f( + l.spatial { + position = Vector3f( Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(1.0f, 5.0f) - ) + ) + } l.emissionColor = Random.random3DVectorFromRange(0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -88,7 +93,9 @@ class BetaStrandExample: SceneryBase("BetaStrandExample", windowWidth = 1280, wi val stageLight = PointLight(radius = 10.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -97,7 +104,9 @@ class BetaStrandExample: SceneryBase("BetaStrandExample", windowWidth = 1280, wi cameraLight.intensity = 0.8f val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) cam.addChild(cameraLight) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/BloodCellsExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/BloodCellsExample.kt index 428e8af76..8cf614fd3 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/BloodCellsExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/BloodCellsExample.kt @@ -4,6 +4,7 @@ import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random import graphics.scenery.Mesh +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.minus import org.joml.Vector3f import kotlin.concurrent.thread @@ -24,16 +25,20 @@ class BloodCellsExample : SceneryBase("BloodCellsExample", windowWidth = 1280, w renderer = hub.add(Renderer.createRenderer(hub, applicationName, scene, windowWidth, windowHeight)) val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 20.0f, -20.0f) + cam.spatial { + position = Vector3f(0.0f, 20.0f, -20.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight, 2.0f, 5000.0f) scene.addChild(cam) val hull = Box(Vector3f(2*positionRange, 2*positionRange, 2*positionRange), insideNormals = true) - hull.material.ambient = Vector3f(0.0f, 0.0f, 0.0f) - hull.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - hull.material.specular = Vector3f(0.0f, 0.0f, 0.0f) - hull.material.cullingMode = Material.CullingMode.Front + hull.material { + ambient = Vector3f(0.0f, 0.0f, 0.0f) + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + specular = Vector3f(0.0f, 0.0f, 0.0f) + cullingMode = Material.CullingMode.Front + } hull.name = "hull" scene.addChild(hull) @@ -41,7 +46,9 @@ class BloodCellsExample : SceneryBase("BloodCellsExample", windowWidth = 1280, w val lights = (0 until lightCount).map { PointLight(radius = positionRange) } lights.map { - it.position = Random.random3DVectorFromRange(-positionRange/2, positionRange/2) + it.spatial { + position = Random.random3DVectorFromRange(-positionRange/2, positionRange/2) + } it.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) it.intensity = 0.5f @@ -50,78 +57,82 @@ class BloodCellsExample : SceneryBase("BloodCellsExample", windowWidth = 1280, w val erythrocyte = Mesh() erythrocyte.readFromOBJ(Mesh::class.java.getResource("models/erythrocyte.obj").file) - erythrocyte.material = ShaderMaterial.fromFiles("DefaultDeferredInstanced.vert", "DefaultDeferred.frag") - erythrocyte.material.ambient = Vector3f(0.1f, 0.0f, 0.0f) - erythrocyte.material.diffuse = Vector3f(0.9f, 0.0f, 0.02f) - erythrocyte.material.specular = Vector3f(0.05f, 0f, 0f) - erythrocyte.material.metallic = 0.01f - erythrocyte.material.roughness = 0.9f + erythrocyte.setMaterial(ShaderMaterial.fromFiles("DefaultDeferredInstanced.vert", "DefaultDeferred.frag")) { + ambient = Vector3f(0.1f, 0.0f, 0.0f) + diffuse = Vector3f(0.9f, 0.0f, 0.02f) + specular = Vector3f(0.05f, 0f, 0f) + metallic = 0.01f + roughness = 0.9f + } erythrocyte.name = "Erythrocyte_Master" - erythrocyte.instancedProperties["ModelMatrix"] = { erythrocyte.model } - scene.addChild(erythrocyte) + val erythrocyteInstanced = InstancedNode(erythrocyte) + scene.addChild(erythrocyteInstanced) val leucocyte = Mesh() leucocyte.readFromOBJ(Mesh::class.java.getResource("models/leukocyte.obj").file) leucocyte.name = "leucocyte_Master" - leucocyte.material = ShaderMaterial.fromFiles("DefaultDeferredInstanced.vert", "DefaultDeferred.frag") - leucocyte.material.ambient = Vector3f(0.1f, 0.0f, 0.0f) - leucocyte.material.diffuse = Vector3f(0.8f, 0.7f, 0.7f) - leucocyte.material.specular = Vector3f(0.05f, 0f, 0f) - leucocyte.material.metallic = 0.01f - leucocyte.material.roughness = 0.5f - leucocyte.instancedProperties["ModelMatrix"] = { leucocyte.model } - scene.addChild(leucocyte) + leucocyte.setMaterial(ShaderMaterial.fromFiles("DefaultDeferredInstanced.vert", "DefaultDeferred.frag")) { + ambient = Vector3f(0.1f, 0.0f, 0.0f) + diffuse = Vector3f(0.8f, 0.7f, 0.7f) + specular = Vector3f(0.05f, 0f, 0f) + metallic = 0.01f + roughness = 0.5f + } + val leucocyteInstanced = InstancedNode(leucocyte) + scene.addChild(leucocyteInstanced) - val container = Node("Cell container") + val container = RichNode("Cell container") val leucocytes = (0 until leucocyteCount).map { - val v = Mesh() + val v = leucocyteInstanced.addInstance() v.name = "leucocyte_$it" - v.instancedProperties["ModelMatrix"] = { v.world } v.metadata["axis"] = Vector3f(sin(0.1 * it).toFloat(), -cos(0.1 * it).toFloat(), sin(1.0f*it)*cos(1.0f*it)).normalize() v.parent = container val scale = Random.randomFromRange(3.0f, 4.0f) - v.scale = Vector3f(scale, scale, scale) - v.position = Random.random3DVectorFromRange(-positionRange, positionRange) - v.rotation.rotateXYZ( - Random.randomFromRange(0.01f, 0.9f), - Random.randomFromRange(0.01f, 0.9f), - Random.randomFromRange(0.01f, 0.9f) - ) + v.spatial { + this.scale = Vector3f(scale, scale, scale) + this.position = Random.random3DVectorFromRange(-positionRange, positionRange) + this.rotation.rotateXYZ( + Random.randomFromRange(0.01f, 0.9f), + Random.randomFromRange(0.01f, 0.9f), + Random.randomFromRange(0.01f, 0.9f) + ) + } v } - leucocyte.instances.addAll(leucocytes) // erythrocytes make up about 40% of human blood, while leucocytes make up about 1% val erythrocytes = (0 until leucocyteCount*40).map { - val v = Mesh() + val v = erythrocyteInstanced.addInstance() v.name = "erythrocyte_$it" - v.instancedProperties["ModelMatrix"] = { v.world } v.metadata["axis"] = Vector3f(sin(0.1 * it).toFloat(), -cos(0.1 * it).toFloat(), sin(1.0f*it)*cos(1.0f*it)).normalize() v.parent = container - val scale = Random.randomFromRange(0.5f, 1.2f) - v.scale = Vector3f(scale, scale, scale) - v.position = Random.random3DVectorFromRange(-positionRange, positionRange) - v.rotation.rotateXYZ( - Random.randomFromRange(0.01f, 0.9f), - Random.randomFromRange(0.01f, 0.9f), - Random.randomFromRange(0.01f, 0.9f) - ) + val randomScale = Random.randomFromRange(0.5f, 1.2f) + v.spatial { + this.scale = Vector3f(randomScale, randomScale, randomScale) + position = Random.random3DVectorFromRange(-positionRange, positionRange) + rotation.rotateXYZ( + Random.randomFromRange(0.01f, 0.9f), + Random.randomFromRange(0.01f, 0.9f), + Random.randomFromRange(0.01f, 0.9f) + ) + } v } - erythrocyte.instances.addAll(erythrocytes) scene.addChild(container) fun Node.hoverAndTumble(magnitude: Float) { val axis = this.metadata["axis"] as? Vector3f ?: return - this.rotation.rotateAxis(magnitude, axis.x(), axis.y(), axis.z()) - this.rotation.rotateY(-1.0f * magnitude) - this.needsUpdate = true + this.ifSpatial { + rotation.rotateAxis(magnitude, axis.x(), axis.y(), axis.z()) + rotation.rotateY(-1.0f * magnitude) + needsUpdate = true + } } thread { @@ -133,8 +144,10 @@ class BloodCellsExample : SceneryBase("BloodCellsExample", windowWidth = 1280, w erythrocytes.parallelStream().forEach { erythrocyte -> erythrocyte.hoverAndTumble(Random.randomFromRange(0.001f, 0.01f)) } leucocytes.parallelStream().forEach { leucocyte -> leucocyte.hoverAndTumble(0.001f) } - container.position = container.position - Vector3f(0.01f, 0.01f, 0.01f) - container.updateWorld(false) + container.spatial{ + position = position - Vector3f(0.01f, 0.01f, 0.01f) + updateWorld(false) + } Thread.sleep(5) ticks++ diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/CycleRenderQualityExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/CycleRenderQualityExample.kt index f37246409..df02d9850 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/CycleRenderQualityExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/CycleRenderQualityExample.kt @@ -5,6 +5,7 @@ import graphics.scenery.* import graphics.scenery.backends.RenderConfigReader import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.minus import kotlin.concurrent.thread import kotlin.math.floor @@ -21,17 +22,21 @@ class CycleRenderQualityExample: SceneryBase("CycleRenderQualityExample", window val rowSize = 10f val spheres = (0 until 100).map { val s = Icosphere(0.4f, 2) - s.position = Vector3f( - floor(it / rowSize), - (it % rowSize.toInt()).toFloat(), - 0.0f) - s.position = s.position - Vector3f( - (rowSize - 1.0f)/2.0f, - (rowSize - 1.0f)/2.0f, - 0.0f) - s.material.roughness = (it / rowSize)/rowSize - s.material.metallic = (it % rowSize.toInt())/rowSize - s.material.diffuse = Vector3f(1.0f, 0.0f, 0.0f) + s.spatial { + position = Vector3f( + floor(it / rowSize), + (it % rowSize.toInt()).toFloat(), + 0.0f) + position = position - Vector3f( + (rowSize - 1.0f)/2.0f, + (rowSize - 1.0f)/2.0f, + 0.0f) + } + s.material { + roughness = (it / rowSize)/rowSize + metallic = (it % rowSize.toInt())/rowSize + diffuse = Vector3f(1.0f, 0.0f, 0.0f) + } s } @@ -40,19 +45,23 @@ class CycleRenderQualityExample: SceneryBase("CycleRenderQualityExample", window val lightbox = Box(Vector3f(25.0f, 25.0f, 25.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 20.0f) - l.position = Vector3f( - Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), - Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), - Random.randomFromRange(1.0f, 5.0f) - ) + l.spatial { + position = Vector3f( + Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), + Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), + Random.randomFromRange(1.0f, 5.0f) + ) + } l.emissionColor = Random.random3DVectorFromRange(0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -64,7 +73,9 @@ class CycleRenderQualityExample: SceneryBase("CycleRenderQualityExample", window val stageLight = PointLight(radius = 35.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -74,7 +85,9 @@ class CycleRenderQualityExample: SceneryBase("CycleRenderQualityExample", window val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.2f, 12.0f) + spatial { + position = Vector3f(0.0f, 0.2f, 12.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/EyeTrackingExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/EyeTrackingExample.kt index ac5450bc9..ec14dd198 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/EyeTrackingExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/EyeTrackingExample.kt @@ -7,6 +7,7 @@ import graphics.scenery.controls.OpenVRHMD import graphics.scenery.controls.eyetracking.PupilEyeTracker import graphics.scenery.controls.TrackedDeviceType import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.plus import graphics.scenery.utils.extensions.times import org.joml.Vector2f @@ -32,7 +33,9 @@ class EyeTrackingExample: SceneryBase("Eye Tracking Example", windowWidth = 1280 val cam = DetachedHeadCamera(hmd) with(cam) { - position = Vector3f(0.0f, 0.2f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.2f, 5.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight, 0.05f, 100.0f) scene.addChild(this) @@ -40,24 +43,30 @@ class EyeTrackingExample: SceneryBase("Eye Tracking Example", windowWidth = 1280 cam.disableCulling = true referenceTarget.visible = false - referenceTarget.material.roughness = 1.0f - referenceTarget.material.metallic = 0.5f - referenceTarget.material.diffuse = Vector3f(0.8f, 0.8f, 0.8f) + referenceTarget.ifMaterial { + roughness = 1.0f + metallic = 0.5f + diffuse = Vector3f(0.8f, 0.8f, 0.8f) + } scene.addChild(referenceTarget) val lightbox = Box(Vector3f(20.0f, 20.0f, 20.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.4f, 0.4f, 0.4f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.Front + lightbox.material { + diffuse = Vector3f(0.4f, 0.4f, 0.4f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.Front + } scene.addChild(lightbox) (0..10).map { val light = PointLight(radius = 15.0f) light.emissionColor = Random.random3DVectorFromRange(0.0f, 1.0f) - light.position = Random.random3DVectorFromRange(-5.0f, 5.0f) + light.spatial { + position = Random.random3DVectorFromRange(-5.0f, 5.0f) + } light.intensity = 100.0f light @@ -109,19 +118,23 @@ class EyeTrackingExample: SceneryBase("Eye Tracking Example", windowWidth = 1280 if (!pupilTracker.isCalibrated && cam != null) { pupilTracker.onCalibrationFailed = { for(i in 0 until 2) { - referenceTarget.material.diffuse = Vector3f(1.0f, 0.0f, 0.0f) - Thread.sleep(300) - referenceTarget.material.diffuse = Vector3f(0.8f, 0.8f, 0.8f) - Thread.sleep(300) + referenceTarget.ifMaterial { + diffuse = Vector3f(1.0f, 0.0f, 0.0f) + Thread.sleep(300) + diffuse = Vector3f(0.8f, 0.8f, 0.8f) + Thread.sleep(300) + } } } pupilTracker.onCalibrationSuccess = { for(i in 0 until 20) { - referenceTarget.material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) - Thread.sleep(100) - referenceTarget.material.diffuse = Vector3f(0.8f, 0.8f, 0.8f) - Thread.sleep(30) + referenceTarget.ifMaterial { + diffuse = Vector3f(0.0f, 1.0f, 0.0f) + Thread.sleep(100) + diffuse = Vector3f(0.8f, 0.8f, 0.8f) + Thread.sleep(30) + } } } @@ -132,32 +145,40 @@ class EyeTrackingExample: SceneryBase("Eye Tracking Example", windowWidth = 1280 pupilTracker.onGazeReceived = when (pupilTracker.calibrationType) { PupilEyeTracker.CalibrationType.ScreenSpace -> { gaze -> - when { - gaze.confidence < 0.85f -> referenceTarget.material.diffuse = Vector3f(0.8f, 0.0f, 0.0f) - gaze.confidence > 0.85f -> referenceTarget.material.diffuse = Vector3f(0.0f, 0.5f, 0.5f) - gaze.confidence > 0.95f -> referenceTarget.material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) + referenceTarget.ifMaterial { + when { + gaze.confidence < 0.85f -> diffuse = Vector3f(0.8f, 0.0f, 0.0f) + gaze.confidence > 0.85f -> diffuse = Vector3f(0.0f, 0.5f, 0.5f) + gaze.confidence > 0.95f -> diffuse = Vector3f(0.0f, 1.0f, 0.0f) + } } if(gaze.confidence > 0.85f) { referenceTarget.visible = true - referenceTarget.position = cam.viewportToWorld( - Vector2f( - gaze.normalizedPosition().x() * 2.0f - 1.0f, - gaze.normalizedPosition().y() * 2.0f - 1.0f), + referenceTarget.ifSpatial { + position = cam.spatial().viewportToWorld( + Vector2f( + gaze.normalizedPosition().x() * 2.0f - 1.0f, + gaze.normalizedPosition().y() * 2.0f - 1.0f), ) + cam.forward * 0.15f + } } } PupilEyeTracker.CalibrationType.WorldSpace -> { gaze -> - when { - gaze.confidence < 0.85f -> referenceTarget.material.diffuse = Vector3f(0.8f, 0.0f, 0.0f) - gaze.confidence > 0.85f -> referenceTarget.material.diffuse = Vector3f(0.0f, 0.5f, 0.5f) - gaze.confidence > 0.95f -> referenceTarget.material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) + referenceTarget.ifMaterial { + when { + gaze.confidence < 0.85f -> diffuse = Vector3f(0.8f, 0.0f, 0.0f) + gaze.confidence > 0.85f -> diffuse = Vector3f(0.0f, 0.5f, 0.5f) + gaze.confidence > 0.95f -> diffuse = Vector3f(0.0f, 1.0f, 0.0f) + } } if(gaze.confidence > 0.85f) { referenceTarget.visible = true - referenceTarget.position = gaze.gazePoint() + referenceTarget.ifSpatial { + position = gaze.gazePoint() + } } } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/HelixExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/HelixExample.kt index a5fc4356b..3b945dda9 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/HelixExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/HelixExample.kt @@ -5,6 +5,7 @@ import org.joml.* import graphics.scenery.backends.Renderer import graphics.scenery.geometry.CatmullRomSpline import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.proteins.Helix import graphics.scenery.proteins.MathLine @@ -47,18 +48,22 @@ class HelixExample: SceneryBase("FlatRibbonSketch", windowWidth = 1280, windowHe val lightbox = Box(Vector3f(100.0f, 100.0f, 100.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 80.0f) - l.position = Vector3f( + l.spatial { + position = Vector3f( Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), Random.randomFromRange(1.0f, 5.0f) - ) + ) + } l.emissionColor = Random.random3DVectorFromRange( 0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -71,7 +76,7 @@ class HelixExample: SceneryBase("FlatRibbonSketch", windowWidth = 1280, windowHe val stageLight = PointLight(radius = 35.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial().position = Vector3f(0.0f, 0.0f, 5.0f) scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -80,7 +85,9 @@ class HelixExample: SceneryBase("FlatRibbonSketch", windowWidth = 1280, windowHe cameraLight.intensity = 0.8f val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/LocalisationExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/LocalisationExample.kt index 976600821..ed930ef8d 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/LocalisationExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/LocalisationExample.kt @@ -6,6 +6,7 @@ import graphics.scenery.backends.Renderer import graphics.scenery.controls.TrackedStereoGlasses import graphics.scenery.numerics.Random import graphics.scenery.primitives.PointCloud +import graphics.scenery.attribute.material.Material import org.scijava.Context import org.scijava.ui.UIService import org.scijava.widget.FileWidget @@ -34,17 +35,21 @@ class LocalisationExample : SceneryBase("Localisation Microscopy Rendering examp val cam: Camera = DetachedHeadCamera(hmd) with(cam) { - position = Vector3f(0.0f, 0.5f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.5f, 5.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } val shell = Box(Vector3f(20.0f, 20.0f, 20.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.Front - shell.material.diffuse = Vector3f(0.9f, 0.9f, 0.9f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) + shell.material { + cullingMode = Material.CullingMode.Front + diffuse = Vector3f(0.9f, 0.9f, 0.9f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } scene.addChild(shell) if(System.getProperty("datasets") != null) { @@ -61,8 +66,12 @@ class LocalisationExample : SceneryBase("Localisation Microscopy Rendering examp files.mapIndexed { i, file -> val dataset = PointCloud() dataset.readFromPALM(file) - dataset.material.diffuse = channelColors.getOrElse(i, { Random.random3DVectorFromRange(0.1f, 0.9f) }) - dataset.fitInto(5.0f) + dataset.material { + diffuse = channelColors.getOrElse(i, { Random.random3DVectorFromRange(0.1f, 0.9f) }) + } + dataset.spatial { + fitInto(5.0f) + } dataset }.forEach { scene.addChild(it) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/MouseInputExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/MouseInputExample.kt index 241b1c692..4e16f9bcb 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/MouseInputExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/MouseInputExample.kt @@ -8,12 +8,9 @@ import graphics.scenery.controls.behaviours.MouseRotate import graphics.scenery.controls.behaviours.SelectCommand import graphics.scenery.effectors.LineRestrictionEffector import graphics.scenery.numerics.Random -import graphics.scenery.utils.extensions.minus +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.plus -import graphics.scenery.utils.extensions.times -import graphics.scenery.utils.extensions.xyz import org.joml.Vector3f -import org.joml.Vector4f import kotlin.concurrent.thread /** @@ -36,41 +33,43 @@ class MouseInputExample : SceneryBase("MouseInputExample", wantREPL = true) { for (i in 0 until 200) { if (i % 2 == 0) { val s = Icosphere(Random.randomFromRange(0.04f, 0.2f), 2) - s.position = Random.random3DVectorFromRange(-5.0f, 5.0f) + s.spatial().position = Random.random3DVectorFromRange(-5.0f, 5.0f) scene.addChild(s) } else { val box = Box(Random.random3DVectorFromRange(0.04f, 0.2f), insideNormals = true) - box.material.diffuse = Vector3f(0f, 1.0f, 1.0f) - box.position = Random.random3DVectorFromRange(-5.0f, 5.0f) + box.material().diffuse = Vector3f(0f, 1.0f, 1.0f) + box.spatial().position = Random.random3DVectorFromRange(-5.0f, 5.0f) scene.addChild(box) } } val box = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - box.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - box.material.cullingMode = Material.CullingMode.Front + box.material { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(box) val largePlate = Box(Vector3f(7.0f, 1.0f, 8.0f)) - largePlate.material.diffuse = Vector3f(0.0f, 1.0f, 0.5f) - largePlate.position = Vector3f(0f,-4.0f,0f) + largePlate.material().diffuse = Vector3f(0.0f, 1.0f, 0.5f) + largePlate.spatial().position = Vector3f(0f,-4.0f,0f) scene.addChild(largePlate) val restrictedDragSphere = Icosphere(Random.randomFromRange(0.04f, 0.2f), 2) - restrictedDragSphere.material.diffuse = Vector3f(0f, 1.0f, 0f) + restrictedDragSphere.material().diffuse = Vector3f(0f, 1.0f, 0f) scene.addChild(restrictedDragSphere) LineRestrictionEffector(restrictedDragSphere,{Vector3f(-1f,0f,0f)},{Vector3f(1f,0f,0f)}) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 1.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial().position = Vector3f(0.0f, 0.0f, 5.0f) perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -82,10 +81,10 @@ class MouseInputExample : SceneryBase("MouseInputExample", wantREPL = true) { val wiggle: (Scene.RaycastResult, Int, Int) -> Unit = { result, _, _ -> result.matches.firstOrNull()?.let { nearest -> - val originalPosition = Vector3f(nearest.node.position) + val originalPosition = Vector3f(nearest.node.spatialOrNull()?.position) thread { for (i in 0 until 200) { - nearest.node.position = originalPosition + Random.random3DVectorFromRange(-0.05f, 0.05f) + nearest.node.spatialOrNull()?.position = originalPosition + Random.random3DVectorFromRange(-0.05f, 0.05f) Thread.sleep(2) } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxExample.kt index 7c761c09c..2aff8b4d9 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxExample.kt @@ -5,6 +5,7 @@ import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random import graphics.scenery.Mesh +import graphics.scenery.attribute.material.Material import kotlin.concurrent.thread /** @@ -18,7 +19,9 @@ class MultiBoxExample : SceneryBase("MultiBoxExample") { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(10.0f, 10.0f, 10.0f) + spatial { + position = Vector3f(10.0f, 10.0f, 10.0f) + } perspectiveCamera(60.0f, windowWidth, windowHeight, 1.0f, 1000.0f) scene.addChild(this) } @@ -38,8 +41,12 @@ class MultiBoxExample : SceneryBase("MultiBoxExample") { val j: Double = (s / boundaryWidth) % boundaryHeight val i: Double = s / (boundaryWidth * boundaryHeight) - box.position = Vector3f(Math.floor(i).toFloat() * 3.0f, Math.floor(j).toFloat() * 3.0f, Math.floor(k).toFloat() * 3.0f) - box.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) + box.spatial { + position = Vector3f(Math.floor(i).toFloat() * 3.0f, Math.floor(j).toFloat() * 3.0f, Math.floor(k).toFloat() * 3.0f) + } + box.material { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + } m.addChild(box) } @@ -49,7 +56,9 @@ class MultiBoxExample : SceneryBase("MultiBoxExample") { val lights = (0..20).map { PointLight(radius = 250.0f) }.map { - it.position = Random.random3DVectorFromRange(-100.0f, 100.0f) + it.spatial { + position = Random.random3DVectorFromRange(-100.0f, 100.0f) + } it.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) it.intensity = Random.randomFromRange(0.1f, 0.5f) it @@ -59,20 +68,26 @@ class MultiBoxExample : SceneryBase("MultiBoxExample") { val hullbox = Box(Vector3f(100.0f, 100.0f, 100.0f), insideNormals = true) with(hullbox) { - position = Vector3f(0.0f, 0.0f, 0.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } - material.ambient = Vector3f(0.6f, 0.6f, 0.6f) - material.diffuse = Vector3f(0.4f, 0.4f, 0.4f) - material.specular = Vector3f(0.0f, 0.0f, 0.0f) - material.cullingMode = Material.CullingMode.Front + material { + ambient = Vector3f(0.6f, 0.6f, 0.6f) + diffuse = Vector3f(0.4f, 0.4f, 0.4f) + specular = Vector3f(0.0f, 0.0f, 0.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(this) } thread { while (true) { - m.rotation.rotateXYZ(0.001f, 0.001f, 0.0f) - m.needsUpdate = true + m.spatial { + rotation.rotateXYZ(0.001f, 0.001f, 0.0f) + needsUpdate = true + } Thread.sleep(10) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxInstancedExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxInstancedExample.kt index de8710222..777c92ba8 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxInstancedExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/MultiBoxInstancedExample.kt @@ -5,6 +5,7 @@ import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random import graphics.scenery.Mesh +import graphics.scenery.attribute.material.Material import kotlin.concurrent.thread /** @@ -18,7 +19,9 @@ class MultiBoxInstancedExample : SceneryBase("MultiBoxInstancedExample") { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(10.0f, 10.0f, 10.0f) + spatial { + position = Vector3f(10.0f, 10.0f, 10.0f) + } perspectiveCamera(60.0f, windowWidth, windowHeight, 1.0f, 1000.0f) scene.addChild(this) @@ -31,30 +34,29 @@ class MultiBoxInstancedExample : SceneryBase("MultiBoxInstancedExample") { val b = Box(Vector3f(0.7f, 0.7f, 0.7f)) b.name = "boxmaster" - b.instancedProperties.put("ModelMatrix", { b.model }) - b.material = ShaderMaterial.fromFiles("DefaultDeferredInstanced.vert", "DefaultDeferred.frag") - b.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - b.material.ambient = Vector3f(1.0f, 1.0f, 1.0f) - b.material.specular = Vector3f(1.0f, 1.0f, 1.0f) - b.material.metallic = 0.0f - b.material.roughness = 1.0f - - scene.addChild(b) + b.setMaterial(ShaderMaterial.fromFiles("DefaultDeferredInstanced.vert", "DefaultDeferred.frag")) { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + ambient = Vector3f(1.0f, 1.0f, 1.0f) + specular = Vector3f(1.0f, 1.0f, 1.0f) + metallic = 0.0f + roughness = 1.0f + } + val bInstanced = InstancedNode(b) + scene.addChild(bInstanced) (0 until (boundaryWidth * boundaryHeight * boundaryHeight).toInt()).map { - val inst = Mesh() + val inst = bInstanced.addInstance() inst.name = "Box_$it" - inst.material = b.material - - inst.instancedProperties["ModelMatrix"] = { inst.world } + inst.addAttribute(Material::class.java, b.material()) val k: Double = it.rem(boundaryWidth) val j: Double = (it / boundaryWidth).rem(boundaryHeight) val i: Double = it / (boundaryWidth * boundaryHeight) - inst.position = Vector3f(Math.floor(i).toFloat(), Math.floor(j).toFloat(), Math.floor(k).toFloat()) + inst.spatial { + position = Vector3f(Math.floor(i).toFloat(), Math.floor(j).toFloat(), Math.floor(k).toFloat()) + } - b.instances.add(inst) inst.parent = container inst } @@ -62,7 +64,9 @@ class MultiBoxInstancedExample : SceneryBase("MultiBoxInstancedExample") { val lights = (0..20).map { PointLight(radius = 250.0f) }.map { - it.position = Random.random3DVectorFromRange(-100.0f, 100.0f) + it.spatial { + position = Random.random3DVectorFromRange(-100.0f, 100.0f) + } it.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) it.intensity = Random.randomFromRange(0.1f, 0.5f) it @@ -71,28 +75,34 @@ class MultiBoxInstancedExample : SceneryBase("MultiBoxInstancedExample") { lights.forEach { scene.addChild(it) } val hullbox = Box(Vector3f(100.0f, 100.0f, 100.0f)) - hullbox.position = Vector3f(0.0f, 0.0f, 0.0f) + hullbox.spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } hullbox.name = "hullbox" - hullbox.material.ambient = Vector3f(0.6f, 0.6f, 0.6f) - hullbox.material.diffuse = Vector3f(0.4f, 0.4f, 0.4f) - hullbox.material.specular = Vector3f(0.0f, 0.0f, 0.0f) - hullbox.material.cullingMode = Material.CullingMode.Front + hullbox.material { + ambient = Vector3f(0.6f, 0.6f, 0.6f) + diffuse = Vector3f(0.4f, 0.4f, 0.4f) + specular = Vector3f(0.0f, 0.0f, 0.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(hullbox) thread { while (running) { - container.rotation.rotateXYZ(0.001f, 0.001f, 0.0f) - container.needsUpdateWorld = true - container.needsUpdate = true - container.updateWorld(true, false) - - val inst = Mesh() - inst.instancedProperties["ModelMatrix"] = { inst.world } - inst.position = Random.random3DVectorFromRange(-40.0f, 40.0f) + container.spatial { + rotation.rotateXYZ(0.001f, 0.001f, 0.0f) + needsUpdateWorld = true + needsUpdate = true + updateWorld(true, false) + } + + val inst = bInstanced.addInstance() + inst.spatial { + position = Random.random3DVectorFromRange(-40.0f, 40.0f) + } inst.parent = container - b.instances.add(inst) - b.instances.removeAt(kotlin.random.Random.nextInt(b.instances.size - 1)) + bInstanced.instances.removeAt(kotlin.random.Random.nextInt(bInstanced.instances.size - 1)) Thread.sleep(20) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/PBLExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/PBLExample.kt index 3837df6a0..a7586dcc1 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/PBLExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/PBLExample.kt @@ -4,6 +4,7 @@ import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.minus import graphics.scenery.utils.extensions.xyz import graphics.scenery.volumes.Colormap @@ -22,17 +23,21 @@ class PBLExample: SceneryBase("PBLExample", windowWidth = 1280, windowHeight = 7 val rowSize = 10f val spheres = (0 until (rowSize*rowSize).toInt()).map { val s = Icosphere(0.4f, 2) - s.position = Vector3f( - floor(it / rowSize), - (it % rowSize.toInt()).toFloat(), - 0.0f) - s.position = s.position - Vector3f( - (rowSize - 1.0f)/2.0f, - (rowSize - 1.0f)/2.0f, - 0.0f) - s.material.roughness = (it / rowSize)/rowSize - s.material.metallic = (it % rowSize.toInt())/rowSize - s.material.diffuse = colormap.sample(it/(rowSize*rowSize)).xyz() + s.spatial { + position = Vector3f( + floor(it / rowSize), + (it % rowSize.toInt()).toFloat(), + 0.0f) + position = position - Vector3f( + (rowSize - 1.0f)/2.0f, + (rowSize - 1.0f)/2.0f, + 0.0f) + } + s.material { + roughness = (it / rowSize)/rowSize + metallic = (it % rowSize.toInt())/rowSize + diffuse = colormap.sample(it/(rowSize*rowSize)).xyz() + } s } @@ -41,19 +46,23 @@ class PBLExample: SceneryBase("PBLExample", windowWidth = 1280, windowHeight = 7 val lightbox = Box(Vector3f(25.0f, 25.0f, 25.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 20.0f) - l.position = Vector3f( - Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), - Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), - Random.randomFromRange(1.0f, 5.0f) - ) + l.spatial { + position = Vector3f( + Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), + Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), + Random.randomFromRange(1.0f, 5.0f) + ) + } l.emissionColor = Random.random3DVectorFromRange(0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -65,7 +74,9 @@ class PBLExample: SceneryBase("PBLExample", windowWidth = 1280, windowHeight = 7 val stageLight = PointLight(radius = 35.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -75,7 +86,9 @@ class PBLExample: SceneryBase("PBLExample", windowWidth = 1280, windowHeight = 7 val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.2f, 12.0f) + spatial { + position = Vector3f(0.0f, 0.2f, 12.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/ProceduralTextureExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/ProceduralTextureExample.kt index 140959641..581ad9e85 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/ProceduralTextureExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/ProceduralTextureExample.kt @@ -3,6 +3,7 @@ package graphics.scenery.tests.examples.advanced import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer +import graphics.scenery.attribute.material.DefaultMaterial import graphics.scenery.textures.Texture import net.imglib2.type.numeric.integer.UnsignedByteType import org.joml.Vector3i @@ -19,7 +20,7 @@ class ProceduralTextureExample : SceneryBase("ProceduralTextureExample") { override fun init() { renderer = hub.add(Renderer.createRenderer(hub, applicationName, scene, 512, 512)) - val boxmaterial = Material() + val boxmaterial = DefaultMaterial() with(boxmaterial) { ambient = Vector3f(1.0f, 0.0f, 0.0f) diffuse = Vector3f(0.0f, 1.0f, 0.0f) @@ -30,7 +31,7 @@ class ProceduralTextureExample : SceneryBase("ProceduralTextureExample") { box.name = "le box du procedurale" with(box) { - box.material = boxmaterial + setMaterial(boxmaterial) scene.addChild(this) } @@ -39,7 +40,9 @@ class ProceduralTextureExample : SceneryBase("ProceduralTextureExample") { } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i, 2.0f * i, 2.0f * i) + light.spatial { + position = Vector3f(2.0f * i, 2.0f * i, 2.0f * i) + } light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 0.5f scene.addChild(light) @@ -47,7 +50,9 @@ class ProceduralTextureExample : SceneryBase("ProceduralTextureExample") { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 3.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 3.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) @@ -62,20 +67,23 @@ class ProceduralTextureExample : SceneryBase("ProceduralTextureExample") { while(true) { if(box.lock.tryLock(2, TimeUnit.MILLISECONDS)) { - box.rotation.rotateY(0.01f) - box.needsUpdate = true + box.spatial { + rotation.rotateY(0.01f) + needsUpdate = true + } textureBuffer.generateProceduralTextureAtTick(ticks, imageSizeX, imageSizeY, imageChannels) - box.material.textures.put("diffuse", - Texture( - Vector3i(imageSizeX, imageSizeY, 1), - channels = imageChannels, contents = textureBuffer, - type = UnsignedByteType())) - - + box.material { + textures.put("diffuse", + Texture( + Vector3i(imageSizeX, imageSizeY, 1), + channels = imageChannels, contents = textureBuffer, + type = UnsignedByteType())) + } box.lock.unlock() + } else { logger.debug("unsuccessful lock") } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/RainbowRibbonExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/RainbowRibbonExample.kt index 578d21bca..e12a26326 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/RainbowRibbonExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/RainbowRibbonExample.kt @@ -4,6 +4,7 @@ import org.joml.* import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.proteins.Protein import graphics.scenery.proteins.RibbonDiagram @@ -31,18 +32,22 @@ class RainbowRibbonExample: SceneryBase("RainbowRibbon", windowWidth = 1280, win val lightbox = Box(Vector3f(500.0f, 500.0f, 500.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 80.0f) - l.position = Vector3f( - Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), - Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), - Random.randomFromRange(1.0f, 5.0f) - ) + l.spatial { + position = Vector3f( + Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), + Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), + Random.randomFromRange(1.0f, 5.0f) + ) + } l.emissionColor = Random.random3DVectorFromRange( 0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -55,7 +60,9 @@ class RainbowRibbonExample: SceneryBase("RainbowRibbon", windowWidth = 1280, win val stageLight = PointLight(radius = 350.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -64,7 +71,9 @@ class RainbowRibbonExample: SceneryBase("RainbowRibbon", windowWidth = 1280, win cameraLight.intensity = 0.8f val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/RibbonExampleSecondaryStructures.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/RibbonExampleSecondaryStructures.kt index d3d202c82..0d9d83bcf 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/RibbonExampleSecondaryStructures.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/RibbonExampleSecondaryStructures.kt @@ -4,6 +4,7 @@ import org.joml.* import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.proteins.Protein import graphics.scenery.proteins.RibbonDiagram @@ -35,7 +36,9 @@ class RibbonExampleSecondaryStructures: SceneryBase("FlatRibbonSketch", windowWi (ss.name == "alpha") -> { ss.children.forEach { alpha -> alpha.children.forEach { - it.material.diffuse.set(alphaColour) + it.ifMaterial { + diffuse.set(alphaColour) + } } } } @@ -44,7 +47,9 @@ class RibbonExampleSecondaryStructures: SceneryBase("FlatRibbonSketch", windowWi beta.children.forEach {child -> //due to the partition of the curve we need to take one step further down the tree child.children.forEach { - it.material.diffuse.set(betaColour) + it.ifMaterial { + diffuse.set(betaColour) + } } } } @@ -54,7 +59,9 @@ class RibbonExampleSecondaryStructures: SceneryBase("FlatRibbonSketch", windowWi coil.children.forEach { child -> //due to the partition of the curve we need to take one step further down the tree child.children.forEach { - it.material.diffuse.set(coilColour) + it.ifMaterial { + diffuse.set(coilColour) + } } } } @@ -67,18 +74,22 @@ class RibbonExampleSecondaryStructures: SceneryBase("FlatRibbonSketch", windowWi val lightbox = Box(Vector3f(100.0f, 100.0f, 100.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 80.0f) - l.position = Vector3f( + l.spatial { + position = Vector3f( Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), Random.randomFromRange(-rowSize/2.0f, rowSize/2.0f), Random.randomFromRange(1.0f, 5.0f) - ) + ) + } l.emissionColor = Random.random3DVectorFromRange( 0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -91,7 +102,9 @@ class RibbonExampleSecondaryStructures: SceneryBase("FlatRibbonSketch", windowWi val stageLight = PointLight(radius = 35.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -100,7 +113,9 @@ class RibbonExampleSecondaryStructures: SceneryBase("FlatRibbonSketch", windowWi cameraLight.intensity = 0.8f val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VRControllerExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VRControllerExample.kt index a8d652b31..a00701f90 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VRControllerExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VRControllerExample.kt @@ -7,6 +7,7 @@ import graphics.scenery.controls.OpenVRHMD import graphics.scenery.controls.TrackedDeviceType import graphics.scenery.controls.TrackerRole import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.plus import graphics.scenery.utils.extensions.times import org.scijava.ui.behaviour.ClickBehaviour @@ -39,7 +40,9 @@ class VRControllerExample : SceneryBase(VRControllerExample::class.java.simpleNa renderer?.toggleVR() val cam: Camera = DetachedHeadCamera(hmd) - cam.position = Vector3f(0.0f, 0.0f, 0.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) @@ -47,7 +50,9 @@ class VRControllerExample : SceneryBase(VRControllerExample::class.java.simpleNa boxes = (0..10).map { val obj = Box(Vector3f(0.1f, 0.1f, 0.1f)) - obj.position = Vector3f(-1.0f + (it + 1) * 0.2f, 1.0f, -0.5f) + obj.spatial { + position = Vector3f(-1.0f + (it + 1) * 0.2f, 1.0f, -0.5f) + } obj } @@ -56,19 +61,21 @@ class VRControllerExample : SceneryBase(VRControllerExample::class.java.simpleNa (0..5).map { val light = PointLight(radius = 15.0f) light.emissionColor = Random.random3DVectorFromRange(0.0f, 1.0f) - light.position = Random.random3DVectorFromRange(-5.0f, 5.0f) + light.spatial { + position = Random.random3DVectorFromRange(-5.0f, 5.0f) + } light.intensity = 1.0f light }.forEach { scene.addChild(it) } hullbox = Box(Vector3f(20.0f, 20.0f, 20.0f), insideNormals = true) - val hullboxMaterial = Material() - hullboxMaterial.ambient = Vector3f(0.6f, 0.6f, 0.6f) - hullboxMaterial.diffuse = Vector3f(0.4f, 0.4f, 0.4f) - hullboxMaterial.specular = Vector3f(0.0f, 0.0f, 0.0f) - hullboxMaterial.cullingMode = Material.CullingMode.Front - hullbox.material = hullboxMaterial + hullbox.material { + ambient = Vector3f(0.6f, 0.6f, 0.6f) + diffuse = Vector3f(0.4f, 0.4f, 0.4f) + specular = Vector3f(0.0f, 0.0f, 0.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(hullbox) @@ -90,10 +97,14 @@ class VRControllerExample : SceneryBase(VRControllerExample::class.java.simpleNa // an intersection with a box, that box is slightly nudged in the direction // of the controller's velocity. controller.update.add { - boxes.forEach { it.material.diffuse = Vector3f(0.9f, 0.5f, 0.5f) } - boxes.filter { box -> controller.children.first().intersects(box) }.forEach { box -> - box.material.diffuse = Vector3f(1.0f, 0.0f, 0.0f) - box.position = (device.velocity ?: Vector3f(0.0f)) * 0.05f + box.position + boxes.forEach { it.materialOrNull()?.diffuse = Vector3f(0.9f, 0.5f, 0.5f) } + boxes.filter { box -> controller.children.first().spatialOrNull()?.intersects(box) ?: false }.forEach { box -> + box.ifMaterial { + diffuse = Vector3f(1.0f, 0.0f, 0.0f) + } + box.ifSpatial { + position = (device.velocity ?: Vector3f(0.0f)) * 0.05f + position + } } } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VertexUpdateExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VertexUpdateExample.kt index 309957f2d..3ef93d68c 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VertexUpdateExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VertexUpdateExample.kt @@ -3,6 +3,7 @@ package graphics.scenery.tests.examples.advanced import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer +import graphics.scenery.attribute.material.Material import java.nio.FloatBuffer import java.util.* import kotlin.concurrent.thread @@ -21,19 +22,24 @@ class VertexUpdateExample : SceneryBase("VertexUpdateExample") { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(70.0f, windowWidth, windowHeight, 1.0f, 1000.0f) scene.addChild(this) } val sphere = Sphere(2.0f, 50) with(sphere) { - material.ambient = Vector3f(1.0f, 1.0f, 1.0f) - material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - material.specular = Vector3f(1.0f, 1.0f, 1.0f) - material.cullingMode = Material.CullingMode.None - - position = Vector3f(0.0f, 0.0f, 0.0f) + material { + ambient = Vector3f(1.0f, 1.0f, 1.0f) + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + specular = Vector3f(1.0f, 1.0f, 1.0f) + cullingMode = Material.CullingMode.None + } + spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } scene.addChild(this) } @@ -41,7 +47,9 @@ class VertexUpdateExample : SceneryBase("VertexUpdateExample") { val lights = (0..2).map { PointLight(radius = 10.0f) }.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 2.0f, 2.0f * i - 2.0f, 2.0f * i - 2.0f) + light.spatial { + position = Vector3f(2.0f * i - 2.0f, 2.0f * i - 2.0f, 2.0f * i - 2.0f) + } light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 150f * (i + 1) light @@ -59,58 +67,62 @@ class VertexUpdateExample : SceneryBase("VertexUpdateExample") { } while (true) { - sphere.rotation.rotateY(0.01f) - sphere.needsUpdate = true - ticks++ - - val vbuffer = ArrayList() - val nbuffer = ArrayList() - - val segments = 50 - val radius = 2.0f - for (i in 0..segments) { - val lat0: Float = Math.PI.toFloat() * (-0.5f + (i.toFloat() - 1.0f) / segments.toFloat()); - val lat1: Float = Math.PI.toFloat() * (-0.5f + i.toFloat() / segments.toFloat()); - - val z0 = Math.sin(lat0.toDouble()).toFloat() - val z1 = Math.sin(lat1.toDouble()).toFloat() - - val zr0 = Math.cos(lat0.toDouble()).toFloat() - val zr1 = Math.cos(lat1.toDouble()).toFloat() - - for (j: Int in 1..segments) { - val lng = 2 * Math.PI.toFloat() * (j - 1) / segments - val x = Math.cos(lng.toDouble()).toFloat() - val y = Math.sin(lng.toDouble()).toFloat() - var r = radius - - if (j % 10 == 0) { - r = radius + Math.sin(ticks / 100.0).toFloat() + sphere.spatial { + rotation.rotateY(0.01f) + needsUpdate = true + } + sphere.geometry { + ticks++ + + val vbuffer = ArrayList() + val nbuffer = ArrayList() + + val segments = 50 + val radius = 2.0f + for (i in 0..segments) { + val lat0: Float = Math.PI.toFloat() * (-0.5f + (i.toFloat() - 1.0f) / segments.toFloat()); + val lat1: Float = Math.PI.toFloat() * (-0.5f + i.toFloat() / segments.toFloat()); + + val z0 = Math.sin(lat0.toDouble()).toFloat() + val z1 = Math.sin(lat1.toDouble()).toFloat() + + val zr0 = Math.cos(lat0.toDouble()).toFloat() + val zr1 = Math.cos(lat1.toDouble()).toFloat() + + for (j: Int in 1..segments) { + val lng = 2 * Math.PI.toFloat() * (j - 1) / segments + val x = Math.cos(lng.toDouble()).toFloat() + val y = Math.sin(lng.toDouble()).toFloat() + var r = radius + + if (j % 10 == 0) { + r = radius + Math.sin(ticks / 100.0).toFloat() + } + vbuffer.add(x * zr0 * r) + vbuffer.add(y * zr0 * r) + vbuffer.add(z0 * r) + + vbuffer.add(x * zr1 * r) + vbuffer.add(y * zr1 * r) + vbuffer.add(z1 * r) + + nbuffer.add(x) + nbuffer.add(y) + nbuffer.add(z0) + + nbuffer.add(x) + nbuffer.add(y) + nbuffer.add(z1) } - vbuffer.add(x * zr0 * r) - vbuffer.add(y * zr0 * r) - vbuffer.add(z0 * r) - - vbuffer.add(x * zr1 * r) - vbuffer.add(y * zr1 * r) - vbuffer.add(z1 * r) - - nbuffer.add(x) - nbuffer.add(y) - nbuffer.add(z0) - - nbuffer.add(x) - nbuffer.add(y) - nbuffer.add(z1) } - } - sphere.vertices = FloatBuffer.wrap(vbuffer.toFloatArray()) - sphere.normals = FloatBuffer.wrap(nbuffer.toFloatArray()) - sphere.recalculateNormals() + vertices = FloatBuffer.wrap(vbuffer.toFloatArray()) + normals = FloatBuffer.wrap(nbuffer.toFloatArray()) + recalculateNormals() + dirty = true + } sphere.boundingBox = sphere.generateBoundingBox() - sphere.dirty = true Thread.sleep(20) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoDecodingExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoDecodingExample.kt index 974b924db..8e8d6280a 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoDecodingExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoDecodingExample.kt @@ -31,14 +31,14 @@ class VideoDecodingExample : SceneryBase("VideoDecodingExample", 600, 600, wantR val cam = DetachedHeadCamera() with(cam) { - position = Vector3f(-4.365f, 0.38f, 0.62f) perspectiveCamera(50.0f, windowWidth, windowHeight) - + spatial { + position = Vector3f(3.213f, 8.264E-1f, -9.844E-1f) + rotation = Quaternionf(3.049E-2, 9.596E-1, -1.144E-1, -2.553E-1) + } scene.addChild(this) } - cam.position = Vector3f(3.213f, 8.264E-1f, -9.844E-1f) - cam.rotation = Quaternionf(3.049E-2, 9.596E-1, -1.144E-1, -2.553E-1) val plane = FullscreenObject() scene.addChild(plane) @@ -80,7 +80,9 @@ class VideoDecodingExample : SceneryBase("VideoDecodingExample", 600, 600, wantR buffer.put(tex).flip() } - plane.material.textures["diffuse"] = Texture(Vector3i(width, height, 1), 4, contents = buffer, mipmap = true) + plane.material { + textures["diffuse"] = Texture(Vector3i(width, height, 1), 4, contents = buffer, mipmap = true) + } } override fun main() { @@ -100,4 +102,4 @@ class VideoDecodingExample : SceneryBase("VideoDecodingExample", 600, 600, wantR } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoRecordingExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoRecordingExample.kt index 8c7b6b704..0815a0049 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoRecordingExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/advanced/VideoRecordingExample.kt @@ -23,20 +23,26 @@ class VideoRecordingExample: SceneryBase("VideoRecordingExample") { val box = Box(Vector3f(1.0f, 1.0f, 1.0f)) with(box) { - box.name = "le box du win" - box.material.textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", TexturedCubeExample::class.java)) + name = "le box du win" + material { + textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", TexturedCubeExample::class.java)) + } scene.addChild(this) } val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial { + position = Vector3f(0.0f, 0.0f, 2.0f) + } light.intensity = 5.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -44,9 +50,11 @@ class VideoRecordingExample: SceneryBase("VideoRecordingExample") { thread { while (true) { - box.rotation.rotateY(Random.randomFromRange(-0.04f, 0.04f)) - box.rotation.rotateZ(Random.randomFromRange(-0.04f, 0.04f)) - box.needsUpdate = true + box.spatial { + rotation.rotateY(Random.randomFromRange(-0.04f, 0.04f)) + rotation.rotateZ(Random.randomFromRange(-0.04f, 0.04f)) + needsUpdate = true + } Thread.sleep(20) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/ArcballExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/ArcballExample.kt index 04abc76d6..18208ff72 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/ArcballExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/ArcballExample.kt @@ -21,7 +21,9 @@ class ArcballExample : SceneryBase("ArcballExample") { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 2.5f) + spatial { + position = Vector3f(0.0f, 0.0f, 2.5f) + } perspectiveCamera(70.0f, windowWidth, windowHeight) targeted = true @@ -37,12 +39,15 @@ class ArcballExample : SceneryBase("ArcballExample") { val box = Box(Vector3f(1.0f, 1.0f, 1.0f)) with(box) { - box.position = Vector3f(0.0f, 0.0f, 0.0f) - - material.ambient = Vector3f(1.0f, 0.0f, 0.0f) - material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) - material.specular = Vector3f(1.0f, 1.0f, 1.0f) - material.textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", TexturedCubeExample::class.java)) + spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } + material { + ambient = Vector3f(1.0f, 0.0f, 0.0f) + diffuse = Vector3f(0.0f, 1.0f, 0.0f) + textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", TexturedCubeExample::class.java)) + specular = Vector3f(1.0f, 1.0f, 1.0f) + } scene.addChild(this) } @@ -50,15 +55,21 @@ class ArcballExample : SceneryBase("ArcballExample") { val lights = (0..2).map { PointLight(radius = 15.0f) }.map { light -> - light.position = Random.random3DVectorFromRange(-3.0f, 3.0f) + light.spatial { + position = Random.random3DVectorFromRange(-3.0f, 3.0f) + } light.emissionColor = Random.random3DVectorFromRange(0.2f, 0.8f) light.intensity = Random.randomFromRange(0.1f, 0.8f) light } val floor = Box(Vector3f(500.0f, 0.05f, 500.0f)) - floor.position = Vector3f(0.0f, -1.0f, 0.0f) - floor.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) + floor.spatial { + position = Vector3f(0.0f, -1.0f, 0.0f) + } + floor.material { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + } scene.addChild(floor) lights.forEach(scene::addChild) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/ArrowExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/ArrowExample.kt index a818ae94a..17ca9238e 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/ArrowExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/ArrowExample.kt @@ -4,6 +4,8 @@ import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.primitives.Arrow +import graphics.scenery.attribute.material.DefaultMaterial +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.minus import kotlin.concurrent.thread import kotlin.math.PI @@ -33,8 +35,10 @@ class ArrowExample : SceneryBase("ArrowExample") { private fun setupScene() { //boundaries of our world val hull = Box(Vector3f(50.0f, 50.0f, 50.0f), insideNormals = true) - hull.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - hull.material.cullingMode = Material.CullingMode.Front + hull.material { + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + cullingMode = Material.CullingMode.Front + } scene.addChild(hull) //lights and camera @@ -48,13 +52,13 @@ class ArrowExample : SceneryBase("ArrowExample") { scene.addChild(l) pl = pl.plus(l) } - pl[0].position = Vector3f(0f,10f,0f) - pl[1].position = Vector3f(0f,-10f,0f) - pl[2].position = Vector3f(-10f,0f,0f) - pl[3].position = Vector3f(10f,0f,0f) + pl[0].spatial().position = Vector3f(0f,10f,0f) + pl[1].spatial().position = Vector3f(0f,-10f,0f) + pl[2].spatial().position = Vector3f(-10f,0f,0f) + pl[3].spatial().position = Vector3f(10f,0f,0f) val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial().position = Vector3f(0.0f, 0.0f, 15.0f) cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) } @@ -62,13 +66,13 @@ class ArrowExample : SceneryBase("ArrowExample") { private fun useScene() { //we shall have faint and bright vectors... - val matBright = Material() + val matBright = DefaultMaterial() matBright.diffuse = Vector3f(0.0f, 1.0f, 0.0f) matBright.ambient = Vector3f(1.0f, 1.0f, 1.0f) matBright.specular = Vector3f(1.0f, 1.0f, 1.0f) matBright.cullingMode = Material.CullingMode.None - val matFaint = Material() + val matFaint = DefaultMaterial() matFaint.diffuse = Vector3f(0.0f, 0.6f, 0.6f) matFaint.ambient = Vector3f(1.0f, 1.0f, 1.0f) matFaint.specular = Vector3f(1.0f, 1.0f, 1.0f) @@ -94,8 +98,10 @@ class ArrowExample : SceneryBase("ArrowExample") { // ========= this is how you create an Arrow ========= val a = Arrow(currPos - lastPos) //shape of the vector itself - a.position = lastPos //position/base of the vector - a.material = matFaint //usual stuff follows... + a.spatial { + position = lastPos //position/base of the vector + } + a.addAttribute(Material::class.java, matFaint) //usual stuff follows... a.edgeWidth = 0.5f scene.addChild(a) @@ -115,9 +121,9 @@ class ArrowExample : SceneryBase("ArrowExample") { thread { var i = 0 while (true) { - al[i].material = matFaint + al[i].addAttribute(Material::class.java, matFaint) i = (i+1).rem(arrowsInCircle) - al[i].material = matBright + al[i].addAttribute(Material::class.java, matBright) Thread.sleep(150) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveCatmullRomExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveCatmullRomExample.kt index ed9f12143..fa22431e3 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveCatmullRomExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveCatmullRomExample.kt @@ -6,6 +6,7 @@ import graphics.scenery.backends.Renderer import graphics.scenery.geometry.CatmullRomSpline import graphics.scenery.geometry.Curve import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material /** * Just a quick example of a CatmullRomSpline with a triangle as a baseShape. @@ -50,18 +51,22 @@ class CurveCatmullRomExample: SceneryBase("CurveCatmullRomExample", windowWidth val lightbox = Box(Vector3f(25.0f, 25.0f, 25.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 20.0f) - l.position = Vector3f( + l.spatial { + position = Vector3f( Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(1.0f, 5.0f) - ) + ) + } l.emissionColor = Random.random3DVectorFromRange(0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -73,7 +78,9 @@ class CurveCatmullRomExample: SceneryBase("CurveCatmullRomExample", windowWidth val stageLight = PointLight(radius = 10.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -82,7 +89,9 @@ class CurveCatmullRomExample: SceneryBase("CurveCatmullRomExample", windowWidth cameraLight.intensity = 0.8f val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) cam.addChild(cameraLight) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveDifferentBaseShapes.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveDifferentBaseShapes.kt index 17d553660..c816dcca1 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveDifferentBaseShapes.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveDifferentBaseShapes.kt @@ -6,6 +6,7 @@ import graphics.scenery.backends.Renderer import graphics.scenery.geometry.Curve import graphics.scenery.geometry.UniformBSpline import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material /** * Example of a curve with different baseShapes. @@ -70,18 +71,22 @@ class CurveDifferentBaseShapes: SceneryBase("CurveDifferentBaseShapes", windowWi val lightbox = Box(Vector3f(25.0f, 25.0f, 25.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 20.0f) - l.position = Vector3f( + l.spatial { + position = Vector3f( Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(1.0f, 5.0f) - ) + ) + } l.emissionColor = Random.random3DVectorFromRange( 0.2f, 0.8f) l.intensity = Random.randomFromRange(0.2f, 0.8f) @@ -93,7 +98,9 @@ class CurveDifferentBaseShapes: SceneryBase("CurveDifferentBaseShapes", windowWi val stageLight = PointLight(radius = 10.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -102,7 +109,9 @@ class CurveDifferentBaseShapes: SceneryBase("CurveDifferentBaseShapes", windowWi cameraLight.intensity = 0.8f val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveUniformBSplineExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveUniformBSplineExample.kt index 681f0b102..895b8925d 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveUniformBSplineExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/CurveUniformBSplineExample.kt @@ -6,6 +6,7 @@ import graphics.scenery.backends.Renderer import graphics.scenery.geometry.Curve import graphics.scenery.geometry.UniformBSpline import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material /** * Just a quick example a UniformBSpline with a triangle as a baseShape. @@ -50,14 +51,16 @@ class CurveUniformBSplineExample: SceneryBase("CurveUniformBSplineExample", wind val lightbox = Box(Vector3f(25.0f, 25.0f, 25.0f), insideNormals = true) lightbox.name = "Lightbox" - lightbox.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - lightbox.material.roughness = 1.0f - lightbox.material.metallic = 0.0f - lightbox.material.cullingMode = Material.CullingMode.None + lightbox.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + roughness = 1.0f + metallic = 0.0f + cullingMode = Material.CullingMode.None + } scene.addChild(lightbox) val lights = (0 until 8).map { val l = PointLight(radius = 20.0f) - l.position = Vector3f( + l.spatial().position = Vector3f( Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(-rowSize / 2.0f, rowSize / 2.0f), Random.randomFromRange(1.0f, 5.0f) @@ -73,7 +76,7 @@ class CurveUniformBSplineExample: SceneryBase("CurveUniformBSplineExample", wind val stageLight = PointLight(radius = 10.0f) stageLight.name = "StageLight" stageLight.intensity = 0.5f - stageLight.position = Vector3f(0.0f, 0.0f, 5.0f) + stageLight.spatial().position = Vector3f(0.0f, 0.0f, 5.0f) scene.addChild(stageLight) val cameraLight = PointLight(radius = 5.0f) @@ -82,7 +85,7 @@ class CurveUniformBSplineExample: SceneryBase("CurveUniformBSplineExample", wind cameraLight.intensity = 0.8f val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial().position = Vector3f(0.0f, 0.0f, 15.0f) cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/DistanceMeasurement.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/DistanceMeasurement.kt index 797a2e1d7..2ce41fbfe 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/DistanceMeasurement.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/DistanceMeasurement.kt @@ -6,6 +6,8 @@ import graphics.scenery.controls.behaviours.SelectCommand import graphics.scenery.numerics.Random import graphics.scenery.primitives.Line import graphics.scenery.primitives.TextBoard +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.spatial.Spatial import graphics.scenery.utils.extensions.plus import org.joml.Vector3f import org.joml.Vector4f @@ -25,24 +27,32 @@ class DistanceMeasurement: SceneryBase("RulerPick", wantREPL = true) { for(i in 0 until 200) { val s = Icosphere(Random.randomFromRange(0.04f, 0.2f), 2) - s.position = Random.random3DVectorFromRange(-5.0f, 5.0f) + s.spatial { + position = Random.random3DVectorFromRange(-5.0f, 5.0f) + } scene.addChild(s) } val box = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - box.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - box.material.cullingMode = Material.CullingMode.Front + box.material { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(box) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial { + position = Vector3f(0.0f, 0.0f, 2.0f) + } light.intensity = 1.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -52,41 +62,45 @@ class DistanceMeasurement: SceneryBase("RulerPick", wantREPL = true) { override fun inputSetup() { super.inputSetup() - var lastNode = Node() + var lastSpatial: Spatial? = null val wiggle: (Scene.RaycastResult, Int, Int) -> Unit = { result, _, _ -> result.matches.firstOrNull()?.let { nearest -> - val originalPosition = Vector3f(nearest.node.position) - thread { - for(i in 0 until 200) { - nearest.node.position = originalPosition + Random.random3DVectorFromRange(-0.05f, 0.05f) - Thread.sleep(2) + nearest.node.ifSpatial { + val originalPosition = Vector3f(this.position) + thread { + for(i in 0 until 200) { + this.position = originalPosition + Random.random3DVectorFromRange(-0.05f, 0.05f) + Thread.sleep(2) + } } + if(!secondNode) { + lastSpatial = this + } + else { + val position0 = lastSpatial?.position ?: Vector3f(0f, 0f, 0f) + val position1 = this.position + val lastToPresent = Vector3f() + logger.info("distance: ${position1.sub(position0, lastToPresent).length()}") + val line = Line(simple = true) + line.addPoint(position0) + line.addPoint(position1) + scene.addChild(line) + val board = TextBoard() + board.text = "Distance: ${lastToPresent.length()} units" + board.name = "DistanceTextBoard" + board.transparent = 0 + board.fontColor = Vector4f(0.0f, 0.0f, 0.0f, 1.0f) + board.backgroundColor = Vector4f(100f, 100f, 100f, 1.0f) + val boardPosition = Vector3f() + position0.add(position1, boardPosition) + board.spatial { + position = boardPosition.mul(0.5f) + scale = Vector3f(0.5f, 0.5f, 0.5f) + } + scene.addChild(board) + } + secondNode = !secondNode } - if(!secondNode) { - lastNode = nearest.node - } - else { - val position0 = lastNode.position - val position1 = nearest.node.position - val lastToPresent = Vector3f() - logger.info("distance: ${position1.sub(position0, lastToPresent).length()}") - val line = Line(simple = true) - line.addPoint(position0) - line.addPoint(position1) - scene.addChild(line) - val board = TextBoard() - board.text = "Distance: ${lastToPresent.length()} units" - board.name = "DistanceTextBoard" - board.transparent = 0 - board.fontColor = Vector4f(0.0f, 0.0f, 0.0f, 1.0f) - board.backgroundColor = Vector4f(100f, 100f, 100f, 1.0f) - val boardPosition = Vector3f() - position0.add(position1, boardPosition) - board.position = boardPosition.mul(0.5f) - board.scale = Vector3f(0.5f, 0.5f, 0.5f) - scene.addChild(board) - } - secondNode = !secondNode } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/EdgeBundlerExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/EdgeBundlerExample.kt index 6eb7fa2b3..7efcb7396 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/EdgeBundlerExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/EdgeBundlerExample.kt @@ -7,6 +7,7 @@ import graphics.scenery.compute.EdgeBundler import graphics.scenery.numerics.Random import graphics.scenery.primitives.Line import graphics.scenery.primitives.LinePair +import graphics.scenery.attribute.material.Material import kotlin.concurrent.thread import kotlin.math.PI import kotlin.math.cos @@ -31,9 +32,11 @@ class EdgeBundlerExample : SceneryBase("EdgeBundlerExample") { val colorMap = Array(eb.numberOfClusters) { Random.random3DVectorFromRange(0.2f, 0.8f) } eb.getLinePairsAndClusters().forEach { (track, clusterId) -> - track.material.ambient = colorMap[clusterId] - track.material.diffuse = colorMap[clusterId] - track.material.specular = colorMap[clusterId] + track.material { + ambient = colorMap[clusterId] + diffuse = colorMap[clusterId] + specular = colorMap[clusterId] + } scene.addChild(track) } } @@ -43,8 +46,10 @@ class EdgeBundlerExample : SceneryBase("EdgeBundlerExample") { */ private fun initScene() { val hull = Box(Vector3f(250.0f, 250.0f, 250.0f), insideNormals = true) - hull.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - hull.material.cullingMode = Material.CullingMode.Front + hull.material { + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + cullingMode = Material.CullingMode.Front + } scene.addChild(hull) val lights = (0 until 3).map { val l = PointLight(radius = 150.0f) @@ -55,7 +60,9 @@ class EdgeBundlerExample : SceneryBase("EdgeBundlerExample") { } val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, -2.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, -2.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) @@ -64,7 +71,7 @@ class EdgeBundlerExample : SceneryBase("EdgeBundlerExample") { while(true) { val t = runtime/100.0f lights.forEachIndexed { i, pointLight -> - pointLight.position = Vector3f( + pointLight.spatial().position = Vector3f( 33.0f* sin(2.0f*i*PI/3.0f+t*PI/50.0f).toFloat(), 0.0f, -33.0f* cos(2.0f*i*PI/3.0f+t*PI/50.0f).toFloat()) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/FontRenderingExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/FontRenderingExample.kt index 962e51357..716acad11 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/FontRenderingExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/FontRenderingExample.kt @@ -4,6 +4,7 @@ import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.primitives.TextBoard +import graphics.scenery.attribute.material.Material import org.joml.Vector4f import kotlin.concurrent.thread @@ -23,15 +24,19 @@ class FontRenderingExample: SceneryBase("FontRenderingExample", windowWidth = 12 val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(3.3f, 0.0f, 4.0f) + spatial { + position = Vector3f(3.3f, 0.0f, 4.0f) + } perspectiveCamera(70.0f, windowWidth, windowHeight, 1.0f, 1000.0f) scene.addChild(this) } val box = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - box.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - box.material.cullingMode = Material.CullingMode.Front + box.material { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(box) val board = TextBoard() @@ -40,13 +45,15 @@ class FontRenderingExample: SceneryBase("FontRenderingExample", windowWidth = 12 board.transparent = 0 board.fontColor = Vector4f(0.0f, 0.0f, 0.0f, 1.0f) board.backgroundColor = Vector4f(100.0f, 100.0f, 0.0f, 1.0f) - board.position = Vector3f(-4.0f, 0.0f, -4.9f) - board.scale = Vector3f(2.0f, 2.0f, 2.0f) + board.spatial { + position = Vector3f(-4.0f, 0.0f, -4.9f) + scale = Vector3f(2.0f, 2.0f, 2.0f) + } scene.addChild(board) thread { - while(board.dirty) { Thread.sleep(200) } + while(board.geometry().dirty) { Thread.sleep(200) } val text = arrayOf( "this is scenery.", diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/IntersectionExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/IntersectionExample.kt index 02594c0ac..f0c79b234 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/IntersectionExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/IntersectionExample.kt @@ -3,6 +3,8 @@ package graphics.scenery.tests.examples.basic import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer +import graphics.scenery.attribute.material.DefaultMaterial +import graphics.scenery.attribute.material.Material import kotlin.concurrent.thread /** @@ -14,7 +16,7 @@ class IntersectionExample: SceneryBase("IntersectionExample") { override fun init() { renderer = hub.add(Renderer.createRenderer(hub, applicationName, scene, 512, 512)) - val boxmaterial = Material() + val boxmaterial = DefaultMaterial() with(boxmaterial) { ambient = Vector3f(1.0f, 0.0f, 0.0f) diffuse = Vector3f(0.0f, 1.0f, 0.0f) @@ -29,24 +31,30 @@ class IntersectionExample: SceneryBase("IntersectionExample") { val box2 = Box(Vector3f(1.0f, 1.0f, 1.0f)) with(box) { - box.material = boxmaterial + box.addAttribute(Material::class.java, boxmaterial) scene.addChild(this) } with(box2) { - position = Vector3f(-1.5f, 0.0f, 0.0f) - box.material = boxmaterial + spatial { + position = Vector3f(-1.5f, 0.0f, 0.0f) + } + box2.addAttribute(Material::class.java, boxmaterial) scene.addChild(this) } val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial { + position = Vector3f(0.0f, 0.0f, 2.0f) + } light.intensity = 100.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -54,10 +62,10 @@ class IntersectionExample: SceneryBase("IntersectionExample") { thread { while (true) { - if (box.intersects(box2)) { - box.material.diffuse = Vector3f(1.0f, 0.0f, 0.0f) + if (box.spatial().intersects(box2)) { + box.material().diffuse = Vector3f(1.0f, 0.0f, 0.0f) } else { - box.material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) + box.material().diffuse = Vector3f(0.0f, 1.0f, 0.0f) } Thread.sleep(20) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/LineExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/LineExample.kt index 317013749..3598be3eb 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/LineExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/LineExample.kt @@ -5,6 +5,7 @@ import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random import graphics.scenery.primitives.Line +import graphics.scenery.attribute.material.Material import kotlin.concurrent.thread /** @@ -23,8 +24,10 @@ class LineExample : SceneryBase("LineExample") { renderer = hub.add(Renderer.createRenderer(hub, applicationName, scene, windowWidth, windowHeight)) val hull = Box(Vector3f(50.0f, 50.0f, 50.0f), insideNormals = true) - hull.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - hull.material.cullingMode = Material.CullingMode.Front + hull.material { + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + cullingMode = Material.CullingMode.Front + } scene.addChild(hull) val line = Line(transparent = false) @@ -32,11 +35,15 @@ class LineExample : SceneryBase("LineExample") { line.addPoint(Vector3f(0.0f, 0.0f, 0.0f)) line.addPoint(Vector3f(5.0f, 5.0f, 5.0f)) - line.material.ambient = Vector3f(1.0f, 0.0f, 0.0f) - line.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - line.material.specular = Vector3f(1.0f, 1.0f, 1.0f) + line.material { + ambient = Vector3f(1.0f, 0.0f, 0.0f) + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + specular = Vector3f(1.0f, 1.0f, 1.0f) + } + line.spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } - line.position = Vector3f(0.0f, 0.0f, 0.0f) line.edgeWidth = 0.02f scene.addChild(line) @@ -51,7 +58,9 @@ class LineExample : SceneryBase("LineExample") { } val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 15.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) cam.target = Vector3f(0.0f, 0.0f, 0.0f) @@ -73,10 +82,12 @@ class LineExample : SceneryBase("LineExample") { while(true) { val t = runtime/100 lights.forEachIndexed { i, pointLight -> - pointLight.position = Vector3f( - 3.0f*Math.sin(2*i*Math.PI/3.0f+t*Math.PI/50).toFloat(), - 0.0f, - -3.0f*Math.cos(2*i*Math.PI/3.0f+t*Math.PI/50).toFloat()) + pointLight.spatial { + position = Vector3f( + 3.0f*Math.sin(2*i*Math.PI/3.0f+t*Math.PI/50).toFloat(), + 0.0f, + -3.0f*Math.cos(2*i*Math.PI/3.0f+t*Math.PI/50).toFloat()) + } } Thread.sleep(20) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/MeshAddingExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/MeshAddingExample.kt index 09c783782..474b663b1 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/MeshAddingExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/MeshAddingExample.kt @@ -3,6 +3,7 @@ package graphics.scenery.tests.examples.basic import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.controls.behaviours.MeshAdder +import graphics.scenery.attribute.material.Material import org.joml.Vector3f /** @@ -15,19 +16,23 @@ class MeshAddingExample: SceneryBase("CreationSketch", wantREPL = true) { renderer = hub.add(Renderer.createRenderer(hub, applicationName, scene, 512, 512)) val box = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - box.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - box.material.cullingMode = Material.CullingMode.Front + box.material { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(box) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 1.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/PointCloudExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/PointCloudExample.kt index 5b43ac56a..2d77d0b76 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/PointCloudExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/PointCloudExample.kt @@ -5,6 +5,7 @@ import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random import graphics.scenery.primitives.PointCloud +import graphics.scenery.attribute.material.Material /** * Simple example to demonstrate the drawing of a 3D point cloud. @@ -20,8 +21,10 @@ class PointCloudExample : SceneryBase("PointCloudExample") { renderer?.pushMode = true val hull = Box(Vector3f(20.0f, 20.0f, 20.0f), insideNormals = true) - hull.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - hull.material.cullingMode = Material.CullingMode.Front + hull.material { + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + cullingMode = Material.CullingMode.Front + } scene.addChild(hull) val lights = Light.createLightTetrahedron(spread = 5.0f, radius = 15.0f) @@ -33,7 +36,9 @@ class PointCloudExample : SceneryBase("PointCloudExample") { } val cam: Camera = DetachedHeadCamera() - cam.position = Vector3f(0.0f, 0.0f, 5.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } cam.perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(cam) @@ -42,12 +47,14 @@ class PointCloudExample : SceneryBase("PointCloudExample") { with(pointCloud) { readFromOBJ( TexturedCubeExample::class.java.getResource("models/sphere.obj").file, importMaterials = false) name = "Sphere Mesh" - for(i in 0 until pointCloud.texcoords.limit()) { - pointCloud.texcoords.put(i, Random.randomFromRange(10.0f, 25.0f)) - } + pointCloud.geometry { + for(i in 0 until texcoords.limit()) { + texcoords.put(i, Random.randomFromRange(10.0f, 25.0f)) + } - for(i in 0 until pointCloud.normals.limit()) { - pointCloud.normals.put(i, Random.randomFromRange(0.2f, 0.8f)) + for(i in 0 until normals.limit()) { + normals.put(i, Random.randomFromRange(0.2f, 0.8f)) + } } setupPointCloud() diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/ReaderExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/ReaderExample.kt index dde56bba2..eac4c180f 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/ReaderExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/ReaderExample.kt @@ -58,8 +58,12 @@ class ReaderExample : SceneryBase("ReaderExample", 1280, 720) { renderer?.pushMode = true val b = Box(Vector3f(50.0f, 0.2f, 50.0f)) - b.position = Vector3f(0.0f, -1.0f, 0.0f) - b.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) + b.spatial { + position = Vector3f(0.0f, -1.0f, 0.0f) + } + b.material { + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + } scene.addChild(b) val tetrahedron = listOf( @@ -85,11 +89,13 @@ class ReaderExample : SceneryBase("ReaderExample", 1280, 720) { } } else { logger.warn("No file selected, returning empty node.") - Node("empty") + DefaultNode("empty") } loadedFilename = files.firstOrNull() - loadedObject.fitInto(6.0f, scaleUp = false) + loadedObject.ifSpatial { + fitInto(6.0f, scaleUp = false) + } scene.addChild(loadedObject) @@ -97,25 +103,29 @@ class ReaderExample : SceneryBase("ReaderExample", 1280, 720) { bg.node = loadedObject tetrahedron.mapIndexed { i, position -> - lights[i].position = position * 5.0f + lights[i].spatial().position = position * 5.0f lights[i].emissionColor = Random.random3DVectorFromRange(0.8f, 1.0f) lights[i].intensity = 0.5f scene.addChild(lights[i]) } with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } thread { - while(!loadedObject.initialized) { + while(loadedObject.initialized == false) { Thread.sleep(200) } - loadedObject.putAbove(Vector3f(0.0f, -0.3f, 0.0f)) + loadedObject.ifSpatial { + putAbove(Vector3f(0.0f, -0.3f, 0.0f)) + } hmd?.events?.onDeviceConnect?.add { hmd, device, timestamp -> if(device.type == TrackedDeviceType.Controller) { @@ -171,8 +181,10 @@ class ReaderExample : SceneryBase("ReaderExample", 1280, 720) { delay = minOf((delay / scaleFactor).toLong(), 2000L) cam.showMessage("Speed: ${String.format("%.2f", (1000f/delay.toFloat()))} vol/s") } else { - val scale = minOf(loadedObject.scale.x() * 1.2f, 3.0f) - loadedObject.scale = Vector3f(1.0f) * scale + loadedObject.ifSpatial { + val scale = minOf(this.scale.x() * 1.2f, 3.0f) + this.scale = Vector3f(1.0f) * scale + } } } @@ -184,8 +196,10 @@ class ReaderExample : SceneryBase("ReaderExample", 1280, 720) { delay = maxOf((delay * scaleFactor).toLong(), 5L) cam.showMessage("Speed: ${String.format("%.2f", (1000f/delay.toFloat()))} vol/s") } else { - val scale = maxOf(loadedObject.scale.x() / 1.2f, 0.1f) - loadedObject.scale = Vector3f(1.0f) * scale + loadedObject.ifSpatial { + val scale = maxOf(this.scale.x() / 1.2f, 0.1f) + this.scale = Vector3f(1.0f) * scale + } } } @@ -247,8 +261,10 @@ class ReaderExample : SceneryBase("ReaderExample", 1280, 720) { when(extension) { "obj", "stl" -> { loadedObject = Mesh().readFrom(file.toFile().absolutePath) - loadedObject.centerOn(Vector3f(0.0f)) - loadedObject.fitInto(6.0f, scaleUp = false) + loadedObject.ifSpatial { + centerOn(Vector3f(0.0f)) + fitInto(6.0f, scaleUp = false) + } } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/RulerExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/RulerExample.kt index c0a86e6ed..038630142 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/RulerExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/RulerExample.kt @@ -3,6 +3,7 @@ package graphics.scenery.tests.examples.basic import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.controls.behaviours.Ruler +import graphics.scenery.attribute.material.Material import org.joml.Vector3f /** @@ -16,19 +17,23 @@ class RulerExample: SceneryBase("RulerSketch", wantREPL = true) { renderer = hub.add(Renderer.createRenderer(hub, applicationName, scene, 512, 512)) val box = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - box.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) - box.material.cullingMode = Material.CullingMode.Front + box.material { + diffuse = Vector3f(1.0f, 1.0f, 1.0f) + cullingMode = Material.CullingMode.Front + } scene.addChild(box) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 1.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/SponzaExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/SponzaExample.kt index 64f23d8e7..c91203f58 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/SponzaExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/SponzaExample.kt @@ -27,24 +27,28 @@ class SponzaExample : SceneryBase("SponzaExample", windowWidth = 1280, windowHei val cam: Camera = DetachedHeadCamera() with(cam) { - cam.position = Vector3f(0.0f, 1.0f, 0.0f) - cam.perspectiveCamera(50.0f, windowWidth, windowHeight) + spatial { + position = Vector3f(0.0f, 1.0f, 0.0f) + } + perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } val lights = (0 until 128).map { Box(Vector3f(0.1f, 0.1f, 0.1f)) }.map { - it.position = Vector3f( - Random.randomFromRange(-6.0f, 6.0f), - Random.randomFromRange(0.1f, 1.2f), - Random.randomFromRange(-10.0f, 10.0f) - ) - - it.material.diffuse = Random.random3DVectorFromRange(0.1f, 0.9f) - + it.spatial { + position = Vector3f( + Random.randomFromRange(-6.0f, 6.0f), + Random.randomFromRange(0.1f, 1.2f), + Random.randomFromRange(-10.0f, 10.0f) + ) + } val light = PointLight(radius = Random.randomFromRange(0.5f, 4.0f)) - light.emissionColor = it.material.diffuse + it.material { + diffuse = Random.random3DVectorFromRange(0.1f, 0.9f) + light.emissionColor = diffuse + } light.intensity = Random.randomFromRange(0.1f, 0.5f) it.addChild(light) @@ -56,8 +60,10 @@ class SponzaExample : SceneryBase("SponzaExample", windowWidth = 1280, windowHei val mesh = Mesh() with(mesh) { readFromOBJ(getDemoFilesPath() + "/sponza.obj", importMaterials = true) - rotation.rotateY(Math.PI.toFloat() / 2.0f) - scale = Vector3f(0.01f, 0.01f, 0.01f) + spatial { + rotation.rotateY(Math.PI.toFloat() / 2.0f) + scale = Vector3f(0.01f, 0.01f, 0.01f) + } name = "Sponza Mesh" scene.addChild(this) @@ -65,7 +71,9 @@ class SponzaExample : SceneryBase("SponzaExample", windowWidth = 1280, windowHei val desc = TextBoard() desc.text = "sponza" - desc.position = Vector3f(-2.0f, -0.1f, -4.0f) + desc.spatial { + position = Vector3f(-2.0f, -0.1f, -4.0f) + } desc.fontColor = Vector4f(0.0f, 0.0f, 0.0f, 1.0f) desc.backgroundColor = Vector4f(0.1f, 0.1f, 0.1f, 1.0f) desc.transparent = 0 @@ -78,12 +86,13 @@ class SponzaExample : SceneryBase("SponzaExample", windowWidth = 1280, windowHei lights.mapIndexed { i, light -> val phi = (Math.PI * 2.0f * ticks / 1000.0f) % (Math.PI * 2.0f) - light.position = Vector3f( - light.position.x(), - 5.0f * Math.cos(phi + (i * 0.5f)).toFloat() + 5.2f, - light.position.z()) - - light.children.forEach { it.needsUpdateWorld = true } + light.spatial { + position = Vector3f( + position.x(), + 5.0f * Math.cos(phi + (i * 0.5f)).toFloat() + 5.2f, + position.z()) + } + light.children.forEach { it.spatialOrNull()?.needsUpdateWorld = true } } ticks++ diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/SwingTexturedCubeExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/SwingTexturedCubeExample.kt index 2131684f8..204e12e37 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/SwingTexturedCubeExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/SwingTexturedCubeExample.kt @@ -3,6 +3,8 @@ package graphics.scenery.tests.examples.basic import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer +import graphics.scenery.attribute.material.DefaultMaterial +import graphics.scenery.attribute.material.Material import graphics.scenery.textures.Texture import graphics.scenery.utils.Image import graphics.scenery.utils.SceneryJPanel @@ -30,7 +32,7 @@ class SwingTexturedCubeExample : SceneryBase("SwingTexturedCubeExample", windowW renderer = hub.add(Renderer.createRenderer(hub, applicationName, scene, windowWidth, windowHeight, embedIn = sceneryPanel)) renderer?.pushMode = true - val boxmaterial = Material() + val boxmaterial = DefaultMaterial() with(boxmaterial) { ambient = Vector3f(1.0f, 0.0f, 0.0f) diffuse = Vector3f(0.0f, 1.0f, 0.0f) @@ -42,19 +44,23 @@ class SwingTexturedCubeExample : SceneryBase("SwingTexturedCubeExample", windowW box.name = "le box du win" with(box) { - box.material = boxmaterial + box.addAttribute(Material::class.java, boxmaterial) scene.addChild(this) } val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial { + position = Vector3f(0.0f, 0.0f, 2.0f) + } light.intensity = 5.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -62,8 +68,10 @@ class SwingTexturedCubeExample : SceneryBase("SwingTexturedCubeExample", windowW thread { while (true) { - box.rotation.rotateY(0.01f) - box.needsUpdate = true + box.spatial { + rotation.rotateY(0.01f) + needsUpdate = true + } Thread.sleep(20) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/basic/TexturedCubeExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/basic/TexturedCubeExample.kt index a3ba701b1..a13c52742 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/basic/TexturedCubeExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/basic/TexturedCubeExample.kt @@ -19,20 +19,24 @@ class TexturedCubeExample : SceneryBase("TexturedCubeExample") { val box = Box(Vector3f(1.0f, 1.0f, 1.0f)) box.name = "le box du win" - box.material.textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", this::class.java)) - box.material.metallic = 0.3f - box.material.roughness = 0.9f + box.material { + textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", this::class.java)) + metallic = 0.3f + roughness = 0.9f + } scene.addChild(box) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 5.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -40,8 +44,10 @@ class TexturedCubeExample : SceneryBase("TexturedCubeExample") { thread { while (running) { - box.rotation.rotateY(0.01f) - box.needsUpdate = true + box.spatial { + rotation.rotateY(0.01f) + needsUpdate = true + } Thread.sleep(20) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/cluster/BileExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/cluster/BileExample.kt index 93e54458d..7c3ebce3f 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/cluster/BileExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/cluster/BileExample.kt @@ -7,6 +7,7 @@ import graphics.scenery.controls.TrackedStereoGlasses import graphics.scenery.net.NodePublisher import graphics.scenery.net.NodeSubscriber import graphics.scenery.Mesh +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.times /** @@ -29,17 +30,21 @@ class BileExample: SceneryBase("Bile Canaliculi example") { val cam: Camera = DetachedHeadCamera(hmd) with(cam) { - position = Vector3f(.0f, -0.4f, 5.0f) + spatial { + position = Vector3f(.0f, -0.4f, 5.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } val shell = Box(Vector3f(120.0f, 120.0f, 120.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.Front - shell.material.diffuse = Vector3f(0.0f, 0.0f, 0.0f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) + shell.material { + cullingMode = Material.CullingMode.Front + diffuse = Vector3f(0.0f, 0.0f, 0.0f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } scene.addChild(shell) val lights = (0..4).map { @@ -53,7 +58,7 @@ class BileExample: SceneryBase("Bile Canaliculi example") { Vector3f(0.0f,-1.0f,1.0f/Math.sqrt(2.0).toFloat())) tetrahedron.mapIndexed { i, position -> - lights[i].position = position * 50.0f + lights[i].spatial().position = position * 50.0f lights[i].emissionColor = Vector3f(1.0f, 0.5f,0.3f)//Random.random3DVectorFromRange(0.2f, 0.8f) lights[i].intensity = 200.2f scene.addChild(lights[i]) @@ -61,11 +66,15 @@ class BileExample: SceneryBase("Bile Canaliculi example") { val bile = Mesh() bile.readFrom("M:/meshes/adult_mouse_bile_canaliculi_network_2.stl") - bile.scale = Vector3f(0.1f, 0.1f, 0.1f) - bile.position = Vector3f(-600.0f, -800.0f, -20.0f) - bile.material.diffuse = Vector3f(0.8f, 0.5f, 0.5f) - bile.material.specular = Vector3f(1.0f, 1.0f, 1.0f) - bile.material.roughness = 0.5f + bile.spatial { + scale = Vector3f(0.1f, 0.1f, 0.1f) + position = Vector3f(-600.0f, -800.0f, -20.0f) + } + bile.material { + diffuse = Vector3f(0.8f, 0.5f, 0.5f) + specular = Vector3f(1.0f, 1.0f, 1.0f) + roughness = 0.5f + } scene.addChild(bile) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/cluster/ClusterExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/cluster/ClusterExample.kt index 11b36aae3..b4b94ddf9 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/cluster/ClusterExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/cluster/ClusterExample.kt @@ -41,7 +41,9 @@ class ClusterExample: SceneryBase("Clustered Volume Rendering example") { val cam: Camera = DetachedHeadCamera(hmd) with(cam) { //position = Vector3f(.4f, .4f, 1.4f) - position = Vector3f(.0f, 0f, 0f) + cam.spatial { + position = Vector3f(.0f, 0f, 0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) @@ -54,18 +56,22 @@ class ClusterExample: SceneryBase("Clustered Volume Rendering example") { it % 3 == 0 -> Cone(0.1f, 0.2f, 10) else -> Icosphere(0.1f, 2) } - s.position = Vector3f( - floor(it / rowSize), - (it % rowSize.toInt()).toFloat(), - 0.0f) - s.position = s.position - Vector3f( - (rowSize - 1.0f)/4.0f, - (rowSize - 1.0f)/4.0f, - 0.0f) + s.spatial { + position = Vector3f( + floor(it / rowSize), + (it % rowSize.toInt()).toFloat(), + 0.0f) + position = position - Vector3f( + (rowSize - 1.0f)/4.0f, + (rowSize - 1.0f)/4.0f, + 0.0f) + } - s.material.roughness = (it / rowSize)/rowSize - s.material.metallic = (it % rowSize.toInt())/rowSize - s.material.diffuse = Random.random3DVectorFromRange(0.5f, 1.0f) + s.material { + roughness = (it / rowSize)/rowSize + metallic = (it % rowSize.toInt())/rowSize + diffuse = Random.random3DVectorFromRange(0.5f, 1.0f) + } scene.addChild(s) s @@ -74,7 +80,7 @@ class ClusterExample: SceneryBase("Clustered Volume Rendering example") { val lights = Light.createLightTetrahedron(spread = 2.0f, radius = 20.0f) lights.forEach { scene.addChild(it) } val l = PointLight(5.0f) - l.position = Vector3f(0.0f, 2.0f, 2.0f) + l.spatial().position = Vector3f(0.0f, 2.0f, 2.0f) scene.addChild(l) spheres.forEach { publishedNodes.add(it) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/cluster/DemoReelExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/cluster/DemoReelExample.kt index 882c8104c..0a825e7d3 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/cluster/DemoReelExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/cluster/DemoReelExample.kt @@ -8,6 +8,7 @@ import graphics.scenery.controls.TrackedStereoGlasses import graphics.scenery.net.NodePublisher import graphics.scenery.net.NodeSubscriber import graphics.scenery.Mesh +import graphics.scenery.attribute.material.Material import graphics.scenery.volumes.Colormap import graphics.scenery.volumes.TransferFunction import graphics.scenery.volumes.Volume @@ -42,7 +43,9 @@ class DemoReelExample: SceneryBase("Demo Reel") { cam = DetachedHeadCamera(hmd) with(cam) { - position = Vector3f(0.0f, 0.0f, 55.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 55.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight, 0.02f, 500.0f) disableCulling = true @@ -51,10 +54,12 @@ class DemoReelExample: SceneryBase("Demo Reel") { // box setup val shell = Box(Vector3f(120.0f, 120.0f, 120.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.Front - shell.material.diffuse = Vector3f(0.0f, 0.0f, 0.0f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) + shell.material { + cullingMode = Material.CullingMode.Front + diffuse = Vector3f(0.0f, 0.0f, 0.0f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } scene.addChild(shell) Light.createLightTetrahedron(spread = 50.0f, intensity = 150.0f, radius = 150.0f) @@ -77,7 +82,9 @@ class DemoReelExample: SceneryBase("Demo Reel") { Paths.get("$driveLetter:/ssd-backup-inauguration/CAVE_DATA/droso-royer-autopilot-transposed/"), hub ) - drosophilaVolume.rotation.rotateX(1.57f) + drosophilaVolume.spatial { + rotation.rotateX(1.57f) + } drosophilaVolume.transferFunction = TransferFunction.ramp(0.1f, 1.0f) drosophilaVolume.colormap = Colormap.get("hot") drosophilaScene.addChild(drosophilaVolume) @@ -95,25 +102,37 @@ class DemoReelExample: SceneryBase("Demo Reel") { val bile = Mesh() val canaliculi = Mesh() canaliculi.readFrom("$driveLetter:/ssd-backup-inauguration/meshes/bile-canaliculi.obj") - canaliculi.scale = Vector3f(0.1f, 0.1f, 0.1f) - canaliculi.position = Vector3f(-80.0f, -60.0f, 10.0f) - canaliculi.material.diffuse = Vector3f(0.5f, 0.7f, 0.1f) + canaliculi.spatial { + scale = Vector3f(0.1f, 0.1f, 0.1f) + position = Vector3f(-80.0f, -60.0f, 10.0f) + } + canaliculi.material { + diffuse = Vector3f(0.5f, 0.7f, 0.1f) + } bile.addChild(canaliculi) val nuclei = Mesh() nuclei.readFrom("$driveLetter:/ssd-backup-inauguration/meshes/bile-nuclei.obj") - nuclei.scale = Vector3f(0.1f, 0.1f, 0.1f) - nuclei.position = Vector3f(-80.0f, -60.0f, 10.0f) - nuclei.material.diffuse = Vector3f(0.8f, 0.8f, 0.8f) + nuclei.spatial { + scale = Vector3f(0.1f, 0.1f, 0.1f) + position = Vector3f(-80.0f, -60.0f, 10.0f) + } + nuclei.material { + diffuse = Vector3f(0.8f, 0.8f, 0.8f) + } bile.addChild(nuclei) val sinusoidal = Mesh() sinusoidal.readFrom("$driveLetter:/ssd-backup-inauguration/meshes/bile-sinus.obj") - sinusoidal.scale = Vector3f(0.1f, 0.1f, 0.1f) - sinusoidal.position = Vector3f(-80.0f, -60.0f, 10.0f) - sinusoidal.material.ambient = Vector3f(0.1f, 0.0f, 0.0f) - sinusoidal.material.diffuse = Vector3f(0.4f, 0.0f, 0.02f) - sinusoidal.material.specular = Vector3f(0.05f, 0f, 0f) + sinusoidal.spatial { + scale = Vector3f(0.1f, 0.1f, 0.1f) + position = Vector3f(-80.0f, -60.0f, 10.0f) + } + sinusoidal.material { + ambient = Vector3f(0.1f, 0.0f, 0.0f) + diffuse = Vector3f(0.4f, 0.0f, 0.02f) + specular = Vector3f(0.05f, 0f, 0f) + } bile.addChild(sinusoidal) bileScene.addChild(bile) scene.addChild(bileScene) @@ -190,7 +209,7 @@ class DemoReelExample: SceneryBase("Demo Reel") { scenes.filter { it.name == sceneName }.forEach { scene -> scene.runRecursive { it.visible = true } } scenes.filter { it.name != sceneName }.forEach { scene -> scene.runRecursive { it.visible = false } } - scene.findObserver()?.position = Vector3f(0.0f, 0.0f, 3.0f) + scene.findObserver()?.spatial()?.position = Vector3f(0.0f, 0.0f, 3.0f) } inputHandler.addBehaviour("goto_scene_bile", gotoScene("bile")) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/cluster/ScreenConfigVisualizerExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/cluster/ScreenConfigVisualizerExample.kt index 4695968b0..990063462 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/cluster/ScreenConfigVisualizerExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/cluster/ScreenConfigVisualizerExample.kt @@ -7,15 +7,14 @@ import graphics.scenery.controls.ScreenConfig import graphics.scenery.numerics.Random import graphics.scenery.primitives.Plane import graphics.scenery.primitives.TextBoard +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.extensions.minus import graphics.scenery.utils.extensions.plus -import graphics.scenery.volumes.Colormap import org.joml.Vector4f import org.scijava.Context import org.scijava.ui.UIService import org.scijava.ui.behaviour.ClickBehaviour import org.scijava.widget.FileWidget -import kotlin.math.PI /** * @@ -44,11 +43,11 @@ class ScreenConfigVisualizerExample : SceneryBase("Screen Config Visualizer", 12 lights.forEach { scene.addChild(it) } val light = PointLight(20.0f) - light.position = Vector3f(0.0f, 1.8f, 0.0f) + light.spatial().position = Vector3f(0.0f, 1.8f, 0.0f) scene.addChild(light) val origin = Icosphere(0.1f, 2) - origin.material.diffuse = Vector3f(1.0f) + origin.material().diffuse = Vector3f(1.0f) scene.addChild(origin) val screenconfig = ScreenConfig.loadFromFile(files[0]) @@ -63,31 +62,35 @@ class ScreenConfigVisualizerExample : SceneryBase("Screen Config Visualizer", 12 val s = Group() val lowerLeft = Icosphere(0.05f, 2) - lowerLeft.material.diffuse = Vector3f(1.0f, 0.0f, 0.0f) - lowerLeft.position = screen.lowerLeft + lowerLeft.material().diffuse = Vector3f(1.0f, 0.0f, 0.0f) + lowerLeft.spatial().position = screen.lowerLeft s.addChild(lowerLeft) val lowerRight = Icosphere(0.05f, 2) - lowerRight.material.diffuse = Vector3f(0.0f, 1.0f, 0.0f) - lowerRight.position = screen.lowerRight + lowerRight.material().diffuse = Vector3f(0.0f, 1.0f, 0.0f) + lowerRight.spatial().position = screen.lowerRight s.addChild(lowerRight) val upperLeft = Icosphere(0.05f, 2) - upperLeft.material.diffuse = Vector3f(0.0f, 0.0f, 1.0f) - upperLeft.position = screen.upperLeft + upperLeft.material().diffuse = Vector3f(0.0f, 0.0f, 1.0f) + upperLeft.spatial().position = screen.upperLeft s.addChild(upperLeft) val up: Vector3f = screen.lowerLeft + (screen.lowerRight - screen.lowerLeft) + (screen.upperLeft - screen.lowerLeft) val p = Plane(screen.lowerLeft, screen.upperLeft, screen.lowerRight, up) - p.material.diffuse = color - p.material.cullingMode = Material.CullingMode.None + p.material { + diffuse = color + cullingMode = Material.CullingMode.None + } val label = TextBoard() label.text = name label.backgroundColor = Vector4f(0.0f) label.fontColor = Vector4f(1.0f) - label.scale = Vector3f(0.5f, 0.5f, 0.5f) - label.position = Vector3f(0.0f, 1.5f, 0.0f) + label.spatial { + scale = Vector3f(0.5f, 0.5f, 0.5f) + position = Vector3f(0.0f, 1.5f, 0.0f) + } label.visible = false s.addChild(label) @@ -98,14 +101,18 @@ class ScreenConfigVisualizerExample : SceneryBase("Screen Config Visualizer", 12 } val box = Box(Vector3f(scene.getMaximumBoundingBox().getBoundingSphere().radius * 0.5f), insideNormals = true) - box.material.cullingMode = Material.CullingMode.None - box.material.diffuse = Vector3f(0.3f, 0.3f, 0.3f) + box.material { + cullingMode = Material.CullingMode.None + diffuse = Vector3f(0.3f, 0.3f, 0.3f) + } scene.addChild(box) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 1.65f, 5.0f) + spatial { + position = Vector3f(0.0f, 1.65f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderExample.kt index 4cd4a293e..b095737d3 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderExample.kt @@ -30,11 +30,12 @@ class ComputeShaderExample : SceneryBase("ComputeShaderExample") { val helix = Texture.fromImage(Image.fromResource("textures/helix.png", TexturedCubeExample::class.java), usage = hashSetOf(Texture.UsageType.LoadStoreImage, Texture.UsageType.Texture)) val buffer = MemoryUtil.memCalloc(helix.dimensions.x * helix.dimensions.y * helix.dimensions.z * 4) - val compute = Node() + val compute = RichNode() compute.name = "compute node" - compute.material = ShaderMaterial(Shaders.ShadersFromFiles(arrayOf("BGRAMosaic.comp"), this::class.java)) - compute.material.textures["OutputViewport"] = Texture.fromImage(Image(buffer, helix.dimensions.x, helix.dimensions.y, helix.dimensions.z), usage = hashSetOf(Texture.UsageType.LoadStoreImage, Texture.UsageType.Texture)) - compute.material.textures["InputColor"] = helix + compute.setMaterial(ShaderMaterial(Shaders.ShadersFromFiles(arrayOf("BGRAMosaic.comp"), this@ComputeShaderExample::class.java))) { + textures["OutputViewport"] = Texture.fromImage(Image(buffer, helix.dimensions.x, helix.dimensions.y, helix.dimensions.z), usage = hashSetOf(Texture.UsageType.LoadStoreImage, Texture.UsageType.Texture)) + textures["InputColor"] = helix + } compute.metadata["ComputeMetadata"] = ComputeMetadata( workSizes = Vector3i(512, 512, 1), invocationType = InvocationType.Once @@ -44,21 +45,25 @@ class ComputeShaderExample : SceneryBase("ComputeShaderExample") { val box = Box(Vector3f(1.0f, 1.0f, 1.0f)) box.name = "le box du win" - box.material.textures["diffuse"] = compute.material.textures["OutputViewport"]!! - box.material.metallic = 0.3f - box.material.roughness = 0.9f + box.material { + textures["diffuse"] = compute.material().textures["OutputViewport"]!! + metallic = 0.3f + roughness = 0.9f + } scene.addChild(box) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 5.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + cam.spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -66,8 +71,10 @@ class ComputeShaderExample : SceneryBase("ComputeShaderExample") { thread { while (running) { - box.rotation.rotateY(0.01f) - box.needsUpdate = true + box.spatial { + rotation.rotateY(0.01f) + needsUpdate = true + } // box.material.textures["diffuse"] = compute.material.textures["OutputViewport"]!! Thread.sleep(20) @@ -82,7 +89,7 @@ class ComputeShaderExample : SceneryBase("ComputeShaderExample") { inputHandler?.addBehaviour("save_texture", ClickBehaviour { _, _ -> logger.info("Finding node") val node = scene.find("compute node") ?: return@ClickBehaviour - val texture = node.material.textures["OutputViewport"]!! + val texture = node.materialOrNull()?.textures?.get("OutputViewport") ?: return@ClickBehaviour val r = renderer ?: return@ClickBehaviour logger.info("Node found, saving texture") diff --git a/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderRenderpassExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderRenderpassExample.kt index 637b0f58b..b84e63040 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderRenderpassExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/compute/ComputeShaderRenderpassExample.kt @@ -21,20 +21,24 @@ class ComputeShaderRenderpassExample : SceneryBase("ComputeShaderRenderpassExamp val box = Box(Vector3f(1.0f, 1.0f, 1.0f)) box.name = "le box du win" - box.material.textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", TexturedCubeExample::class.java)) - box.material.metallic = 0.3f - box.material.roughness = 0.9f + box.material { + textures["diffuse"] = Texture.fromImage(Image.fromResource("textures/helix.png", TexturedCubeExample::class.java)) + metallic = 0.3f + roughness = 0.9f + } scene.addChild(box) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 5.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -42,8 +46,10 @@ class ComputeShaderRenderpassExample : SceneryBase("ComputeShaderRenderpassExamp thread { while (running) { - box.rotation.rotateY(0.01f) - box.needsUpdate = true + box.spatial { + rotation.rotateY(0.01f) + needsUpdate = true + } Thread.sleep(20) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/compute/CustomVolumeManagerExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/compute/CustomVolumeManagerExample.kt index 5d202506b..c2b39e158 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/compute/CustomVolumeManagerExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/compute/CustomVolumeManagerExample.kt @@ -41,7 +41,7 @@ class CustomVolumeManagerExample : SceneryBase("CustomVolumeManagerExample") { val outputBuffer = MemoryUtil.memCalloc(1280*720*4) val outputTexture = Texture.fromImage(Image(outputBuffer, 1280, 720), usage = hashSetOf(Texture.UsageType.LoadStoreImage, Texture.UsageType.Texture)) - volumeManager.material.textures["OutputRender"] = outputTexture + volumeManager.material().textures["OutputRender"] = outputTexture hub.add(volumeManager) @@ -54,21 +54,25 @@ class CustomVolumeManagerExample : SceneryBase("CustomVolumeManagerExample") { val box = Box(Vector3f(1.0f, 1.0f, 1.0f)) box.name = "le box du win" - box.material.textures["diffuse"] = outputTexture - box.material.metallic = 0.0f - box.material.roughness = 1.0f + box.material { + textures["diffuse"] = outputTexture + metallic = 0.0f + roughness = 1.0f + } scene.addChild(box) val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 5.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/stresstests/LotsOfSpheresExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/stresstests/LotsOfSpheresExample.kt index 53eb14cce..8139c3f66 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/stresstests/LotsOfSpheresExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/stresstests/LotsOfSpheresExample.kt @@ -17,19 +17,21 @@ class LotsOfSpheresExample: SceneryBase("LotsOfSpheres", wantREPL = true) { for(i in 0 until 12000) { val s = Sphere(0.1f, 10) - s.position = Random.random3DVectorFromRange(-10.0f, 10.0f) + s.spatial().position = Random.random3DVectorFromRange(-10.0f, 10.0f) scene.addChild(s) } val light = PointLight(radius = 15.0f) - light.position = Vector3f(0.0f, 0.0f, 2.0f) + light.spatial().position = Vector3f(0.0f, 0.0f, 2.0f) light.intensity = 100.0f light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(light) val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) @@ -38,7 +40,7 @@ class LotsOfSpheresExample: SceneryBase("LotsOfSpheres", wantREPL = true) { thread { for(i in 0 until 12000) { val s = Sphere(0.1f, 10) - s.position = Random.random3DVectorFromRange(-10.0f, 10.0f) + s.spatial().position = Random.random3DVectorFromRange(-10.0f, 10.0f) scene.addChild(s) Thread.sleep(5) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/stresstests/PowerplantExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/stresstests/PowerplantExample.kt index 34717bcba..db29b7cf5 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/stresstests/PowerplantExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/stresstests/PowerplantExample.kt @@ -24,7 +24,9 @@ class PowerplantExample : SceneryBase("PowerplantExample", windowWidth = 1280, w val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 0.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight, nearPlaneLocation = 0.5f, farPlaneLocation = 1000.0f) scene.addChild(this) @@ -39,14 +41,13 @@ class PowerplantExample : SceneryBase("PowerplantExample", windowWidth = 1280, w } boxes.mapIndexed { i, box -> - box.material = Material() box.addChild(lights[i]) scene.addChild(box) } lights.map { it.emissionColor = Random.random3DVectorFromRange(0.0f, 1.0f) - it.parent?.material?.diffuse = it.emissionColor + it.parent?.materialOrNull()?.diffuse = it.emissionColor it.intensity = Random.randomFromRange(0.01f, 10f) scene.addChild(it) @@ -55,10 +56,11 @@ class PowerplantExample : SceneryBase("PowerplantExample", windowWidth = 1280, w val plant = Mesh() with(plant) { readFromOBJ(getDemoFilesPath() + "/powerplant.obj", importMaterials = true) - position = Vector3f(0.0f, 0.0f, 0.0f) - scale = Vector3f(0.001f, 0.001f, 0.001f) - material = Material() - updateWorld(true, true) + spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + scale = Vector3f(0.001f, 0.001f, 0.001f) + updateWorld(true, true) + } name = "rungholt" scene.addChild(this) @@ -71,12 +73,14 @@ class PowerplantExample : SceneryBase("PowerplantExample", windowWidth = 1280, w i, box -> val phi = Math.PI * 2.0f * ticks / 2500.0f - box.position = Vector3f( - -128.0f + 18.0f * (i + 1), - 5.0f + i * 5.0f, - (i + 1) * 50 * Math.cos(phi + (i * 0.2f)).toFloat()) + box.spatial { + position = Vector3f( + -128.0f + 18.0f * (i + 1), + 5.0f + i * 5.0f, + (i + 1) * 50 * Math.cos(phi + (i * 0.2f)).toFloat()) - box.children[0].position = box.position + box.children[0].spatialOrNull()?.position = position + } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/stresstests/RungholtExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/stresstests/RungholtExample.kt index f2eed4c5c..ca1774699 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/stresstests/RungholtExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/stresstests/RungholtExample.kt @@ -28,7 +28,9 @@ class RungholtExample : SceneryBase("RungholtExample", windowWidth = 1280, windo val cam: Camera = DetachedHeadCamera(hmd) with(cam) { perspectiveCamera(50.0f, windowWidth, windowHeight, nearPlaneLocation = 0.5f, farPlaneLocation = 1000.0f) - position = Vector3f(0.0f, 50.0f, -100.0f) + spatial { + position = Vector3f(0.0f, 50.0f, -100.0f) + } scene.addChild(this) } @@ -41,14 +43,13 @@ class RungholtExample : SceneryBase("RungholtExample", windowWidth = 1280, windo } boxes.mapIndexed { i, box -> - box.material = Material() box.addChild(lights[i]) scene.addChild(box) } lights.map { it.emissionColor = Random.random3DVectorFromRange(0.0f, 1.0f) - it.parent?.material?.diffuse = it.emissionColor + it.parent?.materialOrNull()?.diffuse = it.emissionColor it.intensity = Random.randomFromRange(0.1f, 10f) scene.addChild(it) @@ -57,9 +58,11 @@ class RungholtExample : SceneryBase("RungholtExample", windowWidth = 1280, windo val rungholtMesh = Mesh() with(rungholtMesh) { readFromOBJ(getDemoFilesPath() + "/rungholt.obj", importMaterials = true) - position = Vector3f(0.0f, 0.0f, 0.0f) - scale = Vector3f(1.0f, 1.0f, 1.0f) - updateWorld(true, true) + spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + scale = Vector3f(1.0f, 1.0f, 1.0f) + updateWorld(true, true) + } name = "rungholt" scene.addChild(this) @@ -72,11 +75,13 @@ class RungholtExample : SceneryBase("RungholtExample", windowWidth = 1280, windo i, box -> val phi = ticks / 1500.0f % (Math.PI * 2.0f) - box.position = Vector3f( - -320.0f + 5.0f * (i + 1), - 15.0f + i * 0.2f, - 250.0f * Math.cos(phi + (i * 0.2f)).toFloat()) - box.children[0].position = box.position + box.spatial { + position = Vector3f( + -320.0f + 5.0f * (i + 1), + 15.0f + i * 0.2f, + 250.0f * Math.cos(phi + (i * 0.2f)).toFloat()) + box.children[0].spatialOrNull()?.position = position + } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt index 1075d5353..d27a0fc22 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/BDVExample.kt @@ -90,7 +90,7 @@ class BDVExample: SceneryBase("BDV Rendering example", 1280, 720) { } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 50.0f scene.addChild(light) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/BigAndSmallVolumeExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/BigAndSmallVolumeExample.kt index b23ab6f5d..b2c95a5c9 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/BigAndSmallVolumeExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/BigAndSmallVolumeExample.kt @@ -60,7 +60,7 @@ class BigAndSmallVolumeExample: SceneryBase("BDV + SDV Rendering example", 1280, val v = Volume.fromSpimData(XmlIoSpimDataMinimal().load(files.first()), hub, options) v.name = "volume" // v.colormap = "plasma" - v.scale = Vector3f(0.02f, 0.02f, 0.02f) + v.spatial().scale = Vector3f(0.02f, 0.02f, 0.02f) scene.addChild(v) volume = v @@ -70,7 +70,7 @@ class BigAndSmallVolumeExample: SceneryBase("BDV + SDV Rendering example", 1280, } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 50.0f scene.addChild(light) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/CroppingExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/CroppingExample.kt index fd3bbf1e0..09700add4 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/CroppingExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/CroppingExample.kt @@ -20,6 +20,8 @@ import org.scijava.ui.behaviour.ClickBehaviour import tpietzsch.example2.VolumeViewerOptions import kotlin.concurrent.thread import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material +import graphics.scenery.attribute.spatial.Spatial /** * Volume Cropping Example using the "BDV Rendering Example loading a RAII" @@ -44,24 +46,27 @@ class CroppingExample : SceneryBase("Volume Cropping example", 1280, 720) { val cam: Camera = DetachedHeadCamera() with(cam) { perspectiveCamera(50.0f, windowWidth, windowHeight) - - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(this) } val shell = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.None - shell.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) + shell.material { + cullingMode = Material.CullingMode.None + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } scene.addChild(shell) Light.createLightTetrahedron(spread = 4.0f, radius = 15.0f, intensity = 0.5f) .forEach { scene.addChild(it) } val origin = Box(Vector3f(0.1f, 0.1f, 0.1f)) - origin.material.diffuse = Vector3f(0.8f, 0.0f, 0.0f) + origin.material().diffuse = Vector3f(0.8f, 0.0f, 0.0f) scene.addChild(origin) val imp: ImagePlus = IJ.openImage("https://imagej.nih.gov/ij/images/t1-head.zip") @@ -76,10 +81,12 @@ class CroppingExample : SceneryBase("Volume Cropping example", 1280, 720) { if (additionalVolumes) { fun addAdditionalVolume(base: Vector3f): Volume { - val vol2Pivot = Node() + val vol2Pivot = RichNode() scene.addChild(vol2Pivot) - vol2Pivot.position = vol2Pivot.position + base - vol2Pivot.scale = Vector3f(0.5f) + vol2Pivot.spatial { + position += base + scale = Vector3f(0.5f) + } volume2 = Volume.fromRAI(img, UnsignedShortType(), AxisOrder.DEFAULT, "T1 head", hub, VolumeViewerOptions()) @@ -90,7 +97,7 @@ class CroppingExample : SceneryBase("Volume Cropping example", 1280, 720) { scene.removeChild(slicingPlane2) vol2Pivot.addChild(slicingPlane2) slicingPlane2.addTargetVolume(volume2) - val animator2 = Animator(slicingPlane2) + val animator2 = Animator(slicingPlane2.spatial()) additionalAnimators = additionalAnimators + animator2 return volume2 @@ -110,9 +117,9 @@ class CroppingExample : SceneryBase("Volume Cropping example", 1280, 720) { } } - class Animator(val node: Node) { - private var moveDir = Random.random3DVectorFromRange(-1.0f, 1.0f) - node.position - private var rotationStart = Quaternionf(node.rotation) + class Animator(val spatial: Spatial) { + private var moveDir = Random.random3DVectorFromRange(-1.0f, 1.0f) - spatial.position + private var rotationStart = Quaternionf(spatial.rotation) private var rotationTarget = Random.randomQuaternion() private var startTime = System.currentTimeMillis() @@ -120,15 +127,12 @@ class CroppingExample : SceneryBase("Volume Cropping example", 1280, 720) { val relDelta = (System.currentTimeMillis() - startTime) / 5000f val scaledMov = moveDir * (20f / 5000f) - node.position = node.position + scaledMov - - rotationStart.nlerp(rotationTarget, relDelta, node.rotation) - - node.needsUpdate = true - + spatial.position += scaledMov + rotationStart.nlerp(rotationTarget, relDelta, spatial.rotation) + spatial.needsUpdate = true if (startTime + 5000 < System.currentTimeMillis()) { - moveDir = Random.random3DVectorFromRange(-1.0f, 1.0f) - node.position - rotationStart = Quaternionf(node.rotation) + moveDir = Random.random3DVectorFromRange(-1.0f, 1.0f) - spatial.position + rotationStart = Quaternionf(spatial.rotation) rotationTarget = Random.randomQuaternion() startTime = System.currentTimeMillis() } @@ -139,7 +143,7 @@ class CroppingExample : SceneryBase("Volume Cropping example", 1280, 720) { val slicingPlane = createSlicingPlane() slicingPlane.addTargetVolume(volume) - val animator = Animator(slicingPlane) + val animator = Animator(slicingPlane.spatial()) slicingPlanes = slicingPlanes + (slicingPlane to animator) } @@ -160,12 +164,18 @@ class CroppingExample : SceneryBase("Volume Cropping example", 1280, 720) { val slicingPlaneVisual: Node slicingPlaneVisual = Box(Vector3f(1f, 0.01f, 1f)) - slicingPlaneVisual.material.diffuse = Vector3f(0.0f, 0.8f, 0.0f) + slicingPlaneVisual.material { + diffuse = Vector3f(0.0f, 0.8f, 0.0f) + } slicingPlaneFunctionality.addChild(slicingPlaneVisual) val nose = Box(Vector3f(0.1f, 0.1f, 0.1f)) - nose.material.diffuse = Vector3f(0.8f, 0.0f, 0.0f) - nose.position = Vector3f(0f, 0.05f, 0f) + nose.material { + diffuse = Vector3f(0.8f, 0.0f, 0.0f) + } + nose.spatial { + position = Vector3f(0f, 0.05f, 0f) + } slicingPlaneVisual.addChild(nose) return slicingPlaneFunctionality diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/FlybrainOutOfCoreExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/FlybrainOutOfCoreExample.kt index 8698444cd..69e44e8b8 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/FlybrainOutOfCoreExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/FlybrainOutOfCoreExample.kt @@ -45,8 +45,9 @@ class FlybrainOutOfCoreExample: SceneryBase("Flybrain RAI Rendering example", 12 val cam: Camera = DetachedHeadCamera() with(cam) { perspectiveCamera(50.0f, windowWidth, windowHeight) - - position = Vector3f(0.0f, 0.0f, 15.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 15.0f) + } scene.addChild(this) } @@ -96,7 +97,7 @@ class FlybrainOutOfCoreExample: SceneryBase("Flybrain RAI Rendering example", 12 ) brain.transferFunction = TransferFunction.ramp(0.001f, 0.4f) brain.converterSetups.first().setDisplayRange(0.0, 255.0) - brain.position = Vector3f(-3.0f, 3.0f, 0.0f) + brain.spatial().position = Vector3f(-3.0f, 3.0f, 0.0f) @Suppress("UNCHECKED_CAST") val gaussDiff = Volume.fromRAI( @@ -107,7 +108,7 @@ class FlybrainOutOfCoreExample: SceneryBase("Flybrain RAI Rendering example", 12 ) gaussDiff.transferFunction = TransferFunction.ramp(0.1f, 0.1f) gaussDiff.converterSetups.first().setDisplayRange(0.0, 255.0) - gaussDiff.position = Vector3f(3.0f, 3.0f, 0.0f) + gaussDiff.spatial().position = Vector3f(3.0f, 3.0f, 0.0f) @Suppress("UNCHECKED_CAST") val brainGauss1 = Volume.fromRAI( @@ -118,7 +119,7 @@ class FlybrainOutOfCoreExample: SceneryBase("Flybrain RAI Rendering example", 12 ) brainGauss1.transferFunction = TransferFunction.ramp(0.1f, 0.1f) brainGauss1.converterSetups.first().setDisplayRange(0.0, 60.0) - brainGauss1.position = Vector3f(-3.0f, -3.0f, 0.0f) + brainGauss1.spatial().position = Vector3f(-3.0f, -3.0f, 0.0f) @Suppress("UNCHECKED_CAST") val brainGauss2 = Volume.fromRAI( @@ -129,7 +130,7 @@ class FlybrainOutOfCoreExample: SceneryBase("Flybrain RAI Rendering example", 12 ) brainGauss2.transferFunction = TransferFunction.ramp(0.1f, 0.1f) brainGauss2.converterSetups.first().setDisplayRange(0.0, 60.0) - brainGauss2.position = Vector3f(3.0f, -3.0f, 0.0f) + brainGauss2.spatial().position = Vector3f(3.0f, -3.0f, 0.0f) scene.addChild(brain) scene.addChild(gaussDiff) @@ -141,7 +142,7 @@ class FlybrainOutOfCoreExample: SceneryBase("Flybrain RAI Rendering example", 12 } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 50.0f scene.addChild(light) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/OrthoViewExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/OrthoViewExample.kt index e5961066c..79e17baf4 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/OrthoViewExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/OrthoViewExample.kt @@ -3,8 +3,7 @@ package graphics.scenery.tests.examples.volumes import bdv.util.AxisOrder import graphics.scenery.* import graphics.scenery.backends.Renderer -import graphics.scenery.controls.InputHandler -import graphics.scenery.controls.behaviours.MouseDragSphere +import graphics.scenery.attribute.material.Material import graphics.scenery.volumes.* import ij.IJ import ij.ImagePlus @@ -28,24 +27,27 @@ class OrthoViewExample : SceneryBase("Ortho View example", 1280, 720) { val cam: Camera = DetachedHeadCamera() with(cam) { perspectiveCamera(50.0f, windowWidth, windowHeight) - - position = Vector3f(0.0f, 2.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 2.0f, 5.0f) + } scene.addChild(this) } val shell = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.None - shell.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) + shell.material { + cullingMode = Material.CullingMode.None + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } scene.addChild(shell) Light.createLightTetrahedron(spread = 4.0f, radius = 15.0f, intensity = 0.5f) .forEach { scene.addChild(it) } val origin = Box(Vector3f(0.1f, 0.1f, 0.1f)) - origin.material.diffuse = Vector3f(0.8f, 0.0f, 0.0f) + origin.material().diffuse = Vector3f(0.8f, 0.0f, 0.0f) scene.addChild(origin) val imp: ImagePlus = IJ.openImage("https://imagej.nih.gov/ij/images/t1-head.zip") diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/OutOfCoreRAIExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/OutOfCoreRAIExample.kt index 1973576a6..08b2b23cc 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/OutOfCoreRAIExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/OutOfCoreRAIExample.kt @@ -39,7 +39,7 @@ class OutOfCoreRAIExample: SceneryBase("Out-of-core RAI Rendering example", 1280 val cam: Camera = DetachedHeadCamera() cam.perspectiveCamera(50.0f, windowWidth, windowHeight) - cam.position = Vector3f(0.0f, 0.0f, 7.0f) + cam.spatial().position = Vector3f(0.0f, 0.0f, 7.0f) scene.addChild(cam) // download example TIFF stack, T1 head example from NIH @@ -71,7 +71,7 @@ class OutOfCoreRAIExample: SceneryBase("Out-of-core RAI Rendering example", 1280 ) volume.converterSetups.first().setDisplayRange(25.0, 512.0) volume.transferFunction = TransferFunction.ramp(0.01f, 0.03f) - volume.scale = Vector3f(1.0f, 1.0f, 3.0f) + volume.spatial().scale = Vector3f(1.0f, 1.0f, 3.0f) scene.addChild(volume) val lights = (0 until 3).map { @@ -79,7 +79,7 @@ class OutOfCoreRAIExample: SceneryBase("Out-of-core RAI Rendering example", 1280 } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 50.0f scene.addChild(light) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/ProceduralVolumeExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/ProceduralVolumeExample.kt index 83ab24e9b..98f4735e7 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/ProceduralVolumeExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/ProceduralVolumeExample.kt @@ -4,6 +4,7 @@ import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.RingBuffer import graphics.scenery.utils.extensions.plus import graphics.scenery.volumes.Colormap @@ -30,18 +31,24 @@ class ProceduralVolumeExample: SceneryBase("Procedural Volume Rendering Example" val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.5f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.5f, 5.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } val shell = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.None - shell.material.diffuse = Vector3f(0.1f, 0.1f, 0.1f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) - shell.position = Vector3f(0.0f, 4.0f, 0.0f) + shell.material { + cullingMode = Material.CullingMode.None + diffuse = Vector3f(0.1f, 0.1f, 0.1f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } + shell.spatial { + position = Vector3f(0.0f, 4.0f, 0.0f) + } scene.addChild(shell) val volume = if(bitsPerVoxel == 8) { @@ -51,7 +58,7 @@ class ProceduralVolumeExample: SceneryBase("Procedural Volume Rendering Example" } volume.name = "volume" - volume.position = Vector3f(0.0f, 0.0f, 0.0f) + volume.spatial().position = Vector3f(0.0f, 0.0f, 0.0f) volume.colormap = Colormap.get("hot") volume.pixelToWorldRatio = 0.03f @@ -71,7 +78,7 @@ class ProceduralVolumeExample: SceneryBase("Procedural Volume Rendering Example" } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 0.2f scene.addChild(light) diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/RAIExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/RAIExample.kt index d4079a074..67aa43c06 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/RAIExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/RAIExample.kt @@ -4,6 +4,7 @@ import bdv.util.AxisOrder import graphics.scenery.* import org.joml.Vector3f import graphics.scenery.backends.Renderer +import graphics.scenery.attribute.material.Material import graphics.scenery.volumes.TransferFunction import graphics.scenery.volumes.Volume import ij.IJ @@ -28,8 +29,9 @@ class RAIExample: SceneryBase("RAI Rendering example", 1280, 720) { val cam: Camera = DetachedHeadCamera() with(cam) { perspectiveCamera(50.0f, windowWidth, windowHeight) - - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } scene.addChild(this) } @@ -41,17 +43,19 @@ class RAIExample: SceneryBase("RAI Rendering example", 1280, 720) { scene.addChild(volume) val shell = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.None - shell.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) + shell.material { + cullingMode = Material.CullingMode.None + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } scene.addChild(shell) Light.createLightTetrahedron(spread = 4.0f, radius = 15.0f, intensity = 0.5f) .forEach { scene.addChild(it) } val origin = Box(Vector3f(0.1f, 0.1f, 0.1f)) - origin.material.diffuse = Vector3f(0.8f, 0.0f, 0.0f) + origin.material().diffuse = Vector3f(0.8f, 0.0f, 0.0f) scene.addChild(origin) } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeExample.kt index a94a45c7a..998bd0c9b 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeExample.kt @@ -3,6 +3,7 @@ package graphics.scenery.tests.examples.volumes import graphics.scenery.* import graphics.scenery.backends.Renderer import graphics.scenery.controls.TrackedStereoGlasses +import graphics.scenery.attribute.material.Material import graphics.scenery.volumes.Colormap import graphics.scenery.volumes.TransferFunction import graphics.scenery.volumes.Volume @@ -23,30 +24,36 @@ class VolumeExample: SceneryBase("Volume Rendering example", 1280, 720) { val cam: Camera = DetachedHeadCamera(hmd) with(cam) { - position = Vector3f(0.0f, 0.5f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.5f, 5.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } val shell = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.None - shell.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) + shell.material { + cullingMode = Material.CullingMode.None + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } scene.addChild(shell) val s = Icosphere(0.5f, 3) - s.position = Vector3f(2.0f, -1.0f, -2.0f) - s.material.diffuse = Vector3f(0.0f, 0.0f, 0.0f) + s.spatial().position = Vector3f(2.0f, -1.0f, -2.0f) + s.material().diffuse = Vector3f(0.0f, 0.0f, 0.0f) scene.addChild(s) val volume = Volume.fromPathRaw(Paths.get(getDemoFilesPath() + "/volumes/box-iso/"), hub) volume.name = "volume" volume.colormap = Colormap.get("viridis") - volume.position = Vector3f(0.0f, 0.0f, -3.5f) - volume.rotation = volume.rotation.rotateXYZ(0.05f, 0.05f, 0.05f) - volume.scale = Vector3f(20.0f, 20.0f, 20.0f) + volume.spatial { + position = Vector3f(0.0f, 0.0f, -3.5f) + rotation = rotation.rotateXYZ(0.05f, 0.05f, 0.05f) + scale = Vector3f(20.0f, 20.0f, 20.0f) + } volume.transferFunction = TransferFunction.ramp(0.1f, 0.5f) scene.addChild(volume) @@ -55,7 +62,7 @@ class VolumeExample: SceneryBase("Volume Rendering example", 1280, 720) { } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 0.5f scene.addChild(light) @@ -63,7 +70,9 @@ class VolumeExample: SceneryBase("Volume Rendering example", 1280, 720) { thread { while(true) { - volume.rotation = volume.rotation.rotateY(0.003f) + volume.spatial { + rotation = rotation.rotateY(0.003f) + } Thread.sleep(5) } } diff --git a/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeSamplingExample.kt b/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeSamplingExample.kt index 12ba41361..8978f0170 100644 --- a/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeSamplingExample.kt +++ b/src/test/kotlin/graphics/scenery/tests/examples/volumes/VolumeSamplingExample.kt @@ -6,6 +6,7 @@ import graphics.scenery.backends.Renderer import graphics.scenery.numerics.Random import graphics.scenery.primitives.Cylinder import graphics.scenery.primitives.Line +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.MaybeIntersects import graphics.scenery.utils.RingBuffer import graphics.scenery.utils.extensions.minus @@ -72,47 +73,55 @@ class VolumeSamplingExample: SceneryBase("Volume Sampling example", 1280, 720) { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.5f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.5f, 5.0f) + } perspectiveCamera(50.0f, windowWidth, windowHeight) scene.addChild(this) } val shell = Box(Vector3f(10.0f, 10.0f, 10.0f), insideNormals = true) - shell.material.cullingMode = Material.CullingMode.None - shell.material.diffuse = Vector3f(0.2f, 0.2f, 0.2f) - shell.material.specular = Vector3f(0.0f) - shell.material.ambient = Vector3f(0.0f) - shell.position = Vector3f(0.0f, 4.0f, 0.0f) + shell.material { + cullingMode = Material.CullingMode.None + diffuse = Vector3f(0.2f, 0.2f, 0.2f) + specular = Vector3f(0.0f) + ambient = Vector3f(0.0f) + } + shell.spatial { + position = Vector3f(0.0f, 4.0f, 0.0f) + } scene.addChild(shell) val p1 = Icosphere(0.2f, 2) - p1.position = Vector3f(-0.5f, 0.0f, -2.0f) - p1.material.diffuse = Vector3f(0.3f, 0.3f, 0.8f) + p1.spatial().position = Vector3f(-0.5f, 0.0f, -2.0f) + p1.material().diffuse = Vector3f(0.3f, 0.3f, 0.8f) scene.addChild(p1) val p2 = Icosphere(0.2f, 2) - p2.position = Vector3f(0.0f, 0.5f, 2.0f) - p2.material.diffuse = Vector3f(0.3f, 0.8f, 0.3f) + p2.spatial().position = Vector3f(0.0f, 0.5f, 2.0f) + p2.material().diffuse = Vector3f(0.3f, 0.8f, 0.3f) scene.addChild(p2) - val connector = Cylinder.betweenPoints(p1.position, p2.position) - connector.material.diffuse = Vector3f(1.0f, 1.0f, 1.0f) + val connector = Cylinder.betweenPoints(p1.spatial().position, p2.spatial().position) + connector.material().diffuse = Vector3f(1.0f, 1.0f, 1.0f) scene.addChild(connector) p1.update.add { - connector.orientBetweenPoints(p1.position, p2.position, true, true) + connector.spatial().orientBetweenPoints(p1.spatial().position, p2.spatial().position, true, true) } p2.update.add { - connector.orientBetweenPoints(p1.position, p2.position, true, true) + connector.spatial().orientBetweenPoints(p1.spatial().position, p2.spatial().position, true, true) } val volume = Volume.fromBuffer(emptyList(), volumeSize, volumeSize, volumeSize, UnsignedByteType(), hub) volume.name = "volume" - volume.position = Vector3f(0.0f, 0.0f, 0.0f) + volume.spatial { + position = Vector3f(0.0f, 0.0f, 0.0f) + scale = Vector3f(10.0f, 10.0f, 10.0f) + } volume.colormap = Colormap.get("viridis") - volume.scale = Vector3f(10.0f, 10.0f, 10.0f) // volume.voxelSizeZ = 0.5f with(volume.transferFunction) { addControlPoint(0.0f, 0.0f) @@ -133,7 +142,7 @@ class VolumeSamplingExample: SceneryBase("Volume Sampling example", 1280, 720) { } lights.mapIndexed { i, light -> - light.position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) + light.spatial().position = Vector3f(2.0f * i - 4.0f, i - 1.0f, 0.0f) light.emissionColor = Vector3f(1.0f, 1.0f, 1.0f) light.intensity = 0.5f scene.addChild(light) @@ -182,7 +191,7 @@ class VolumeSamplingExample: SceneryBase("Volume Sampling example", 1280, 720) { } } - val intersection = volume.intersectAABB(p1.position, (p2.position - p1.position).normalize()) + val intersection = volume.spatial().intersectAABB(p1.spatial().position, (p2.spatial().position - p1.spatial().position).normalize()) if(intersection is MaybeIntersects.Intersection) { val scale = volume.localScale() val localEntry = (intersection.relativeEntry + Vector3f(1.0f)) * (1.0f/2.0f) @@ -211,8 +220,8 @@ class VolumeSamplingExample: SceneryBase("Volume Sampling example", 1280, 720) { diagram.clearPoints() diagram.name = "diagram" diagram.edgeWidth = 0.005f - diagram.material.diffuse = Vector3f(0.05f, 0.05f, 0.05f) - diagram.position = Vector3f(0.0f, 0.0f, -0.5f) + diagram.material().diffuse = Vector3f(0.05f, 0.05f, 0.05f) + diagram.spatial().position = Vector3f(0.0f, 0.0f, -0.5f) diagram.addPoint(Vector3f(0.0f, 0.0f, 0.0f)) var point = Vector3f(0.0f) samples.filterNotNull().forEachIndexed { i, sample -> diff --git a/src/test/kotlin/graphics/scenery/tests/unit/CameraTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/CameraTests.kt index 592e9a60b..b9191cacd 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/CameraTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/CameraTests.kt @@ -53,7 +53,7 @@ class CameraTests { val boxesInFront = (0 until 10).map { val b = Box() - b.position = Vector3f(0.0f, + b.spatial().position = Vector3f(0.0f, Random.randomFromRange(-0.5f, 0.5f), Random.randomFromRange(2.0f, 10.0f)) s.addChild(b) @@ -62,12 +62,12 @@ class CameraTests { val boxesBehind = (0 until 10).map { val b = Box() - b.position = Random.random3DVectorFromRange(-0.5f, -10.0f) + b.spatial().position = Random.random3DVectorFromRange(-0.5f, -10.0f) s.addChild(b) b } - s.updateWorld(true, true) + s.spatial().updateWorld(true, true) assertTrue { boxesInFront.all { cam.canSee(it) } } assertFalse { boxesBehind.all { cam.canSee(it) } } @@ -82,14 +82,14 @@ class CameraTests { (0 until 10).map { val b = Box() - b.position = Vector3f(0.0f, + b.spatial().position = Vector3f(0.0f, 0f, Random.randomFromRange(2.0f, 10.0f)) s.addChild(b) b } - s.updateWorld(true, true) + s.spatial().updateWorld(true, true) val results = cam.getNodesForScreenSpacePosition(1280/2,720/2) assertEquals(10,results.matches.size) } @@ -101,8 +101,10 @@ class CameraTests { cam.perspectiveCamera(50.0f, 1280, 720, 0.01f, 1000.0f) s.addChild(cam) - cam.position = Vector3f(0f) - cam.rotation.lookAlong(Vector3f(0f,0f,1f),Vector3f(0f,1f,0f)) + cam.spatial { + position = Vector3f(0f) + rotation.lookAlong(Vector3f(0f,0f,1f),Vector3f(0f,1f,0f)) + } val (pos,dir) = cam.screenPointToRay(1280/2,720/2) diff --git a/src/test/kotlin/graphics/scenery/tests/unit/ConeTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/ConeTests.kt index 29ff7e903..6ad4f3138 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/ConeTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/ConeTests.kt @@ -35,7 +35,7 @@ class ConeTests { val c = Cone(radius, height, 4 * segments, axisN) s.addChild(c) - c.updateWorld(true) + c.spatial().updateWorld(true) val bb = c.boundingBox diff --git a/src/test/kotlin/graphics/scenery/tests/unit/MeshTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/MeshTests.kt index b4cc47107..3deb35105 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/MeshTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/MeshTests.kt @@ -24,10 +24,12 @@ class MeshTests { val erythrocyte = Mesh() erythrocyte.readFrom(Mesh::class.java.getResource("models/erythrocyte.obj").file) - assertEquals(18000, erythrocyte.vertices.capacity()) - assertEquals(18000, erythrocyte.normals.capacity()) - assertEquals(3000, erythrocyte.indices.capacity()) - assertEquals(12000, erythrocyte.texcoords.capacity()) + erythrocyte.geometry { + assertEquals(18000, vertices.capacity()) + assertEquals(18000, normals.capacity()) + assertEquals(3000, indices.capacity()) + assertEquals(12000, texcoords.capacity()) + } val erythrocyteBoundingBox = erythrocyte.boundingBox assertNotNull(erythrocyteBoundingBox) @@ -44,10 +46,12 @@ class MeshTests { val leukocyte = Mesh() leukocyte.readFrom(Mesh::class.java.getResource("models/leukocyte.obj").file) - assertEquals(144000, leukocyte.vertices.capacity()) - assertEquals(144000, leukocyte.normals.capacity()) - assertEquals(24000, leukocyte.indices.capacity()) - assertEquals(96000, leukocyte.texcoords.capacity()) + leukocyte.geometry { + assertEquals(144000, vertices.capacity()) + assertEquals(144000, normals.capacity()) + assertEquals(24000, indices.capacity()) + assertEquals(96000, texcoords.capacity()) + } val leukocyteBoundingBox = leukocyte.boundingBox assertNotNull(leukocyteBoundingBox) @@ -71,9 +75,11 @@ class MeshTests { val erythrocyte = Mesh() erythrocyte.readFrom(Mesh::class.java.getResource("models/erythrocyte.stl").file) - assertEquals(9000, erythrocyte.vertices.capacity()) - assertEquals(9000, erythrocyte.normals.capacity()) - /*TODO:indices and texcoords?*/ + erythrocyte.geometry { + assertEquals(9000, vertices.capacity()) + assertEquals(9000, normals.capacity()) + /*TODO:indices and texcoords?*/ + } val boundingBox = erythrocyte.boundingBox assertNotNull(boundingBox) @@ -97,9 +103,11 @@ class MeshTests { val erythrocyte = Mesh() erythrocyte.readFrom(Mesh::class.java.getResource("models/erythrocyte_ascii.stl").file) - assertEquals(9000, erythrocyte.vertices.capacity()) - assertEquals(9000, erythrocyte.normals.capacity()) - /*TODO:indices and texcoords?*/ + erythrocyte.geometry { + assertEquals(9000, vertices.capacity()) + assertEquals(9000, normals.capacity()) + /*TODO:indices and texcoords?*/ + } val boundingBox = erythrocyte.boundingBox assertNotNull(boundingBox) diff --git a/src/test/kotlin/graphics/scenery/tests/unit/NodeTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/NodeTests.kt index 2fb3bcfde..c9b3730a7 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/NodeTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/NodeTests.kt @@ -5,6 +5,8 @@ import org.joml.Vector3f import graphics.scenery.* import graphics.scenery.numerics.Random import graphics.scenery.Mesh +import graphics.scenery.attribute.material.DefaultMaterial +import graphics.scenery.attribute.material.Material import graphics.scenery.utils.LazyLogger import graphics.scenery.utils.extensions.compare import graphics.scenery.utils.extensions.plus @@ -35,27 +37,31 @@ class NodeTests { fun testTransformationPropagation() { val scene = Scene() - val childOne = Node("first child") - val subChild = Node("child of first child") + val childOne = RichNode("first child") + val subChild = RichNode("child of first child") scene.addChild(childOne) childOne.addChild(subChild) - childOne.position = Vector3f(1.0f, 1.0f, 1.0f) - subChild.position = Vector3f(-1.0f, -1.0f, -1.0f) - subChild.scale = Vector3f(2.0f, 1.0f, 1.0f) - subChild.rotation = Quaternionf().rotateXYZ(0.5f, 0.5f, 0.5f) + childOne.spatial { + position = Vector3f(1.0f, 1.0f, 1.0f) + } + subChild.spatial { + position = Vector3f(-1.0f, -1.0f, -1.0f) + scale = Vector3f(2.0f, 1.0f, 1.0f) + rotation = Quaternionf().rotateXYZ(0.5f, 0.5f, 0.5f) + } - logger.info("childOne:\n${childOne.world}") - logger.info("subChild:\n${subChild.world}") + logger.info("childOne:\n${childOne.spatial().world}") + logger.info("subChild:\n${subChild.spatial().world}") - childOne.updateWorld(true, force = false) + childOne.spatial().updateWorld(true, force = false) //subChild.updateWorld(true, force = false) logger.info("\n-------\nAfter update:\n") - logger.info("childOne:\n${childOne.world}") - logger.info("subChild:\n${subChild.world}") + logger.info("childOne:\n${childOne.spatial().world}") + logger.info("subChild:\n${subChild.spatial().world}") val expectedResult = Matrix4f().identity() expectedResult.translate(1.0f, 1.0f, 1.0f) @@ -65,61 +71,63 @@ class NodeTests { logger.info(expectedResult.toString()) - assertTrue(expectedResult.compare(subChild.world, true), "Expected transforms to be equal") + assertTrue(expectedResult.compare(subChild.spatial().world, true), "Expected transforms to be equal") } @Test fun originParentPositionTransformation(){ val scene = Scene() - val parent = Node() + val parent = RichNode() scene.addChild(parent) - val child = Node() + val child = RichNode() parent.addChild(child) - child.position += Vector3f(0.5f) - child.updateWorld(true,false) + child.spatial { + position += Vector3f(0.5f) + updateWorld(true,false) + } - assertEquals(Vector3f(),parent.position) - assertEquals(Vector3f(),parent.worldPosition()) - assertEquals(Vector3f(0.5f),child.position) - assertEquals(Vector3f(0.5f),child.worldPosition(Vector3f(0f))) + assertEquals(Vector3f(), parent.spatial().position) + assertEquals(Vector3f(), parent.spatial().worldPosition()) + assertEquals(Vector3f(0.5f), child.spatial().position) + assertEquals(Vector3f(0.5f), child.spatial().worldPosition(Vector3f(0f))) } @Test fun nonOriginParentPositionTransformation(){ val scene = Scene() - val parent = Node() - parent.position += Vector3f(0.5f) + val parent = RichNode() + parent.spatial().position += Vector3f(0.5f) scene.addChild(parent) - val child = Node() + val child = RichNode() parent.addChild(child) - child.position += Vector3f(0.5f) - parent.updateWorld(true,false) + child.spatial().position += Vector3f(0.5f) + parent.spatial().updateWorld(true, false) - assertEquals(Vector3f(0.5f),parent.position) - assertEquals(Vector3f(0.5f),parent.worldPosition()) - assertEquals(Vector3f(0.5f),child.position) - assertEquals(Vector3f(1f),child.worldPosition(Vector3f(0f))) + assertEquals(Vector3f(0.5f), parent.spatial().position) + assertEquals(Vector3f(0.5f), parent.spatial().worldPosition()) + assertEquals(Vector3f(0.5f), child.spatial().position) + assertEquals(Vector3f(1f), child.spatial().worldPosition(Vector3f(0f))) } @Test fun scaledParentPositionTransformation(){ val scene = Scene() - val parent = Node() + val parent = RichNode() //parent.position += Vector3f(0.5f) - parent.scale = Vector3f(0.5f) + parent.spatial().scale = Vector3f(0.5f) scene.addChild(parent) - val child = Node() + val child = RichNode() parent.addChild(child) - child.position += Vector3f(1f) - parent.updateWorld(true,false) + child.spatial().position += Vector3f(1f) + parent.spatial().updateWorld(true, false) - assertEquals(Vector3f(0f),parent.position) - assertEquals(Vector3f(0f),parent.worldPosition()) - assertEquals(Vector3f(1f),child.position) - assertEquals(Vector3f(0.5f),child.worldPosition(Vector3f(0f))) + assertEquals(Vector3f(0f),parent.spatial().position) + assertEquals(Vector3f(0f),parent.spatial().worldPosition()) + assertEquals(Vector3f(1f),child.spatial().position) + assertEquals(Vector3f(0.5f),child.spatial().worldPosition(Vector3f(0f))) } private fun addSiblings(toNode: Node, maxSiblings: Int, currentLevel: Int, maxLevels: Int): Int { @@ -131,10 +139,12 @@ class NodeTests { return totalNodes } - val n = Node("Sibling#$it/$currentLevel/$maxLevels") - n.position = Random.random3DVectorFromRange(-100.0f, 100.0f) - n.scale = Random.random3DVectorFromRange(0.1f, 10.0f) - n.rotation = Random.randomQuaternion() + val n = RichNode("Sibling#$it/$currentLevel/$maxLevels") + n.spatial { + position = Random.random3DVectorFromRange(-100.0f, 100.0f) + scale = Random.random3DVectorFromRange(0.1f, 10.0f) + rotation = Random.randomQuaternion() + } toNode.addChild(n) totalNodes++ @@ -160,7 +170,7 @@ class NodeTests { logger.info("Created $totalNodes nodes") val start = System.nanoTime() - scene.updateWorld(true, true) + scene.spatial().updateWorld(true, true) val duration = (System.nanoTime() - start)/10e6 assertTrue(totalNodes <= Math.pow(1.0*maxSiblings, 1.0*levels).toInt(), "Expected total nodes to be less than maximum allowed number") @@ -185,7 +195,7 @@ class NodeTests { logger.info("Created $totalNodes nodes") var start = System.nanoTime() - scene.updateWorld(true, true) + scene.spatial().updateWorld(true, true) var duration = (System.nanoTime() - start)/10e6 assertTrue(totalNodes <= Math.pow(1.0*maxSiblings, 1.0*levels).toInt(), "Expected total nodes to be less than maximum allowed number") @@ -212,16 +222,16 @@ class NodeTests { val expectedMin = Vector3f(-1.0f, -2.0f, -3.0f) val expectedMax = Vector3f(4.0f, 5.0f, 6.0f) - m.vertices = BufferUtils.allocateFloatAndPut( + m.geometry().vertices = BufferUtils.allocateFloatAndPut( floatArrayOf( expectedMin[0], expectedMin[1], expectedMin[2], expectedMax[0], expectedMax[1], expectedMax[2])) - val expectedPosition = m.vertices.position() - val expectedLimit = m.vertices.limit() + val expectedPosition = m.geometry().vertices.position() + val expectedLimit = m.geometry().vertices.limit() - assertEquals("Vertex buffer position", expectedPosition, m.vertices.position()) - assertEquals("Vertex buffer limit", expectedLimit, m.vertices.limit()) + assertEquals("Vertex buffer position", expectedPosition, m.geometry().vertices.position()) + assertEquals("Vertex buffer limit", expectedLimit, m.geometry().vertices.limit()) m.boundingBox = m.generateBoundingBox() @@ -259,13 +269,13 @@ class NodeTests { @Test fun testCentering() { val m = Mesh() - m.vertices = BufferUtils.allocateFloatAndPut( + m.geometry().vertices = BufferUtils.allocateFloatAndPut( floatArrayOf(-1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f)) m.boundingBox = m.generateBoundingBox() val expectedCenter = Vector3f(1.0f, 1.0f, 1.0f) - val center = m.centerOn(Vector3f(0.0f, 0.0f, 0.0f)) + val center = m.spatial().centerOn(Vector3f(0.0f, 0.0f, 0.0f)) logger.info("Centering on $center, expected=$expectedCenter") assertArrayEquals("Centering on $center", @@ -279,11 +289,11 @@ class NodeTests { @Test fun testFitting() { val m = Mesh() - m.vertices = BufferUtils.allocateFloatAndPut( + m.geometry().vertices = BufferUtils.allocateFloatAndPut( floatArrayOf(-1.0f, -2.0f, -4.0f, 1.0f, 2.0f, 4.0f)) m.boundingBox = m.generateBoundingBox() - val scaling = m.fitInto(0.5f) + val scaling = m.spatial().fitInto(0.5f) val expectedScaling = Vector3f(0.0625f, 0.0625f, 0.0625f) logger.info("Applied scaling: $scaling, expected=$expectedScaling") @@ -299,8 +309,8 @@ class NodeTests { @Test fun testGetScene() { val scene = Scene() - val n1 = Node() - val n2 = Node() + val n1 = RichNode() + val n2 = RichNode() scene.addChild(n1) assertEquals(scene, n1.getScene(), "Expected node scene is attached to to be $scene, but is ${n1.getScene()}") @@ -313,20 +323,20 @@ class NodeTests { @Test fun testPositionChangeTriggersUpdate() { val scene = Scene() - val node = Node() + val node = RichNode() scene.addChild(node) - assertTrue(node.needsUpdate, "Expected node to need update after creation") - assertTrue(node.needsUpdateWorld, "Expected node to need world update after creation") + assertTrue(node.spatial().needsUpdate, "Expected node to need update after creation") + assertTrue(node.spatial().needsUpdateWorld, "Expected node to need world update after creation") - node.position = Random.random3DVectorFromRange(-100.0f, 100.0f) - assertTrue(node.needsUpdate, "Expected node to need update after position change") - assertTrue(node.needsUpdateWorld, "Expected node to need world update after position change") + node.spatial().position = Random.random3DVectorFromRange(-100.0f, 100.0f) + assertTrue(node.spatial().needsUpdate, "Expected node to need update after position change") + assertTrue(node.spatial().needsUpdateWorld, "Expected node to need world update after position change") - scene.updateWorld(true) + scene.spatial().updateWorld(true) - assertFalse(node.needsUpdate, "Expected node to not need update after updating manually") - assertFalse(node.needsUpdateWorld, "Expected node not to need world update after updating manually") + assertFalse(node.spatial().needsUpdate, "Expected node to not need update after updating manually") + assertFalse(node.spatial().needsUpdateWorld, "Expected node not to need world update after updating manually") } /** @@ -335,20 +345,20 @@ class NodeTests { @Test fun testScaleChangeTriggersUpdate() { val scene = Scene() - val node = Node() + val node = RichNode() scene.addChild(node) - assertTrue(node.needsUpdate, "Expected node to need update after creation") - assertTrue(node.needsUpdateWorld, "Expected node to need world update after creation") + assertTrue(node.spatial().needsUpdate, "Expected node to need update after creation") + assertTrue(node.spatial().needsUpdateWorld, "Expected node to need world update after creation") - node.scale = Random.random3DVectorFromRange(0.0f, 1.0f) - assertTrue(node.needsUpdate, "Expected node to need update after position change") - assertTrue(node.needsUpdateWorld, "Expected node to need world update after position change") + node.spatial().scale = Random.random3DVectorFromRange(0.0f, 1.0f) + assertTrue(node.spatial().needsUpdate, "Expected node to need update after position change") + assertTrue(node.spatial().needsUpdateWorld, "Expected node to need world update after position change") - scene.updateWorld(true) + scene.spatial().updateWorld(true) - assertFalse(node.needsUpdate, "Expected node to not need update after updating manually") - assertFalse(node.needsUpdateWorld, "Expected node not to need world update after updating manually") + assertFalse(node.spatial().needsUpdate, "Expected node to not need update after updating manually") + assertFalse(node.spatial().needsUpdateWorld, "Expected node not to need world update after updating manually") } /** @@ -357,20 +367,20 @@ class NodeTests { @Test fun testRotationChangeTriggersUpdate() { val scene = Scene() - val node = Node() + val node = RichNode() scene.addChild(node) - assertTrue(node.needsUpdate, "Expected node to need update after creation") - assertTrue(node.needsUpdateWorld, "Expected node to need world update after creation") + assertTrue(node.spatial().needsUpdate, "Expected node to need update after creation") + assertTrue(node.spatial().needsUpdateWorld, "Expected node to need world update after creation") - node.rotation = Random.randomQuaternion() - assertTrue(node.needsUpdate, "Expected node to need update after position change") - assertTrue(node.needsUpdateWorld, "Expected node to need world update after position change") + node.spatial().rotation = Random.randomQuaternion() + assertTrue(node.spatial().needsUpdate, "Expected node to need update after position change") + assertTrue(node.spatial().needsUpdateWorld, "Expected node to need world update after position change") - scene.updateWorld(true) + scene.spatial().updateWorld(true) - assertFalse(node.needsUpdate, "Expected node to not need update after updating manually") - assertFalse(node.needsUpdateWorld, "Expected node not to need world update after updating manually") + assertFalse(node.spatial().needsUpdate, "Expected node to not need update after updating manually") + assertFalse(node.spatial().needsUpdateWorld, "Expected node not to need world update after updating manually") } /** @@ -378,27 +388,27 @@ class NodeTests { */ @Test fun testNodeRecursion() { - val parent = Node() - val child1 = Node() - val child2 = Node() - val grandchild = Node() + val parent = RichNode() + val child1 = RichNode() + val child2 = RichNode() + val grandchild = RichNode() - val myShinyNewMaterial = Material() + val myShinyNewMaterial = DefaultMaterial() parent.addChild(child1) parent.addChild(child2) child1.addChild(grandchild) - parent.runRecursive { it.material = myShinyNewMaterial } + parent.runRecursive { it.addAttribute(Material::class.java, myShinyNewMaterial) } assertEquals("Parent of $child1 should be $parent", parent, child1.parent) assertEquals("Parent of $child2 should be $parent", parent, child2.parent) - assertEquals("Material of parent should be $myShinyNewMaterial", myShinyNewMaterial, parent.material) - assertEquals("Material of child1 should be $myShinyNewMaterial", myShinyNewMaterial, child1.material) - assertEquals("Material of child2 should be $myShinyNewMaterial", myShinyNewMaterial, child2.material) - assertEquals("Material of grandchild should be $myShinyNewMaterial", myShinyNewMaterial, grandchild.material) + assertEquals("Material of parent should be $myShinyNewMaterial", myShinyNewMaterial, parent.material()) + assertEquals("Material of child1 should be $myShinyNewMaterial", myShinyNewMaterial, child1.material()) + assertEquals("Material of child2 should be $myShinyNewMaterial", myShinyNewMaterial, child2.material()) + assertEquals("Material of grandchild should be $myShinyNewMaterial", myShinyNewMaterial, grandchild.material()) parent.visible = false parent.runRecursive { assertFalse(it.visible, "Child node should be invisible") } @@ -409,24 +419,24 @@ class NodeTests { */ @Test fun testNodeRecursionJavaConsumer() { - val parent = Node() - val child1 = Node() - val child2 = Node() - val grandchild = Node() + val parent = RichNode() + val child1 = RichNode() + val child2 = RichNode() + val grandchild = RichNode() - val myShinyNewMaterial = Material() + val myShinyNewMaterial = DefaultMaterial() parent.addChild(child1) parent.addChild(child2) child1.addChild(grandchild) - parent.runRecursive(Consumer { it.material = myShinyNewMaterial }) + parent.runRecursive(Consumer { it.addAttribute(Material::class.java, myShinyNewMaterial) }) - assertEquals("Material of parent should be $myShinyNewMaterial", myShinyNewMaterial, parent.material) - assertEquals("Material of child1 should be $myShinyNewMaterial", myShinyNewMaterial, child1.material) - assertEquals("Material of child2 should be $myShinyNewMaterial", myShinyNewMaterial, child2.material) - assertEquals("Material of grandchild should be $myShinyNewMaterial", myShinyNewMaterial, grandchild.material) + assertEquals("Material of parent should be $myShinyNewMaterial", myShinyNewMaterial, parent.material()) + assertEquals("Material of child1 should be $myShinyNewMaterial", myShinyNewMaterial, child1.material()) + assertEquals("Material of child2 should be $myShinyNewMaterial", myShinyNewMaterial, child2.material()) + assertEquals("Material of grandchild should be $myShinyNewMaterial", myShinyNewMaterial, grandchild.material()) } /** @@ -455,14 +465,14 @@ class NodeTests { @Test fun testShaderProperties() { val randomInt = kotlin.random.Random.nextInt() - val n = object: Node("MyNode") { + val n = object: RichNode("MyNode") { @ShaderProperty val myShaderProperty = randomInt } assertEquals(randomInt, n.getShaderProperty("myShaderProperty"), "Expected value from shader property to be") - val m = object: Node("MyOtherNode") { + val m = object: RichNode("MyOtherNode") { @ShaderProperty val shaderProperties = HashMap() } @@ -490,10 +500,10 @@ class NodeTests { @Test fun testMaximumBoundingBox() { val parent = Group() - parent.position = Vector3f(100f, 100f, 100f) + parent.spatial().position = Vector3f(100f, 100f, 100f) ( 0 until 2).forEach {i -> val sphere = Sphere(5f, 3) - sphere.position = Vector3f(-100f * ( i % 2 ), 0f, i.toFloat()) + sphere.spatial().position = Vector3f(-100f * ( i % 2 ), 0f, i.toFloat()) parent.addChild(sphere) } @@ -507,34 +517,34 @@ class NodeTests { */ @Test fun testImglib2Implementations() { - val n = Node("testnode") + val n = RichNode("testnode") - assertEquals( 3, n.numDimensions() ) + assertEquals( 3, n.spatial().numDimensions()) - n.position = Vector3f(0f, 17f, 0f) - assertEquals( 17f, n.getFloatPosition(1), 0.01f) + n.spatial().position = Vector3f(0f, 17f, 0f) + assertEquals( 17f, n.spatial().getFloatPosition(1), 0.01f) - n.position = Vector3f(0f, 17f, 0f) - n.move( -1, 1 ) - assertEquals( 16f, n.position.y(), 0.01f) + n.spatial().position = Vector3f(0f, 17f, 0f) + n.spatial().move( -1, 1 ) + assertEquals( 16f, n.spatial().position.y(), 0.01f) - n.position = Vector3f(0f, 17f, 0f) - n.move(RealPoint(0.0, -1.0, 0.0)) - assertEquals( 16f, n.position.y(), 0.01f) + n.spatial().position = Vector3f(0f, 17f, 0f) + n.spatial().move(RealPoint(0.0, -1.0, 0.0)) + assertEquals( 16f, n.spatial().position.y(), 0.01f) - n.position = Vector3f(0f, 17f, 0f) - n.fwd(0) - assertEquals( 1f, n.position.x(), 0.01f) + n.spatial().position = Vector3f(0f, 17f, 0f) + n.spatial().fwd(0) + assertEquals( 1f, n.spatial().position.x(), 0.01f) - n.position = Vector3f(0f, 17f, 0f) - n.setPosition(doubleArrayOf(17.0, 23.0, -19.0)) - assertEquals( 17.0f, n.position.x(), 0.01f) - assertEquals( 23.0f, n.position.y(), 0.01f) - assertEquals( -19.0f, n.position.z(), 0.01f) + n.spatial().position = Vector3f(0f, 17f, 0f) + n.spatial().setPosition(doubleArrayOf(17.0, 23.0, -19.0)) + assertEquals( 17.0f, n.spatial().position.x(), 0.01f) + assertEquals( 23.0f, n.spatial().position.y(), 0.01f) + assertEquals( -19.0f, n.spatial().position.z(), 0.01f) val pos = FloatArray(3) - n.position = Vector3f(0f, 17f, 0f) - n.localize(pos) + n.spatial().position = Vector3f(0f, 17f, 0f) + n.spatial().localize(pos) assertEquals( 17f, pos[1], 0.01f) } } diff --git a/src/test/kotlin/graphics/scenery/tests/unit/RainbowTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/RainbowTests.kt index b9ac052fe..3b606d6e3 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/RainbowTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/RainbowTests.kt @@ -34,7 +34,7 @@ class RainbowTests { val rainbowPalette = rainbowPaletteNotScaled.map { it.mul(1/255f) } val children = child2.children for(j in 0 until 6) { - assertEquals(children[j].material.diffuse, rainbowPalette[j]) + assertEquals(children[j].materialOrNull()?.diffuse, rainbowPalette[j]) } } } diff --git a/src/test/kotlin/graphics/scenery/tests/unit/SceneTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/SceneTests.kt index 18a6ed1fb..e3d070d15 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/SceneTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/SceneTests.kt @@ -3,6 +3,7 @@ package graphics.scenery.tests.unit import org.joml.Vector3f import graphics.scenery.Box import graphics.scenery.Node +import graphics.scenery.RichNode import graphics.scenery.Scene import graphics.scenery.numerics.Random import graphics.scenery.utils.LazyLogger @@ -28,14 +29,14 @@ class SceneTests { (0 until count).map { Box() }.forEachIndexed { i, n -> - n.position = Vector3f( + n.spatial().position = Vector3f( Random.randomFromRange(-0.4f, 0.4f), Random.randomFromRange(-0.4f, 0.4f), -1.0f + i * (-1.5f)) scene.addChild(n) } - scene.updateWorld(true) + scene.spatial().updateWorld(true) val results = scene.raycast( position = Vector3f(0.0f), @@ -61,7 +62,7 @@ class SceneTests { } val nodeCount = Random.randomFromRange(2.0f, 10.0f).roundToInt() val nodes = (0 until nodeCount).map { - Node() + RichNode() } nodes.forEach { scene.addChild(it) } diff --git a/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/ArcballCameraControlTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/ArcballCameraControlTests.kt index 4bdeac4f7..17dd3dfe7 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/ArcballCameraControlTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/ArcballCameraControlTests.kt @@ -44,7 +44,7 @@ class ArcballCameraControlTests { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial().position = Vector3f(0.0f, 0.0f, 5.0f) perspectiveCamera(50.0f, 512, 512) scene.addChild(this) } @@ -66,7 +66,9 @@ class ArcballCameraControlTests { /*TODO: Generate multiple test sequences*/ for(i in 1..seqLength) { - printWriter.println("[$i, $x, $y, ${cam.rotation.x}, ${cam.rotation.y}, ${cam.rotation.z}, ${cam.rotation.w}, ${cam.position.x()}, ${cam.position.y()}, ${cam.position.z()}]") + cam.spatial { + printWriter.println("[$i, $x, $y, ${rotation.x}, ${rotation.y}, ${rotation.z}, ${rotation.w}, ${position.x()}, ${position.y()}, ${position.z()}]") + } val deltaX = kotlin.random.Random.nextInt(-movementRange, movementRange) val deltaY = kotlin.random.Random.nextInt(-movementRange, movementRange) @@ -100,7 +102,7 @@ class ArcballCameraControlTests { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial().position = Vector3f(0.0f, 0.0f, 5.0f) perspectiveCamera(50.0f, 512, 512) scene.addChild(this) } @@ -124,8 +126,8 @@ class ArcballCameraControlTests { val expectedRotation = Quaternionf(numbers[3], numbers[4], numbers[5], numbers[6]) val expectedPosition = Vector3f(numbers[7], numbers[8], numbers[9]) - Assert.assertEquals("Computed camera rotation is incorrect", expectedRotation, cam.rotation) - Assert.assertEquals("Computed camera position is incorrect", expectedPosition, cam.position) + Assert.assertEquals("Computed camera rotation is incorrect", expectedRotation, cam.spatial().rotation) + Assert.assertEquals("Computed camera position is incorrect", expectedPosition, cam.spatial().position) } } } diff --git a/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/GamepadCameraControlTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/GamepadCameraControlTests.kt index 079f44a73..104f1c4fb 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/GamepadCameraControlTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/controls/behaviours/GamepadCameraControlTests.kt @@ -40,7 +40,7 @@ class GamepadCameraControlTests { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial().position = Vector3f(0.0f, 0.0f, 5.0f) perspectiveCamera(50.0f, 512, 512) scene.addChild(this) } @@ -61,7 +61,9 @@ class GamepadCameraControlTests { else { gamepadCameraControl.axisEvent(Component.Identifier.Axis.Y, rotationVal) } - printWriter.println("[$i, $axis, $rotationVal, ${cam.rotation.x}, ${cam.rotation.y}, ${cam.rotation.z}, ${cam.rotation.w}]") + cam.spatial { + printWriter.println("[$i, $axis, $rotationVal, ${rotation.x}, ${rotation.y}, ${rotation.z}, ${rotation.w}]") + } } printWriter.close() } @@ -88,7 +90,9 @@ class GamepadCameraControlTests { val cam: Camera = DetachedHeadCamera() with(cam) { - position = Vector3f(0.0f, 0.0f, 5.0f) + spatial { + position = Vector3f(0.0f, 0.0f, 5.0f) + } perspectiveCamera(50.0f, 512, 512) scene.addChild(this) } @@ -103,10 +107,10 @@ class GamepadCameraControlTests { else { gamepadCameraControl.axisEvent(Component.Identifier.Axis.Y, numbers[2]) } - Assert.assertEquals("Computed camera rotation is incorrect", numbers[3], cam.rotation.x, 0.00001f) - Assert.assertEquals("Computed camera rotation is incorrect", numbers[4], cam.rotation.y, 0.00001f) - Assert.assertEquals("Computed camera rotation is incorrect", numbers[5], cam.rotation.z, 0.00001f) - Assert.assertEquals("Computed camera rotation is incorrect", numbers[6], cam.rotation.w, 0.00001f) + Assert.assertEquals("Computed camera rotation is incorrect", numbers[3], cam.spatial().rotation.x, 0.00001f) + Assert.assertEquals("Computed camera rotation is incorrect", numbers[4], cam.spatial().rotation.y, 0.00001f) + Assert.assertEquals("Computed camera rotation is incorrect", numbers[5], cam.spatial().rotation.z, 0.00001f) + Assert.assertEquals("Computed camera rotation is incorrect", numbers[6], cam.spatial().rotation.w, 0.00001f) } } } diff --git a/src/test/kotlin/graphics/scenery/tests/unit/fonts/SDFFontAtlasTests.kt b/src/test/kotlin/graphics/scenery/tests/unit/fonts/SDFFontAtlasTests.kt index aaaaf8537..1e0d5071f 100644 --- a/src/test/kotlin/graphics/scenery/tests/unit/fonts/SDFFontAtlasTests.kt +++ b/src/test/kotlin/graphics/scenery/tests/unit/fonts/SDFFontAtlasTests.kt @@ -60,8 +60,8 @@ class SDFFontAtlasTests { } val mesh = sdf.createMeshForString("hello world") - assertTrue(mesh.vertices.remaining() > 0) - assertTrue(mesh.normals.remaining() > 0) - assertTrue(mesh.texcoords.remaining() > 0) + assertTrue(mesh.geometry().vertices.remaining() > 0) + assertTrue(mesh.geometry().normals.remaining() > 0) + assertTrue(mesh.geometry().texcoords.remaining() > 0) } }