From 922ac41a4a753a9f7d4e094092375bd877f67446 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 31 Mar 2022 14:41:08 -0400
Subject: [PATCH 001/679] voxels
---
.../Voxel/VoxelBox3DTiles/0/0/0/0/a.bin | Bin 0 -> 32 bytes
.../Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin | Bin 0 -> 312 bytes
.../Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf | 1 +
.../Voxel/VoxelBox3DTiles/schema.json | 1 +
.../Voxel/VoxelBox3DTiles/tileset.json | 1 +
.../Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin | Bin 0 -> 32 bytes
.../VoxelCylinder3DTiles/0/0/0/0/subtree.bin | Bin 0 -> 312 bytes
.../VoxelCylinder3DTiles/0/0/0/0/tile.gltf | 1 +
.../Voxel/VoxelCylinder3DTiles/schema.json | 1 +
.../Voxel/VoxelCylinder3DTiles/tileset.json | 1 +
.../Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin | Bin 0 -> 32 bytes
.../VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin | Bin 0 -> 312 bytes
.../VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf | 1 +
.../Voxel/VoxelEllipsoid3DTiles/schema.json | 1 +
.../Voxel/VoxelEllipsoid3DTiles/tileset.json | 1 +
Apps/Sandcastle/gallery/Voxels.html | 507 ++++
Source/Core/PrimitiveType.js | 29 +-
Source/Renderer/Pass.js | 5 +-
Source/Scene/Cesium3DTilesVoxelProvider.js | 632 ++++
Source/Scene/DerivedCommand.js | 7 +
Source/Scene/GltfLoader.js | 15 +
Source/Scene/GltfVoxelProvider.js | 404 +++
Source/Scene/MetadataComponentType.js | 82 +
Source/Scene/ModelComponents.js | 48 +
Source/Scene/Scene.js | 17 +
Source/Scene/VoxelBoxShape.js | 325 ++
Source/Scene/VoxelCylinderShape.js | 529 ++++
Source/Scene/VoxelEllipsoidShape.js | 1027 +++++++
Source/Scene/VoxelPrimitive.js | 2695 +++++++++++++++++
Source/Scene/VoxelProvider.js | 191 ++
Source/Scene/VoxelShape.js | 129 +
Source/Scene/VoxelShapeType.js | 90 +
Source/Scene/VoxelTraversal.js | 2020 ++++++++++++
.../Builtin/Constants/passOverlay.glsl | 2 +-
.../Shaders/Builtin/Constants/passVoxels.glsl | 9 +
.../Functions/windowToEyeCoordinates.glsl | 2 +-
Source/Shaders/VoxelFS.glsl | 1484 +++++++++
Source/Shaders/VoxelVS.glsl | 11 +
.../CesiumInspector/CesiumInspector.css | 2 +-
Source/Widgets/Viewer/Viewer.css | 11 +
Source/Widgets/Viewer/Viewer.js | 60 +-
.../Viewer/viewerVoxelInspectorMixin.js | 34 +
.../Widgets/VoxelInspector/VoxelInspector.css | 121 +
.../Widgets/VoxelInspector/VoxelInspector.js | 422 +++
.../VoxelInspector/VoxelInspectorViewModel.js | 1130 +++++++
Source/Widgets/widgets.css | 1 +
.../Voxel/VoxelBox3DTiles/0/0/0/0/a.bin | Bin 0 -> 32 bytes
.../Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin | Bin 0 -> 312 bytes
.../Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf | 1 +
.../Voxel/VoxelBox3DTiles/schema.json | 1 +
.../Voxel/VoxelBox3DTiles/tileset.json | 1 +
.../Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin | Bin 0 -> 32 bytes
.../VoxelCylinder3DTiles/0/0/0/0/subtree.bin | Bin 0 -> 312 bytes
.../VoxelCylinder3DTiles/0/0/0/0/tile.gltf | 1 +
.../Voxel/VoxelCylinder3DTiles/schema.json | 1 +
.../Voxel/VoxelCylinder3DTiles/tileset.json | 1 +
.../Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin | Bin 0 -> 32 bytes
.../VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin | Bin 0 -> 312 bytes
.../VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf | 1 +
.../Voxel/VoxelEllipsoid3DTiles/schema.json | 1 +
.../Voxel/VoxelEllipsoid3DTiles/tileset.json | 1 +
Specs/Scene/Cesium3DTilesVoxelProviderSpec.js | 123 +
Specs/Scene/GltfVoxelProviderSpec.js | 137 +
Specs/Scene/VoxelBoxShapeSpec.js | 696 +++++
Specs/Scene/VoxelCylinderShapeSpec.js | 214 ++
Specs/Scene/VoxelEllipsoidShapeSpec.js | 179 ++
Specs/Scene/VoxelPrimitiveSpec.js | 224 ++
Specs/Scene/VoxelShapeTypeSpec.js | 43 +
Specs/Scene/VoxelTraversalSpec.js | 396 +++
Specs/Widgets/Viewer/ViewerSpec.js | 159 +
70 files changed, 14175 insertions(+), 55 deletions(-)
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/a.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/subtree.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
create mode 100644 Apps/Sandcastle/gallery/Voxels.html
create mode 100644 Source/Scene/Cesium3DTilesVoxelProvider.js
create mode 100644 Source/Scene/GltfVoxelProvider.js
create mode 100644 Source/Scene/VoxelBoxShape.js
create mode 100644 Source/Scene/VoxelCylinderShape.js
create mode 100644 Source/Scene/VoxelEllipsoidShape.js
create mode 100644 Source/Scene/VoxelPrimitive.js
create mode 100644 Source/Scene/VoxelProvider.js
create mode 100644 Source/Scene/VoxelShape.js
create mode 100644 Source/Scene/VoxelShapeType.js
create mode 100644 Source/Scene/VoxelTraversal.js
create mode 100644 Source/Shaders/Builtin/Constants/passVoxels.glsl
create mode 100644 Source/Shaders/VoxelFS.glsl
create mode 100644 Source/Shaders/VoxelVS.glsl
create mode 100644 Source/Widgets/Viewer/viewerVoxelInspectorMixin.js
create mode 100644 Source/Widgets/VoxelInspector/VoxelInspector.css
create mode 100644 Source/Widgets/VoxelInspector/VoxelInspector.js
create mode 100644 Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/a.bin
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/subtree.bin
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
create mode 100644 Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
create mode 100644 Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
create mode 100644 Specs/Scene/GltfVoxelProviderSpec.js
create mode 100644 Specs/Scene/VoxelBoxShapeSpec.js
create mode 100644 Specs/Scene/VoxelCylinderShapeSpec.js
create mode 100644 Specs/Scene/VoxelEllipsoidShapeSpec.js
create mode 100644 Specs/Scene/VoxelPrimitiveSpec.js
create mode 100644 Specs/Scene/VoxelShapeTypeSpec.js
create mode 100644 Specs/Scene/VoxelTraversalSpec.js
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/a.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin
new file mode 100644
index 0000000000000000000000000000000000000000..2518924ca56aa3fb60ed2c89e2e765ead97fb28a
GIT binary patch
literal 312
zcmXqZ31MJlU|&E7I!ej;c_pcNCFJPL$jnIzE=?*aN+oEq0obDAl0=XzK-MCh
zQLJPYT@B>}<$zL^C8_>tX~n4^r4X)9YF>It2GIQmwXrb0P<=qfa9Ok3*jfb$fCdjV
HbQl-_(;r_a
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
new file mode 100644
index 00000000000..227fd904003
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483648,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxel":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxel","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxel","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
new file mode 100644
index 00000000000..74b24b34572
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
@@ -0,0 +1 @@
+{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
new file mode 100644
index 00000000000..39e46626920
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
@@ -0,0 +1 @@
+{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/subtree.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/subtree.bin
new file mode 100644
index 0000000000000000000000000000000000000000..2518924ca56aa3fb60ed2c89e2e765ead97fb28a
GIT binary patch
literal 312
zcmXqZ31MJlU|&E7I!ej;c_pcNCFJPL$jnIzE=?*aN+oEq0obDAl0=XzK-MCh
zQLJPYT@B>}<$zL^C8_>tX~n4^r4X)9YF>It2GIQmwXrb0P<=qfa9Ok3*jfb$fCdjV
HbQl-_(;r_a
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
new file mode 100644
index 00000000000..bf65877f714
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483650,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
new file mode 100644
index 00000000000..74b24b34572
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
@@ -0,0 +1 @@
+{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
new file mode 100644
index 00000000000..39e46626920
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
@@ -0,0 +1 @@
+{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin
new file mode 100644
index 0000000000000000000000000000000000000000..2518924ca56aa3fb60ed2c89e2e765ead97fb28a
GIT binary patch
literal 312
zcmXqZ31MJlU|&E7I!ej;c_pcNCFJPL$jnIzE=?*aN+oEq0obDAl0=XzK-MCh
zQLJPYT@B>}<$zL^C8_>tX~n4^r4X)9YF>It2GIQmwXrb0P<=qfa9Ok3*jfb$fCdjV
HbQl-_(;r_a
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
new file mode 100644
index 00000000000..90e35e2f214
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483649,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2],"bounds":{"min":[0.0,0.0,-1.0],"max":[1.0,1.0,0.0]}}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
new file mode 100644
index 00000000000..74b24b34572
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
@@ -0,0 +1 @@
+{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
new file mode 100644
index 00000000000..f68ae62e2ab
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
@@ -0,0 +1 @@
+{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"region":[0.0,0.0,1.0,1.0,-1.0,0.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[6378137.0,0.0,0.0,0.0,0.0,6378137.0,0.0,0.0,0.0,0.0,6356752.314245179,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
new file mode 100644
index 00000000000..8ec79d69033
--- /dev/null
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -0,0 +1,507 @@
+
+
+
+
+
+
+
+
+ Cesium Demo
+
+
+
+
+
+
+
+ Loading...
+
+
+
+
diff --git a/Source/Core/PrimitiveType.js b/Source/Core/PrimitiveType.js
index 639c0955a9d..eb86d821493 100644
--- a/Source/Core/PrimitiveType.js
+++ b/Source/Core/PrimitiveType.js
@@ -65,6 +65,30 @@ const PrimitiveType = {
* @constant
*/
TRIANGLE_FAN: WebGLConstants.TRIANGLE_FAN,
+
+ /**
+ * Box voxel primitive from EXT_primitive_voxels.
+ *
+ * @type {Number}
+ * @constant
+ */
+ VOXEL_BOX: 0x80000000,
+
+ /**
+ * Ellipsoid voxel primitive from EXT_primitive_voxels.
+ *
+ * @type {Number}
+ * @constant
+ */
+ VOXEL_ELLIPSOID: 0x80000001,
+
+ /**
+ * Cylinder voxel primitive from EXT_primitive_voxels.
+ *
+ * @type {Number}
+ * @constant
+ */
+ VOXEL_CYLINDER: 0x80000002,
};
/**
@@ -78,7 +102,10 @@ PrimitiveType.validate = function (primitiveType) {
primitiveType === PrimitiveType.LINE_STRIP ||
primitiveType === PrimitiveType.TRIANGLES ||
primitiveType === PrimitiveType.TRIANGLE_STRIP ||
- primitiveType === PrimitiveType.TRIANGLE_FAN
+ primitiveType === PrimitiveType.TRIANGLE_FAN ||
+ primitiveType === PrimitiveType.VOXEL_BOX ||
+ primitiveType === PrimitiveType.VOXEL_CYLINDER ||
+ primitiveType === PrimitiveType.VOXEL_ELLIPSOID
);
};
diff --git a/Source/Renderer/Pass.js b/Source/Renderer/Pass.js
index f965d1ea75f..bdfe61dc156 100644
--- a/Source/Renderer/Pass.js
+++ b/Source/Renderer/Pass.js
@@ -20,7 +20,8 @@ const Pass = {
CESIUM_3D_TILE_CLASSIFICATION_IGNORE_SHOW: 6,
OPAQUE: 7,
TRANSLUCENT: 8,
- OVERLAY: 9,
- NUMBER_OF_PASSES: 10,
+ VOXELS: 9,
+ OVERLAY: 10,
+ NUMBER_OF_PASSES: 11,
};
export default Object.freeze(Pass);
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
new file mode 100644
index 00000000000..7b5037b3167
--- /dev/null
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -0,0 +1,632 @@
+import Cartesian3 from "../Core/Cartesian3.js";
+import Check from "../Core/Check.js";
+import ComponentDatatype from "../Core/ComponentDatatype.js";
+import defaultValue from "../Core/defaultValue.js";
+import defer from "../Core/defer.js";
+import defined from "../Core/defined.js";
+import DeveloperError from "../Core/DeveloperError.js";
+import DoubleEndedPriorityQueue from "../Core/DoubleEndedPriorityQueue.js";
+import Matrix4 from "../Core/Matrix4.js";
+import Resource from "../Core/Resource.js";
+import Cesium3DTilesetMetadata from "./Cesium3DTilesetMetadata.js";
+import GltfLoader from "./GltfLoader.js";
+import ImplicitSubdivisionScheme from "./ImplicitSubdivisionScheme.js";
+import ImplicitSubtree from "./ImplicitSubtree.js";
+import ImplicitTileCoordinates from "./ImplicitTileCoordinates.js";
+import ImplicitTileset from "./ImplicitTileset.js";
+import MetadataComponentType from "./MetadataComponentType.js";
+import MetadataType from "./MetadataType.js";
+import ResourceCache from "./ResourceCache.js";
+import VoxelShapeType from "./VoxelShapeType.js";
+
+/**
+ * A {@link VoxelProvider} that fetches voxel data from a 3D Tiles tileset.
+ *
+ * @alias Cesium3DTilesVoxelProvider
+ * @constructor
+ *
+ * @param {Object} options Object with the following properties:
+ * @param {String|Resource|Uint8Array} options.url The URL to the tileset directory
+ *
+ * @see VoxelProvider
+ */
+function Cesium3DTilesVoxelProvider(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ //>>includeStart('debug', pragmas.debug)
+ Check.defined("options.url", options.url);
+ //>>includeEnd('debug');
+
+ /**
+ * Gets a value indicating whether or not the provider is ready for use.
+ * @type {Boolean}
+ * @readonly
+ */
+ this.ready = false;
+
+ this._readyPromise = defer();
+
+ /**
+ * Gets the promise that will be resolved when the provider is ready for use.
+ * @type {Promise.}
+ * @readonly
+ */
+ this.readyPromise = this._readyPromise.promise;
+
+ /**
+ * An optional model matrix that is applied to all tiles
+ * @type {Matrix4}
+ * @readonly
+ */
+ this.modelMatrix = undefined;
+
+ /**
+ * Gets the {@link VoxelShapeType}
+ * @type {VoxelShapeType}
+ * @readonly
+ */
+ this.shape = undefined;
+
+ /**
+ * Gets the minimum bounds.
+ * If undefined, the shape's default minimum bounds will be used instead.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ this.minBounds = undefined;
+
+ /**
+ * Gets the maximum bounds.
+ * If undefined, the shape's default maximum bounds will be used instead.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ this.maxBounds = undefined;
+
+ /**
+ * Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ this.dimensions = undefined;
+
+ /**
+ * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
+ * TODO: mark this optional
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Number}
+ * @readonly
+ */
+ this.paddingBefore = undefined;
+
+ /**
+ * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * TODO: mark this optional
+ * @type {Number}
+ * @readonly
+ */
+ this.paddingAfter = undefined;
+
+ // TODO is there a good user-facing way to set names, types, componentTypes, min, max, etc? MetadataComponents.Primitive is close, but private and has some fields that voxels don't use
+
+ /**
+ * Gets stuff
+ * @type {String[]}
+ */
+ this.names = new Array();
+
+ /**
+ * Gets stuff
+ * @type {MetadataType[]}
+ */
+ this.types = new Array();
+
+ /**
+ * Gets stuff
+ * @type {MetadataComponentType[]}
+ */
+ this.componentTypes = new Array();
+
+ /**
+ * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
+ * Gets the minimum value
+ * @type {Number[]}
+ */
+ this.minimumValues = undefined;
+
+ /**
+ * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
+ * Gets the maximum value
+ * @type {Number[][]}
+ */
+ this.maximumValues = undefined;
+
+ /**
+ * The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be set to 0.
+ * @type {Number}
+ */
+ this.maximumTileCount = undefined;
+
+ /**
+ * @type {ImplicitTileset}
+ */
+ this._implicitTileset = undefined;
+
+ /**
+ * @type {ImplicitSubtreeCache}
+ */
+ this._subtreeCache = new ImplicitSubtreeCache();
+
+ /**
+ * glTFs that are in the process of being loaded.
+ * @type {GltfLoader[]}
+ */
+ this._gltfLoaders = new Array();
+
+ /**
+ * Subtrees that are in the process of being loaded.
+ * This member exists for unit test purposes only. See _doneLoading.
+ * @type {Subtree[]}
+ */
+ this._subtreeLoaders = new Array();
+
+ /**
+ * Subtree resources that are in the process of being loaded.
+ * This member exists for unit test purposes only. See _doneLoading.
+ * @type {Resource[]}
+ */
+ this._subtreeResourceLoaders = new Array();
+
+ const that = this;
+ let tilesetJson;
+ let tileJson;
+ let metadata;
+ let implicitTileset;
+
+ // 1. Load tileset.json
+ // 2. Load schema.json
+ // 3. Load root glTF to get provider properties
+ const resource = Resource.createIfNeeded(options.url);
+ const tilesetPromise = resource.fetchJson();
+ tilesetPromise
+ .then(function (tileset) {
+ tilesetJson = tileset;
+ tileJson = tilesetJson.root;
+
+ let metadataSchemaLoader;
+ if (defined(tilesetJson.schemaUri)) {
+ metadataSchemaLoader = ResourceCache.loadSchema({
+ resource: resource.getDerivedResource({
+ url: tilesetJson.schemaUri,
+ preserveQueryParameters: true,
+ }),
+ });
+ } else {
+ metadataSchemaLoader = ResourceCache.loadSchema({
+ schema: tilesetJson.schema,
+ });
+ }
+ return metadataSchemaLoader.promise;
+ })
+ .then(function (schemaLoader) {
+ const metadataSchema = schemaLoader.schema;
+ metadata = new Cesium3DTilesetMetadata({
+ metadataJson: tilesetJson,
+ schema: metadataSchema,
+ });
+ implicitTileset = new ImplicitTileset(resource, tileJson, metadataSchema);
+
+ // TODO make sure this fails when root tile is not available?
+ const rootCoord = new ImplicitTileCoordinates({
+ subdivisionScheme: implicitTileset.subdivisionScheme,
+ subtreeLevels: implicitTileset.subtreeLevels,
+ level: 0,
+ x: 0,
+ y: 0,
+ z: 0,
+ });
+ const rootGltfLoader = getGltfLoader(implicitTileset, rootCoord);
+ that._gltfLoaders.push(rootGltfLoader);
+ return rootGltfLoader.promise;
+ })
+ .then(function (rootGltfLoader) {
+ that._gltfLoaders.splice(that._gltfLoaders.indexOf(rootGltfLoader));
+ const gltfPrimitive = rootGltfLoader.components.nodes[0].primitives[0];
+ const voxel = gltfPrimitive.voxel;
+ const primitiveType = gltfPrimitive.primitiveType;
+ const attributes = gltfPrimitive.attributes;
+
+ const attributesLength = attributes.length;
+ const names = new Array(attributesLength);
+ const types = new Array(attributesLength);
+ const componentTypes = new Array(attributesLength);
+ const minimumValues = new Array(attributesLength);
+ const maximumValues = new Array(attributesLength);
+
+ const schema = metadata.schema;
+ const statistics = metadata.statistics;
+ const classNames = Object.keys(schema.classes);
+ const classNamesLength = classNames.length;
+ for (let i = 0; i < classNamesLength; i++) {
+ const className = classNames[i];
+ const classStatistics = statistics.classes[className];
+ const classInfo = schema.classes[className];
+ const properties = classInfo.properties;
+ const propertyNames = Object.keys(properties);
+ const propertyNamesLength = propertyNames.length;
+ for (let i = 0; i < propertyNamesLength; i++) {
+ const propertyName = propertyNames[i];
+ const property = properties[propertyName];
+ const propertyStatistics = classStatistics.properties[propertyName];
+ const propertyMin = Array.isArray(propertyStatistics.min)
+ ? propertyStatistics.min
+ : [propertyStatistics.min];
+ const propertyMax = Array.isArray(propertyStatistics.max)
+ ? propertyStatistics.max
+ : [propertyStatistics.max];
+ const metadataType = property.type;
+ const metadataComponentType = property.componentType;
+ const metadataComponentCount = MetadataType.getComponentCount(
+ metadataType
+ );
+
+ names[i] = propertyName;
+ types[i] = metadataType;
+ componentTypes[i] = metadataComponentType;
+ minimumValues[i] = new Array(metadataComponentCount);
+ maximumValues[i] = new Array(metadataComponentCount);
+
+ for (let j = 0; j < metadataComponentCount; j++) {
+ minimumValues[i][j] = propertyMin[j];
+ maximumValues[i][j] = propertyMax[j];
+ }
+ }
+ }
+
+ that.shape = VoxelShapeType.fromPrimitiveType(primitiveType);
+ that.minBounds = Cartesian3.clone(voxel.minBounds);
+ that.maxBounds = Cartesian3.clone(voxel.maxBounds);
+ that.dimensions = Cartesian3.clone(voxel.dimensions);
+ that.paddingBefore = Cartesian3.clone(voxel.paddingBefore);
+ that.paddingAfter = Cartesian3.clone(voxel.paddingAfter);
+ that.maximumTileCount = defined(statistics.classes.tile)
+ ? statistics.classes.tile.count
+ : undefined;
+ that.modelMatrix = Matrix4.clone(tileJson.transform);
+ that.names = names;
+ that.types = types;
+ that.componentTypes = componentTypes;
+ that.minimumValues = minimumValues;
+ that.maximumValues = maximumValues;
+ that.ready = true;
+ that._readyPromise.resolve(that);
+ that._implicitTileset = implicitTileset;
+ })
+ .catch(function (error) {
+ that._readyPromise.reject(error);
+ });
+}
+
+const scratchImplicitTileCoordinates = new ImplicitTileCoordinates({
+ subdivisionScheme: ImplicitSubdivisionScheme.OCTREE, // not known yet
+ subtreeLevels: 1, // not known yet
+ level: 0,
+ x: 0,
+ y: 0,
+ z: 0,
+});
+
+/**
+ * Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z.
+ * This function should not be called before {@link VoxelProvider#ready} returns true.
+ * @param {Object} [options] Object with the following properties:
+ * @param {Number} [options.tileLevel=0] The tile's level.
+ * @param {Number} [options.tileX=0] The tile's X coordinate.
+ * @param {Number} [options.tileY=0] The tile's Y coordinate.
+ * @param {Number} [options.tileZ=0] The tile's Z coordinate.
+ * @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ const tileLevel = defaultValue(options.tileLevel, 0);
+ const tileX = defaultValue(options.tileX, 0);
+ const tileY = defaultValue(options.tileY, 0);
+ const tileZ = defaultValue(options.tileZ, 0);
+
+ //>>includeStart('debug', pragmas.debug);
+ if (!this.ready) {
+ throw new DeveloperError(
+ "The provider is not ready. Use Cesium3DTilesVoxelProvider.readyPromise or wait for Cesium3DTilesVoxelProvider.ready to be true."
+ );
+ }
+ //>>includeEnd('debug');
+
+ // 1. Load the subtree that the tile belongs to (possibly from the subtree cache)
+ // 1a. If not in the cache, load the subtree resource
+ // 1b. If not in the cache, load the subtree object
+ // 2. Load the glTF if available
+
+ const implicitTileset = this._implicitTileset;
+ const subtreeCache = this._subtreeCache;
+ const types = this.types;
+ const componentTypes = this.componentTypes;
+
+ const tileCoordinates = scratchImplicitTileCoordinates;
+ tileCoordinates.subdivisionScheme = implicitTileset.subdivisionScheme;
+ tileCoordinates.subtreeLevels = implicitTileset.subtreeLevels;
+ tileCoordinates.level = tileLevel;
+ tileCoordinates.x = tileX;
+ tileCoordinates.y = tileY;
+ tileCoordinates.z = tileZ;
+
+ // First load the subtree to check if the tile is available.
+ // If the subtree has been requested previously it might still be in the cache.
+ const subtreeCoord = tileCoordinates.getSubtreeCoordinates();
+ let subtree = subtreeCache.find(subtreeCoord);
+
+ const that = this;
+ let subtreePromise;
+ if (defined(subtree)) {
+ subtreePromise = subtree.readyPromise;
+ } else {
+ const subtreeRelative = implicitTileset.subtreeUriTemplate.getDerivedResource(
+ {
+ templateValues: subtreeCoord.getTemplateValues(),
+ }
+ );
+ const subtreeResource = implicitTileset.baseResource.getDerivedResource({
+ url: subtreeRelative.url,
+ });
+ this._subtreeResourceLoaders.push(subtreeResource);
+ subtreePromise = subtreeResource
+ .fetchArrayBuffer()
+ .then(function (arrayBuffer) {
+ // Check one more time if the subtree is in the cache.
+ // This could happen if there are two in-flight tile requests from the same subtree and one finishes before the other.
+ subtree = subtreeCache.find(subtreeCoord);
+ if (!defined(subtree)) {
+ const bufferU8 = new Uint8Array(arrayBuffer);
+ subtree = new ImplicitSubtree(
+ subtreeResource,
+ undefined,
+ bufferU8,
+ implicitTileset,
+ subtreeCoord
+ );
+ subtreeCache.addSubtree(subtree);
+ that._subtreeLoaders.push(subtree);
+ }
+ that._subtreeResourceLoaders.splice(
+ that._subtreeResourceLoaders.indexOf(subtreeResource)
+ );
+ return subtree.readyPromise;
+ });
+ }
+
+ return subtreePromise
+ .then(function (subtree) {
+ const subtreeLoaderIndex = that._subtreeLoaders.indexOf(subtree);
+ if (!subtree.tileIsAvailableAtCoordinates(tileCoordinates)) {
+ if (subtreeLoaderIndex !== -1) {
+ that._subtreeLoaders.splice(subtreeLoaderIndex);
+ }
+ return Promise.reject("Tile is not available");
+ }
+
+ const gltfLoader = getGltfLoader(implicitTileset, tileCoordinates);
+ that._gltfLoaders.push(gltfLoader);
+
+ if (subtreeLoaderIndex !== -1) {
+ that._subtreeLoaders.splice(subtreeLoaderIndex);
+ }
+
+ return gltfLoader.promise;
+ })
+ .then(function (gltfLoader) {
+ const node = gltfLoader.components.scene.nodes[0];
+ const primitive = node.primitives[0];
+ const attributes = primitive.attributes;
+ const attributesLength = attributes.length;
+
+ const data = new Array(attributesLength);
+ for (let i = 0; i < attributesLength; i++) {
+ const attribute = attributes[i];
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const componentDatatype = MetadataComponentType.toComponentDatatype(
+ componentType
+ );
+ const componentCount = MetadataType.getComponentCount(type);
+ const totalCount = attribute.count * componentCount;
+ data[i] = ComponentDatatype.createArrayBufferView(
+ componentDatatype,
+ attribute.typedArray.buffer,
+ attribute.typedArray.byteOffset + attribute.byteOffset,
+ totalCount
+ );
+ }
+
+ that._gltfLoaders.splice(that._gltfLoaders.indexOf(gltfLoader));
+ return Promise.resolve(data);
+ });
+};
+
+/**
+ * Optional per-frame processing. Not all {@link VoxelProvder} need to do this.
+ *
+ * @param {FrameState} frameState
+ */
+Cesium3DTilesVoxelProvider.prototype.update = function (frameState) {
+ const loaders = this._gltfLoaders;
+ const loaderLength = loaders.length;
+ for (let i = 0; i < loaderLength; i++) {
+ loaders[i].process(frameState);
+ }
+};
+
+/**
+ * Check if anything is still being loaded.
+ * This is intended to be used for unit tests only.
+ * @returns {Boolean}
+ * @private
+ */
+Cesium3DTilesVoxelProvider.prototype._doneLoading = function () {
+ return (
+ this._gltfLoaders.length === 0 &&
+ this._subtreeLoaders.length === 0 &&
+ this._subtreeResourceLoaders.length === 0
+ );
+};
+
+/**
+ * @param {ArrayBuffer} gltfBuffer The buffer that comes when the promise from gltfResource.fetchArrayBuffer() resolves.
+ * @param {Resource} gltfResource Resource derived from base that points to gltf.
+ * @returns {GltfLoader}
+ */
+function getGltfLoader(implicitTileset, tileCoord) {
+ const gltfRelative = implicitTileset.contentUriTemplates[0].getDerivedResource(
+ {
+ templateValues: tileCoord.getTemplateValues(),
+ }
+ );
+ const gltfResource = implicitTileset.baseResource.getDerivedResource({
+ url: gltfRelative.url,
+ });
+
+ const gltfLoader = new GltfLoader({
+ gltfResource: gltfResource,
+ releaseGltfJson: false,
+ loadAsTypedArray: true,
+ });
+
+ gltfLoader.load();
+ return gltfLoader;
+}
+
+/**
+ * @constructor
+ * @param {ImplicitSubtree} subtree
+ * @param {Number} stamp
+ *
+ * @private
+ */
+function ImplicitSubtreeCacheNode(subtree, stamp) {
+ this.subtree = subtree;
+ this.stamp = stamp;
+}
+
+/**
+ * @constructor
+ * @param {Object} [options] Object with the following properties
+ * @param {Number} [options.maximumSubtreeCount=0] The total number of subtrees this cache can store. If adding a new subtree would exceed this limit, the lowest priority subtrees will be removed until there is room, unless the subtree that is going to be removed is the parent of the new subtree, in which case it will not be removed and the new subtree will still be added, exceeding the memory limit.
+ *
+ * @private
+ */
+function ImplicitSubtreeCache(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._maximumSubtreeCount = defaultValue(options.maximumSubtreeCount, 0);
+
+ /**
+ * A counter that goes up whenever a subtree is added. Used to sort subtrees by recency.
+ * @type {Number}
+ * @private
+ */
+ this._subtreeRequestCounter = 0;
+
+ /**
+ * @type {DoubleEndedPriorityQueue}
+ * @private
+ */
+ this._queue = new DoubleEndedPriorityQueue({
+ comparator: ImplicitSubtreeCache.comparator,
+ });
+}
+
+/**
+ * @param {ImplicitSubtree} subtree
+ */
+ImplicitSubtreeCache.prototype.addSubtree = function (subtree) {
+ const cacheNode = new ImplicitSubtreeCacheNode(
+ subtree,
+ this._subtreeRequestCounter
+ );
+ this._subtreeRequestCounter++;
+ this._queue.insert(cacheNode);
+
+ // Make sure the parent subtree exists in the cache
+ const subtreeCoord = subtree.implicitCoordinates;
+ if (subtreeCoord.level > 0) {
+ const parentCoord = subtreeCoord.deriveParentSubtreeCoordinates();
+ const parentNode = this.find(parentCoord);
+
+ //>>includeStart('debug', pragmas.debug)
+ if (parentNode === undefined) {
+ throw new DeveloperError("parent node needs to exist");
+ }
+ //>>includeEnd('debug');
+ }
+
+ if (this._maximumSubtreeCount > 0) {
+ while (this._queue.length > this._maximumSubtreeCount) {
+ const lowestPriorityNode = this._queue.getMinimum();
+ if (lowestPriorityNode === cacheNode) {
+ // Don't remove itself
+ break;
+ }
+
+ this._queue.removeMinimum();
+ }
+ }
+};
+
+/**
+ * @param {ImplicitTileCoordinates} subtreeCoord
+ * @returns {ImplicitSubtree|undefined}
+ */
+ImplicitSubtreeCache.prototype.find = function (subtreeCoord) {
+ const queue = this._queue;
+ const array = queue.internalArray;
+ const arrayLength = queue.length;
+
+ for (let i = 0; i < arrayLength; i++) {
+ const other = array[i];
+ const otherSubtree = other.subtree;
+ const otherCoord = otherSubtree.implicitCoordinates;
+ if (subtreeCoord.isEqual(otherCoord)) {
+ return other.subtree;
+ }
+ }
+ return undefined;
+};
+
+/**
+ * @param {ImplicitSubtreeCacheNode} a
+ * @param {ImplicitSubtreeCacheNode} b
+ * @returns {Number}
+ *
+ * @private
+ */
+ImplicitSubtreeCache.comparator = function (a, b) {
+ const aCoord = a.subtree.implicitCoordinates;
+ const bCoord = b.subtree.implicitCoordinates;
+ if (aCoord.isAncestorOf(bCoord)) {
+ // Technically this shouldn't happen because the ancestor subtree was supposed to be added to the cache first.
+ return +1.0;
+ } else if (bCoord.isAncestorOf(aCoord)) {
+ return -1.0;
+ }
+ return a.stamp - b.stamp;
+};
+
+export default Cesium3DTilesVoxelProvider;
diff --git a/Source/Scene/DerivedCommand.js b/Source/Scene/DerivedCommand.js
index 8d0e058ff89..d5a30d837e8 100644
--- a/Source/Scene/DerivedCommand.js
+++ b/Source/Scene/DerivedCommand.js
@@ -148,6 +148,13 @@ const vertexlogDepthRegex = /\s+czm_vertexLogDepth\(/;
const extensionRegex = /\s*#extension\s+GL_EXT_frag_depth\s*:\s*enable/;
function getLogDepthShaderProgram(context, shaderProgram) {
+ const disableLogDepthWrite =
+ shaderProgram.fragmentShaderSource.defines.indexOf("LOG_DEPTH_READ_ONLY") >=
+ 0;
+ if (disableLogDepthWrite) {
+ return shaderProgram;
+ }
+
let shader = context.shaderCache.getDerivedShaderProgram(
shaderProgram,
"logDepth"
diff --git a/Source/Scene/GltfLoader.js b/Source/Scene/GltfLoader.js
index 5696e6b3352..28c81eabc96 100644
--- a/Source/Scene/GltfLoader.js
+++ b/Source/Scene/GltfLoader.js
@@ -985,6 +985,21 @@ function loadPrimitive(
);
const draco = extensions.KHR_draco_mesh_compression;
+ const extVox = extensions.EXT_primitive_voxels;
+ if (defined(extVox)) {
+ const voxel = new ModelComponents.Voxel();
+ voxel.dimensions = fromArray(Cartesian3, extVox.dimensions);
+ if (defined(extVox.bounds)) {
+ voxel.minBounds = fromArray(Cartesian3, extVox.bounds.min);
+ voxel.maxBounds = fromArray(Cartesian3, extVox.bounds.max);
+ }
+ if (defined(extVox.padding)) {
+ voxel.paddingBefore = fromArray(Cartesian3, extVox.padding.before);
+ voxel.paddingAfter = fromArray(Cartesian3, extVox.padding.after);
+ }
+ primitive.voxel = voxel;
+ }
+
const attributes = gltfPrimitive.attributes;
if (defined(attributes)) {
for (const semantic in attributes) {
diff --git a/Source/Scene/GltfVoxelProvider.js b/Source/Scene/GltfVoxelProvider.js
new file mode 100644
index 00000000000..a795aae74c4
--- /dev/null
+++ b/Source/Scene/GltfVoxelProvider.js
@@ -0,0 +1,404 @@
+import Cartesian3 from "../Core/Cartesian3.js";
+import Check from "../Core/Check.js";
+import ComponentDatatype from "../Core/ComponentDatatype.js";
+import DeveloperError from "../Core/DeveloperError.js";
+import defaultValue from "../Core/defaultValue.js";
+import defer from "../Core/defer.js";
+import defined from "../Core/defined.js";
+import Resource from "../Core/Resource.js";
+import GltfLoader from "./GltfLoader.js";
+import MetadataComponentType from "./MetadataComponentType.js";
+import MetadataType from "./MetadataType.js";
+import ModelExperimentalUtility from "./ModelExperimental/ModelExperimentalUtility.js";
+import VoxelShapeType from "./VoxelShapeType.js";
+
+/**
+ * A {@link VoxelProvider} that fetches voxel data from a glTF.
+ *
+ * @alias GltfVoxelProvider
+ * @constructor
+ *
+ * @param {Object} options Object with the following properties:
+ * @param {String|Resource|Uint8Array|Object|GltfLoader} options.gltf A Resource/URL to a glTF/glb file, a binary glTF buffer, or a JSON object containing the glTF contents
+ *
+ * @see VoxelProvider
+ * @see Cesium3DTilesVoxelProvider
+ * @see VoxelPrimitive
+ */
+function GltfVoxelProvider(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("options.gltf", options.gltf);
+ //>>includeEnd('debug');
+
+ /**
+ * Gets a value indicating whether or not the provider is ready for use.
+ * @type {Boolean}
+ * @readonly
+ */
+ this.ready = false;
+
+ this._readyPromise = defer();
+
+ /**
+ * Gets the promise that will be resolved when the provider is ready for use.
+ * @type {Promise.}
+ * @readonly
+ */
+ this.readyPromise = this._readyPromise.promise;
+
+ /**
+ * An optional model matrix that is applied to all tiles
+ * @type {Matrix4}
+ * @readonly
+ */
+ this.modelMatrix = undefined;
+
+ /**
+ * Gets the {@link VoxelShapeType}
+ * @type {VoxelShapeType}
+ * @readonly
+ */
+ this.shape = undefined;
+
+ /**
+ * Gets the minimum bounds.
+ * If undefined, the shape's default minimum bounds will be used instead.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ this.minBounds = undefined;
+
+ /**
+ * Gets the maximum bounds.
+ * If undefined, the shape's default maximum bounds will be used instead.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ this.maxBounds = undefined;
+
+ /**
+ * Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ this.dimensions = undefined;
+
+ /**
+ * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
+ * TODO: mark this optional
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Number}
+ * @readonly
+ */
+ this.paddingBefore = undefined;
+
+ /**
+ * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * TODO: mark this optional
+ * @type {Number}
+ * @readonly
+ */
+ this.paddingAfter = undefined;
+
+ // TODO is there a good user-facing way to set names, types, componentTypes, min, max, etc? MetadataComponents.Primitive is close, but private and has some fields that voxels don't use
+
+ /**
+ * Gets stuff
+ * @type {String[]}
+ */
+ this.names = new Array();
+
+ /**
+ * Gets stuff
+ * @type {MetadataType[]}
+ */
+ this.types = new Array();
+
+ /**
+ * Gets stuff
+ * @type {MetadataComponentType[]}
+ */
+ this.componentTypes = new Array();
+
+ /**
+ * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
+ * Gets the minimum value
+ * @type {Number[]}
+ */
+ this.minimumValues = undefined;
+
+ /**
+ * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
+ * Gets the maximum value
+ * @type {Number[][]}
+ */
+ this.maximumValues = undefined;
+
+ /**
+ * The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be set to 0.
+ * @type {Number}
+ */
+ this.maximumTileCount = undefined;
+
+ /**
+ * A {@link GltfLoader} that is processed each frame until ready.
+ * @type {GltfLoader}
+ * @private
+ */
+ this._loader = undefined;
+
+ const gltf = options.gltf;
+ let promise;
+ if (defined(gltf.components) && defined(gltf.components.asset)) {
+ promise = gltf.promise;
+ } else {
+ const gltfResource = Resource.createIfNeeded(gltf);
+ const loader = new GltfLoader({
+ gltfResource: gltfResource,
+ releaseGltfJson: true,
+ loadAsTypedArray: true,
+ });
+ loader.load();
+ promise = loader.promise;
+ this._loader = loader;
+ }
+
+ const that = this;
+ promise
+ .then(function (loader) {
+ const gltf = loader.components;
+ const node = gltf.nodes[0];
+ const modelMatrix = ModelExperimentalUtility.getNodeTransform(node);
+ const gltfPrimitive = node.primitives[0];
+ const voxel = gltfPrimitive.voxel;
+ const primitiveType = gltfPrimitive.primitiveType;
+ const shape = VoxelShapeType.fromPrimitiveType(primitiveType);
+ const attributes = gltfPrimitive.attributes;
+ const attributesLength = attributes.length;
+ const names = new Array(attributesLength);
+ const types = new Array(attributesLength);
+ const componentTypes = new Array(attributesLength);
+ const minimumValues = undefined; //new Array(length);
+ const maximumValues = undefined; //new Array(length);
+
+ for (let i = 0; i < attributesLength; i++) {
+ const attribute = attributes[i];
+ const name = attribute.name;
+ const type = attribute.type;
+ const componentDatatype = attribute.componentDatatype;
+ let nameFixed = name.toLowerCase();
+ if (nameFixed.charAt(0) === "_") {
+ nameFixed = nameFixed.slice(1);
+ }
+
+ names[i] = nameFixed;
+ types[i] = type;
+ componentTypes[i] = MetadataComponentType.fromComponentDatatype(
+ componentDatatype
+ );
+ }
+
+ that.ready = true;
+ that._readyPromise = Promise.resolve(that);
+
+ /**
+ * An optional model matrix that is applied to all tiles
+ * @type {Matrix4}
+ * @readonly
+ */
+ that.modelMatrix = modelMatrix;
+
+ /**
+ * Gets the {@link VoxelShapeType}
+ * @type {VoxelShapeType}
+ * @readonly
+ */
+ that.shape = shape;
+
+ /**
+ * Gets the minimum bounds.
+ * This should not be called before {@link GltfVoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ that.minBounds = defined(voxel.minBounds)
+ ? Cartesian3.clone(voxel.minBounds, new Cartesian3())
+ : undefined;
+
+ /**
+ * Gets the maximum bounds.
+ * This should not be called before {@link GltfVoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ that.maxBounds = defined(voxel.maxBounds)
+ ? Cartesian3.clone(voxel.maxBounds, new Cartesian3())
+ : undefined;
+
+ /**
+ * Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
+ * This should not be called before {@link GltfVoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ that.dimensions = Cartesian3.clone(voxel.dimensions, new Cartesian3());
+
+ /**
+ * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
+ * TODO: mark this optional
+ * This should not be called before {@link GltfVoxelProvider#ready} returns true.
+ * @type {Number}
+ * @readonly
+ */
+ that.paddingBefore = defined(voxel.paddingBefore)
+ ? Cartesian3.clone(voxel.paddingBefore, new Cartesian3())
+ : undefined;
+
+ /**
+ * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
+ * This should not be called before {@link GltfVoxelProvider#ready} returns true.
+ * TODO: mark this optional
+ * @type {Number}
+ * @readonly
+ */
+ that.paddingAfter = defined(voxel.paddingAfter)
+ ? Cartesian3.clone(voxel.paddingAfter, new Cartesian3())
+ : undefined;
+
+ // TODO is there a good user-facing way to set names, types, componentTypes, min, max, etc? MetadataComponents.Primitive is close, but private and has some fields that voxels don't use
+
+ /**
+ * Gets stuff
+ * @type {String[]}
+ */
+ that.names = names;
+
+ /**
+ * Gets stuff
+ * @type {MetadataType[]}
+ */
+ that.types = types;
+
+ /**
+ * Gets stuff
+ * @type {MetadataComponentType[]}
+ */
+ that.componentTypes = componentTypes;
+
+ /**
+ * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
+ * Gets the minimum value
+ * @type {Number[]}
+ */
+ that.minimumValues = minimumValues;
+
+ /**
+ * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
+ * Gets the maximum value
+ * @type {Number[][]}
+ */
+ that.maximumValues = maximumValues;
+
+ /**
+ * The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be set to 0.
+ * @type {Number}
+ */
+ that.maximumTileCount = 1;
+
+ /**
+ * @private
+ * @type {Float32Array|Uint16Array|Uint8Array}
+ */
+ that._data = new Array(attributesLength);
+ for (let i = 0; i < attributesLength; i++) {
+ const attribute = attributes[i];
+ const typedArray = attribute.typedArray;
+ const type = attribute.type;
+ const componentDatatype = attribute.componentDatatype;
+ const componentCount = MetadataType.getComponentCount(type);
+ const totalCount = attribute.count * componentCount;
+
+ that._data[i] = ComponentDatatype.createArrayBufferView(
+ componentDatatype,
+ typedArray.buffer,
+ typedArray.byteOffset + attribute.byteOffset,
+ totalCount
+ );
+ }
+
+ // Now that the data is loaded there's no need to keep around the loader.
+ that._loader = undefined;
+ })
+ .catch(function (error) {
+ that.readyPromise = Promise.reject(error);
+ });
+}
+
+/**
+ * A hook to update the provider every frame, called from {@link VoxelPrimitive.update}.
+ * If the provider doesn't need this functionality it should leave this function undefined.
+ * @function
+ * @param {FrameState} frameState
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+GltfVoxelProvider.prototype.update = function (frameState) {
+ const loader = this._loader;
+ if (defined(loader)) {
+ loader.process(frameState);
+ }
+};
+
+/**
+ * Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z.
+ * Note that there is only one "tile" for a glTF so the only valid input is the "root" tile coordinates.
+ * This function should not be called before {@link GltfVoxelProvider#ready} returns true.
+ * @function
+ *
+ * @param {Object} [options] Object with the following properties:
+ * @param {Number} [options.tileLevel=0] The tile's level.
+ * @param {Number} [options.tileX=0] The tile's X coordinate.
+ * @param {Number} [options.tileY=0] The tile's Y coordinate.
+ * @param {Number} [options.tileZ=0] The tile's Z coordinate.
+ *
+ * @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
+ */
+GltfVoxelProvider.prototype.requestData = function (options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ const tileX = defaultValue(options.tileX, 0);
+ const tileY = defaultValue(options.tileY, 0);
+ const tileZ = defaultValue(options.tileZ, 0);
+ const tileLevel = defaultValue(options.tileLevel, 0);
+
+ //>>includeStart('debug', pragmas.debug);
+ if (!this.ready) {
+ throw new DeveloperError(
+ "requestData must not be called before the provider is ready."
+ );
+ }
+ if (!(tileLevel === 0 && tileX === 0 && tileY === 0 && tileZ === 0)) {
+ throw new DeveloperError("GltfVoxelProvider can only have one tile");
+ }
+ //>>includeEnd('debug');
+
+ return Promise.resolve(this._data);
+};
+
+/**
+ * Check if the data is still being loaded.
+ * This is intended to be used for unit tests only.
+ * @returns {Boolean}
+ * @private
+ */
+GltfVoxelProvider.prototype._doneLoading = function () {
+ return !defined(this._loader);
+};
+
+export default GltfVoxelProvider;
diff --git a/Source/Scene/MetadataComponentType.js b/Source/Scene/MetadataComponentType.js
index f14f1ca69a0..cd0d2fc35b5 100644
--- a/Source/Scene/MetadataComponentType.js
+++ b/Source/Scene/MetadataComponentType.js
@@ -1,5 +1,6 @@
import CesiumMath from "../Core/Math.js";
import Check from "../Core/Check.js";
+import ComponentDatatype from "../Core/ComponentDatatype.js";
import DeveloperError from "../Core/DeveloperError.js";
import FeatureDetection from "../Core/FeatureDetection.js";
@@ -445,4 +446,85 @@ MetadataComponentType.getSizeInBytes = function (type) {
}
};
+/**
+ * Gets the type from a {@link ComponentDatatype}.
+ *
+ * @param {ComponentDatatype} componentDatatype The component datatype.
+ * @returns {MetadataComponentType} The metadata component type.
+ *
+ * @private
+ */
+MetadataComponentType.fromComponentDatatype = function (componentDatatype) {
+ switch (componentDatatype) {
+ case ComponentDatatype.BYTE:
+ return MetadataComponentType.INT8;
+ case ComponentDatatype.UNSIGNED_BYTE:
+ return MetadataComponentType.UINT8;
+ case ComponentDatatype.SHORT:
+ return MetadataComponentType.INT16;
+ case ComponentDatatype.UNSIGNED_SHORT:
+ return MetadataComponentType.UINT16;
+ case ComponentDatatype.INT:
+ return MetadataComponentType.INT32;
+ case ComponentDatatype.UNSIGNED_INT:
+ return MetadataComponentType.UINT32;
+ case ComponentDatatype.FLOAT:
+ return MetadataComponentType.FLOAT32;
+ case ComponentDatatype.DOUBLE:
+ return MetadataComponentType.FLOAT64;
+ }
+};
+
+MetadataComponentType.toComponentDatatype = function (type) {
+ //>>includeStart('debug', pragmas.debug);
+ if (!MetadataComponentType.isNumericType(type)) {
+ throw new DeveloperError("type must be a numeric type");
+ }
+ //>>includeEnd('debug');
+ switch (type) {
+ case MetadataComponentType.INT8:
+ return ComponentDatatype.BYTE;
+ case MetadataComponentType.UINT8:
+ return ComponentDatatype.UNSIGNED_BYTE;
+ case MetadataComponentType.INT16:
+ return ComponentDatatype.SHORT;
+ case MetadataComponentType.UINT16:
+ return ComponentDatatype.UNSIGNED_SHORT;
+ case MetadataComponentType.INT32:
+ return ComponentDatatype.INT;
+ case MetadataComponentType.UINT32:
+ return ComponentDatatype.UNSIGNED_INT;
+ case MetadataComponentType.FLOAT32:
+ return ComponentDatatype.FLOAT;
+ case MetadataComponentType.FLOAT64:
+ return ComponentDatatype.DOUBLE;
+ }
+};
+
+MetadataComponentType.toTypedArrayType = function (type) {
+ //>>includeStart('debug', pragmas.debug);
+ if (!MetadataComponentType.isNumericType(type)) {
+ throw new DeveloperError("type must be a numeric type");
+ }
+ //>>includeEnd('debug');
+ switch (type) {
+ case MetadataComponentType.INT8:
+ return Int8Array;
+ case MetadataComponentType.UINT8:
+ return Uint8Array;
+ case MetadataComponentType.INT16:
+ return Int16Array;
+ case MetadataComponentType.UINT16:
+ return Uint16Array;
+ case MetadataComponentType.INT32:
+ return Int32Array;
+ case MetadataComponentType.UINT32:
+ return Uint32Array;
+ case MetadataComponentType.FLOAT32:
+ return Float32Array;
+ case MetadataComponentType.FLOAT64:
+ return Float64Array;
+ }
+};
+
export default Object.freeze(MetadataComponentType);
diff --git a/Source/Scene/ModelComponents.js b/Source/Scene/ModelComponents.js
index 1b52a8e92b9..2ce5fb6eba7 100644
--- a/Source/Scene/ModelComponents.js
+++ b/Source/Scene/ModelComponents.js
@@ -542,6 +542,46 @@ function MorphTarget() {
this.attributes = [];
}
+/**
+ * Properties from EXT_primitive_voxels
+ *
+ * @alias ModelComponents.Voxel
+ * @constructor
+ *
+ * @private
+ */
+function Voxel() {
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
+ this.minBounds = undefined;
+
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
+ this.maxBounds = undefined;
+
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
+ this.dimensions = undefined;
+
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
+ this.paddingBefore = undefined;
+
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
+ this.paddingAfter = undefined;
+}
+
/**
* Geometry to be rendered with a material.
*
@@ -625,6 +665,13 @@ function Primitive() {
* @private
*/
this.propertyAttributeIds = [];
+
+ /**
+ * Properties from EXT_primitive_voxels.
+ * @type {ModelComponents.Voxel}
+ * @private
+ */
+ this.voxel = undefined;
}
/**
@@ -1163,6 +1210,7 @@ function Material() {
Material.DEFAULT_EMISSIVE_FACTOR = Cartesian3.ZERO;
ModelComponents.Quantization = Quantization;
+ModelComponents.Voxel = Voxel;
ModelComponents.Attribute = Attribute;
ModelComponents.Indices = Indices;
ModelComponents.FeatureIdAttribute = FeatureIdAttribute;
diff --git a/Source/Scene/Scene.js b/Source/Scene/Scene.js
index 70d57cb90d8..0bb323707bc 100644
--- a/Source/Scene/Scene.js
+++ b/Source/Scene/Scene.js
@@ -2287,6 +2287,17 @@ function executeTranslucentCommandsFrontToBack(
}
}
+function executeVoxelCommands(scene, executeFunction, passState, commands) {
+ const context = scene.context;
+
+ mergeSort(commands, backToFront, scene.camera.positionWC);
+
+ const length = commands.length;
+ for (let i = 0; i < length; ++i) {
+ executeFunction(commands[i], scene, context, passState);
+ }
+}
+
const scratchPerspectiveFrustum = new PerspectiveFrustum();
const scratchPerspectiveOffCenterFrustum = new PerspectiveOffCenterFrustum();
const scratchOrthographicFrustum = new OrthographicFrustum();
@@ -2695,6 +2706,12 @@ function executeCommands(scene, passState) {
pickDepth.executeCopyDepth(context, passState);
}
+ us.updatePass(Pass.VOXELS);
+ commands = frustumCommands.commands[Pass.VOXELS];
+ length = frustumCommands.indices[Pass.VOXELS];
+ commands.length = length;
+ executeVoxelCommands(scene, executeCommand, passState, commands);
+
if (picking || !usePostProcessSelected) {
continue;
}
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
new file mode 100644
index 00000000000..a66e5cf3073
--- /dev/null
+++ b/Source/Scene/VoxelBoxShape.js
@@ -0,0 +1,325 @@
+import BoundingSphere from "../Core/BoundingSphere.js";
+import Cartesian3 from "../Core/Cartesian3.js";
+import CesiumMath from "../Core/Math.js";
+import Check from "../Core/Check.js";
+import Matrix3 from "../Core/Matrix3.js";
+import Matrix4 from "../Core/Matrix4.js";
+import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
+
+/**
+ * A box {@link VoxelShape}.
+ *
+ * @alias VoxelBoxShape
+ * @constructor
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @see VoxelShape
+ * @see VoxelEllipsoidShape
+ * @see VoxelCylinderShape
+ * @see VoxelShapeType
+ */
+function VoxelBoxShape() {
+ /**
+ * An oriented bounding box containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {OrientedBoundingBox}
+ */
+ this.orientedBoundingBox = new OrientedBoundingBox();
+
+ /**
+ * A bounding sphere containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {BoundingSphere}
+ */
+ this.boundingSphere = new BoundingSphere();
+
+ /**
+ * A transformation matrix containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ */
+ this.boundTransform = new Matrix4();
+
+ /**
+ * A transformation matrix containing the shape, ignoring the bounds.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ */
+ this.shapeTransform = new Matrix4();
+
+ /**
+ * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
+ * The update function must be called before accessing this value.
+ * @type {Boolean}
+ */
+ this.isVisible = false;
+
+ this._minBounds = Cartesian3.clone(
+ VoxelBoxShape.DefaultMinBounds,
+ new Cartesian3()
+ );
+ this._maxBounds = Cartesian3.clone(
+ VoxelBoxShape.DefaultMaxBounds,
+ new Cartesian3()
+ );
+}
+
+const scratchTranslation = new Cartesian3();
+const scratchScale = new Cartesian3();
+const scratchRotation = new Matrix3();
+
+/**
+ * @param {Cartesian3} minimumX
+ * @param {Cartesian3} maximumX
+ * @param {Cartesian3} minimumY
+ * @param {Cartesian3} maximumY
+ * @param {Cartesian3} minimumZ
+ * @param {Cartesian3} maximumZ
+ * @param {Matrix4} matrix
+ * @param {OrientedBoundingBox} result
+ * @returns {OrientedBoundingBox}
+ */
+function getBoxChunkObb(
+ minimumX,
+ maximumX,
+ minimumY,
+ maximumY,
+ minimumZ,
+ maximumZ,
+ matrix,
+ result
+) {
+ const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelBoxShape.DefaultMaxBounds;
+
+ const isDefaultBounds =
+ minimumX === defaultMinBounds.x &&
+ minimumY === defaultMinBounds.y &&
+ minimumZ === defaultMinBounds.z &&
+ maximumX === defaultMaxBounds.x &&
+ maximumY === defaultMaxBounds.y &&
+ maximumZ === defaultMaxBounds.z;
+
+ if (isDefaultBounds) {
+ result.center = Matrix4.getTranslation(matrix, result.center);
+ result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
+ } else {
+ let scale = Matrix4.getScale(matrix, scratchScale);
+ const translation = Matrix4.getTranslation(matrix, scratchTranslation);
+ result.center = Cartesian3.fromElements(
+ translation.x + scale.x * 0.5 * (minimumX + maximumX),
+ translation.y + scale.y * 0.5 * (maximumY + minimumY),
+ translation.z + scale.z * 0.5 * (maximumZ + minimumZ),
+ result.center
+ );
+
+ scale = Cartesian3.fromElements(
+ scale.x * 0.5 * (maximumX - minimumX),
+ scale.y * 0.5 * (maximumY - minimumY),
+ scale.z * 0.5 * (maximumZ - minimumZ),
+ scratchScale
+ );
+ const rotation = Matrix4.getRotation(matrix, scratchRotation);
+ result.halfAxes = Matrix3.setScale(rotation, scale, result.halfAxes);
+ }
+
+ return result;
+}
+
+/**
+ * Update the shape's state.
+ * @function
+ * @param {Matrix4} modelMatrix The model matrix.
+ * @param {Cartesian3} minBounds The minimum bounds.
+ * @param {Cartesian3} maxBounds The maximum bounds.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("modelMatrix", modelMatrix);
+ Check.typeOf.object("minBounds", minBounds);
+ Check.typeOf.object("maxBounds", maxBounds);
+ //>>includeEnd('debug');
+
+ // Don't render if any of the min bounds exceed the max bounds.
+ // Don't render if two of the min bounds equal the max bounds because it would be an invisible line.
+ // Don't render if all of the min bounds equal the max bounds because it would be an invisible point.
+ // Still render if one of the min bounds is equal to the max bounds because it will be a square/rectangle.
+ if (
+ minBounds.x > maxBounds.x ||
+ minBounds.y > maxBounds.y ||
+ minBounds.z > maxBounds.z ||
+ (minBounds.x === maxBounds.x) +
+ (minBounds.y === maxBounds.y) +
+ (minBounds.z === maxBounds.z) >=
+ 2
+ ) {
+ this.isVisible = false;
+ return;
+ }
+
+ // If two or more of the scales are 0 the shape will not render.
+ // If one of the scales is 0 it will still be visible but will appear as a square/rectangle.
+ const scale = Matrix4.getScale(modelMatrix, scratchScale);
+ if ((scale.x === 0.0) + (scale.y === 0.0) + (scale.z === 0.0) >= 2) {
+ this.isVisible = false;
+ return;
+ }
+
+ this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
+ this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
+
+ this.orientedBoundingBox = getBoxChunkObb(
+ minBounds.x,
+ maxBounds.x,
+ minBounds.y,
+ maxBounds.y,
+ minBounds.z,
+ maxBounds.z,
+ modelMatrix,
+ this.orientedBoundingBox
+ );
+
+ this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
+
+ // All of the box bounds go from -1 to +1, so the model matrix scale can be
+ // used as the oriented bounding box half axes.
+ this.boundTransform = Matrix4.fromRotationTranslation(
+ this.orientedBoundingBox.halfAxes,
+ this.orientedBoundingBox.center,
+ this.boundTransform
+ );
+
+ this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
+ this.orientedBoundingBox,
+ this.boundingSphere
+ );
+
+ this.isVisible = true;
+};
+
+/**
+ * Computes an oriented bounding box for a specified tile.
+ * The update function must be called before calling this function.
+ * @function
+ * @param {Number} tileLevel The tile's level.
+ * @param {Number} tileX The tile's x coordinate.
+ * @param {Number} tileY The tile's y coordinate.
+ * @param {Number} tileZ The tile's z coordinate.
+ * @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
+ * @returns {OrientedBoundingBox} The oriented bounding box.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ result
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number("tileLevel", tileLevel);
+ Check.typeOf.number("tileX", tileX);
+ Check.typeOf.number("tileY", tileY);
+ Check.typeOf.number("tileZ", tileZ);
+ Check.typeOf.object("result", result);
+ //>>includeEnd('debug');
+
+ const rootTransform = this.shapeTransform;
+ const sizeAtLevel = 1.0 / Math.pow(2, tileLevel);
+
+ const minBounds = this._minBounds;
+ const maxBounds = this._maxBounds;
+
+ const minimumX = CesiumMath.lerp(
+ minBounds.x,
+ maxBounds.x,
+ sizeAtLevel * tileX
+ );
+ const minimumY = CesiumMath.lerp(
+ minBounds.y,
+ maxBounds.y,
+ sizeAtLevel * tileY
+ );
+ const minimumZ = CesiumMath.lerp(
+ minBounds.z,
+ maxBounds.z,
+ sizeAtLevel * tileZ
+ );
+ const maximumX = CesiumMath.lerp(
+ minBounds.x,
+ maxBounds.x,
+ sizeAtLevel * (tileX + 1)
+ );
+ const maximumY = CesiumMath.lerp(
+ minBounds.y,
+ maxBounds.y,
+ sizeAtLevel * (tileY + 1)
+ );
+ const maximumZ = CesiumMath.lerp(
+ minBounds.z,
+ maxBounds.z,
+ sizeAtLevel * (tileZ + 1)
+ );
+
+ return getBoxChunkObb(
+ minimumX,
+ maximumX,
+ minimumY,
+ maximumY,
+ minimumZ,
+ maximumZ,
+ rootTransform,
+ result
+ );
+};
+
+/**
+ * Computes an approximate step size for raymarching the root tile of a voxel grid.
+ * The update function must be called before calling this function.
+ * @function
+ * @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
+ * @returns {Number} The step size.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelBoxShape.prototype.computeApproximateStepSize = function (dimensions) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("dimensions", dimensions);
+ //>>includeEnd('debug');
+
+ return 1.0 / Cartesian3.maximumComponent(dimensions);
+};
+
+/**
+ * Defines the minimum bounds of the shape. Corresponds to minimum X, Y, Z.
+ * @type {Cartesian3}
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelBoxShape.DefaultMinBounds = Cartesian3.negate(
+ Cartesian3.ONE,
+ new Cartesian3()
+);
+
+/**
+ * Defines the maximum bounds of the shape. Corresponds to maximum X, Y, Z.
+ * @type {Cartesian3}
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelBoxShape.DefaultMaxBounds = Cartesian3.clone(
+ Cartesian3.ONE,
+ new Cartesian3()
+);
+
+export default VoxelBoxShape;
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
new file mode 100644
index 00000000000..e379a8b0b11
--- /dev/null
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -0,0 +1,529 @@
+import BoundingSphere from "../Core/BoundingSphere.js";
+import Cartesian3 from "../Core/Cartesian3.js";
+import Check from "../Core/Check.js";
+import CesiumMath from "../Core/Math.js";
+import Matrix3 from "../Core/Matrix3.js";
+import Matrix4 from "../Core/Matrix4.js";
+import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
+
+/**
+ * A cylinder {@link VoxelShape}.
+ *
+ * @alias VoxelCylinderShape
+ * @constructor
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @see VoxelShape
+ * @see VoxelCylinderShape
+ * @see VoxelEllipsoidShape
+ * @see VoxelShapeType
+ */
+function VoxelCylinderShape() {
+ /**
+ * An oriented bounding box containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {OrientedBoundingBox}
+ */
+ this.orientedBoundingBox = new OrientedBoundingBox();
+
+ /**
+ * A bounding sphere containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {BoundingSphere}
+ */
+ this.boundingSphere = new BoundingSphere();
+
+ /**
+ * A transformation matrix containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ */
+ this.boundTransform = new Matrix4();
+
+ /**
+ * A transformation matrix containing the shape, ignoring the bounds.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ */
+ this.shapeTransform = new Matrix4();
+
+ /**
+ * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
+ * The update function must be called before accessing this value.
+ * @type {Boolean}
+ */
+ this.isVisible = false;
+
+ this._minimumRadius = VoxelCylinderShape.DefaultMinBounds.x;
+ this._maximumRadius = VoxelCylinderShape.DefaultMaxBounds.x;
+ this._minimumHeight = VoxelCylinderShape.DefaultMinBounds.y;
+ this._maximumHeight = VoxelCylinderShape.DefaultMaxBounds.y;
+ this._minimumAngle = VoxelCylinderShape.DefaultMinBounds.z;
+ this._maximumAngle = VoxelCylinderShape.DefaultMaxBounds.z;
+}
+
+const scratchTestAngles = new Array(6);
+
+// Preallocated arrays for all of the possible test angle counts
+const scratchPositions = [
+ new Array(),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+];
+
+/**
+ * @function
+ * @param {Number} radiusStart
+ * @param {Number} radiusEnd
+ * @param {Number} heightStart
+ * @param {Number} heightEnd
+ * @param {Number} angleStart
+ * @param {Number} angleEnd
+ * @param {Matrix4} matrix
+ * @param {OrientedBoundingBox} result
+ * @returns {OrientedBoundingBox}
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+function getCylinderChunkObb(
+ radiusStart,
+ radiusEnd,
+ heightStart,
+ heightEnd,
+ angleStart,
+ angleEnd,
+ matrix,
+ result
+) {
+ const defaultMinBounds = VoxelCylinderShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds;
+ const defaultMinRadius = defaultMinBounds.x; // 0
+ const defaultMaxRadius = defaultMaxBounds.x; // 1
+ const defaultMinHeight = defaultMinBounds.y; // -1
+ const defaultMaxHeight = defaultMaxBounds.y; // +1
+ const defaultMinAngle = defaultMinBounds.z; // -pi/2
+ const defaultMaxAngle = defaultMaxBounds.z; // +pi/2
+
+ // Return early if using the default bounds
+ if (
+ radiusStart === defaultMinRadius &&
+ radiusEnd === defaultMaxRadius &&
+ heightStart === defaultMinHeight &&
+ heightEnd === defaultMaxHeight &&
+ angleStart === defaultMinAngle &&
+ angleEnd === defaultMaxAngle
+ ) {
+ result.center = Matrix4.getTranslation(matrix, result.center);
+ result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
+ return result;
+ }
+
+ let testAngleCount = 0;
+ const testAngles = scratchTestAngles;
+ const halfPi = CesiumMath.PI_OVER_TWO;
+
+ testAngles[testAngleCount++] = angleStart;
+ testAngles[testAngleCount++] = angleEnd;
+
+ if (angleStart > angleEnd) {
+ if (angleStart > 0.0 && angleEnd > 0.0) {
+ testAngles[testAngleCount++] = 0.0;
+ }
+ if (angleStart > +halfPi && angleEnd > +halfPi) {
+ testAngles[testAngleCount++] = +halfPi;
+ }
+ if (angleStart > -halfPi && angleEnd > -halfPi) {
+ testAngles[testAngleCount++] = -halfPi;
+ }
+ // It will always cross the 180th meridian
+ testAngles[testAngleCount++] = CesiumMath.PI;
+ } else {
+ if (angleStart < 0.0 && angleEnd > 0.0) {
+ testAngles[testAngleCount++] = 0.0;
+ }
+ if (angleStart < +halfPi && angleEnd > +halfPi) {
+ testAngles[testAngleCount++] = +halfPi;
+ }
+ if (angleStart < -halfPi && angleEnd > -halfPi) {
+ testAngles[testAngleCount++] = -halfPi;
+ }
+ }
+
+ const positions = scratchPositions[testAngleCount];
+
+ for (let i = 0; i < testAngleCount; i++) {
+ const angle = testAngles[i];
+ const cosAngle = Math.cos(angle);
+ const sinAngle = Math.sin(angle);
+
+ positions[i * 4 + 0] = Cartesian3.fromElements(
+ cosAngle * radiusStart,
+ sinAngle * radiusStart,
+ heightStart,
+ positions[i * 4 + 0]
+ );
+ positions[i * 4 + 1] = Cartesian3.fromElements(
+ cosAngle * radiusEnd,
+ sinAngle * radiusEnd,
+ heightStart,
+ positions[i * 4 + 1]
+ );
+ positions[i * 4 + 2] = Cartesian3.fromElements(
+ cosAngle * radiusStart,
+ sinAngle * radiusStart,
+ heightEnd,
+ positions[i * 4 + 2]
+ );
+ positions[i * 4 + 3] = Cartesian3.fromElements(
+ cosAngle * radiusEnd,
+ sinAngle * radiusEnd,
+ heightEnd,
+ positions[i * 4 + 3]
+ );
+ }
+
+ for (let i = 0; i < testAngleCount * 4; i++) {
+ positions[i] = Matrix4.multiplyByPoint(matrix, positions[i], positions[i]);
+ }
+
+ return OrientedBoundingBox.fromPoints(positions, result);
+}
+
+const scratchScale = new Cartesian3();
+
+/**
+ * Update the shape's state.
+ * @function
+ * @param {Matrix4} modelMatrix The model matrix.
+ * @param {Cartesian3} minBounds The minimum bounds.
+ * @param {Cartesian3} maxBounds The maximum bounds.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelCylinderShape.prototype.update = function (
+ modelMatrix,
+ minBounds,
+ maxBounds
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("modelMatrix", modelMatrix);
+ Check.typeOf.object("minBounds", minBounds);
+ Check.typeOf.object("maxBounds", maxBounds);
+ //>>includeEnd('debug');
+
+ // Don't let the scale be 0, it will screw up a bunch of math
+ const scaleEps = CesiumMath.EPSILON8;
+ const scale = Matrix4.getScale(modelMatrix, scratchScale);
+ if (Math.abs(scale.x) < scaleEps) {
+ scale.x = CesiumMath.signNotZero(scale.x) * scaleEps;
+ }
+ if (Math.abs(scale.y) < scaleEps) {
+ scale.y = CesiumMath.signNotZero(scale.y) * scaleEps;
+ }
+ if (Math.abs(scale.z) < scaleEps) {
+ scale.z = CesiumMath.signNotZero(scale.z) * scaleEps;
+ }
+
+ // // If two or more of the scales are 0 the shape will not render.
+ // // If the X scale or Y scale is 0 the shape will appear as a square/rectangle.
+ // // If the Z scale is 0 the shape will appear as an circle/ellipse.
+ // const xIsZero = scale.x === 0.0;
+ // const yIsZero = scale.y === 0.0;
+ // const zIsZero = scale.z === 0.0;
+ // if (xIsZero + yIsZero + zIsZero >= 2) {
+ // this.isVisible = false;
+ // return;
+ // }
+
+ this._minimumRadius = minBounds.x; // [0,1]
+ this._maximumRadius = maxBounds.x; // [0,1]
+ this._minimumHeight = minBounds.y; // [-1,+1]
+ this._maximumHeight = maxBounds.y; // [-1,+1]
+ this._minimumAngle = CesiumMath.negativePiToPi(minBounds.z); // [-halfPi,+halfPi]
+ this._maximumAngle = CesiumMath.negativePiToPi(maxBounds.z); // [-halfPi,+halfPi]
+
+ const minRadius = this._minimumRadius;
+ const maxRadius = this._maximumRadius;
+ const minHeight = this._minimumHeight;
+ const maxHeight = this._maximumHeight;
+ const minAngle = this._minimumAngle;
+ const maxAngle = this._maximumAngle;
+
+ // Exit early if the bounds make the shape invisible.
+ // Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
+ if (minRadius > maxRadius || minHeight > maxHeight) {
+ this.isVisible = false;
+ return;
+ }
+
+ this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
+
+ this.orientedBoundingBox = getCylinderChunkObb(
+ minRadius,
+ maxRadius,
+ minHeight,
+ maxHeight,
+ minAngle,
+ maxAngle,
+ this.shapeTransform,
+ this.orientedBoundingBox
+ );
+
+ this.boundTransform = Matrix4.fromRotationTranslation(
+ this.orientedBoundingBox.halfAxes,
+ this.orientedBoundingBox.center,
+ this.boundTransform
+ );
+
+ this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
+ this.orientedBoundingBox,
+ this.boundingSphere
+ );
+
+ this.isVisible = true;
+};
+
+/**
+ * Computes an oriented bounding box for a specified tile.
+ * The update function must be called before calling this function.
+ * @function
+ * @param {Number} tileLevel The tile's level.
+ * @param {Number} tileX The tile's x coordinate.
+ * @param {Number} tileY The tile's y coordinate.
+ * @param {Number} tileZ The tile's z coordinate.
+ * @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
+ * @returns {OrientedBoundingBox} The oriented bounding box.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function (
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ result
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number("tileLevel", tileLevel);
+ Check.typeOf.number("tileX", tileX);
+ Check.typeOf.number("tileY", tileY);
+ Check.typeOf.number("tileZ", tileZ);
+ Check.typeOf.object("result", result);
+ //>>includeEnd('debug');
+
+ const rootTransform = this.shapeTransform;
+ const minimumRadius = this._minimumRadius;
+ const maximumRadius = this._maximumRadius;
+ const minimumHeight = this._minimumHeight;
+ const maximumHeight = this._maximumHeight;
+ const minimumAngle = this._minimumAngle;
+ const maximumAngle = this._maximumAngle;
+
+ const sizeAtLevel = 1.0 / Math.pow(2.0, tileLevel);
+
+ const radiusStart = CesiumMath.lerp(
+ minimumRadius,
+ maximumRadius,
+ tileX * sizeAtLevel
+ );
+ const radiusEnd = CesiumMath.lerp(
+ minimumRadius,
+ maximumRadius,
+ (tileX + 1) * sizeAtLevel
+ );
+ const heightStart = CesiumMath.lerp(
+ minimumHeight,
+ maximumHeight,
+ tileY * sizeAtLevel
+ );
+ const heightEnd = CesiumMath.lerp(
+ minimumHeight,
+ maximumHeight,
+ (tileY + 1) * sizeAtLevel
+ );
+ const angleStart = CesiumMath.lerp(
+ minimumAngle,
+ maximumAngle,
+ tileZ * sizeAtLevel
+ );
+ const angleEnd = CesiumMath.lerp(
+ minimumAngle,
+ maximumAngle,
+ (tileZ + 1) * sizeAtLevel
+ );
+
+ return getCylinderChunkObb(
+ radiusStart,
+ radiusEnd,
+ heightStart,
+ heightEnd,
+ angleStart,
+ angleEnd,
+ rootTransform,
+ result
+ );
+};
+
+const scratchOrientedBoundingBox = new OrientedBoundingBox();
+const scratchVoxelScale = new Cartesian3();
+const scratchRootScale = new Cartesian3();
+const scratchScaleRatio = new Cartesian3();
+
+VoxelCylinderShape.prototype.computeApproximateStepSize = function (
+ voxelDimensions
+) {
+ const rootTransform = this.shapeTransform;
+
+ const minRadius = this._minimumRadius;
+ const maxRadius = this._maximumRadius;
+ const minHeight = this._minimumHeight;
+ const maxHeight = this._maximumHeight;
+ const minAngle = this._minimumAngle;
+ const maxAngle = this._maximumAngle;
+
+ const lerpRadius = 1.0 - 1.0 / voxelDimensions.x;
+ const lerpHeight = 1.0 - 1.0 / voxelDimensions.y;
+ const lerpAngle = 1.0 - 1.0 / voxelDimensions.z;
+
+ // Compare the size of an outermost cylinder voxel to the total cylinder
+ const voxelMinimumRadius = CesiumMath.lerp(minRadius, maxRadius, lerpRadius);
+ const voxelMinimumHeight = CesiumMath.lerp(minHeight, maxHeight, lerpHeight);
+ const voxelMinimumAngle = CesiumMath.lerp(minAngle, maxAngle, lerpAngle);
+ const voxelMaximumRadius = maxRadius;
+ const voxelMaximumHeight = maxHeight;
+ const voxelMaximumAngle = maxAngle;
+
+ const voxelObb = getCylinderChunkObb(
+ voxelMinimumRadius,
+ voxelMaximumRadius,
+ voxelMinimumHeight,
+ voxelMaximumHeight,
+ voxelMinimumAngle,
+ voxelMaximumAngle,
+ rootTransform,
+ scratchOrientedBoundingBox
+ );
+
+ const voxelScale = Matrix3.getScale(voxelObb.halfAxes, scratchVoxelScale);
+ const rootScale = Matrix4.getScale(rootTransform, scratchRootScale);
+ const scaleRatio = Cartesian3.divideComponents(
+ voxelScale,
+ rootScale,
+ scratchScaleRatio
+ );
+ const stepSize = Cartesian3.minimumComponent(scaleRatio);
+ return stepSize;
+};
+
+/**
+ * @private
+ * @type {Cartesian3}
+ */
+VoxelCylinderShape.DefaultMinBounds = new Cartesian3(0.0, -1.0, -CesiumMath.PI);
+
+/**
+ * @private
+ * @type {Cartesian3}
+ */
+VoxelCylinderShape.DefaultMaxBounds = new Cartesian3(1.0, +1.0, +CesiumMath.PI);
+
+export default VoxelCylinderShape;
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
new file mode 100644
index 00000000000..d6aeb46fda0
--- /dev/null
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -0,0 +1,1027 @@
+import BoundingSphere from "../Core/BoundingSphere.js";
+import Cartesian2 from "../Core/Cartesian2.js";
+import Cartesian3 from "../Core/Cartesian3.js";
+import Check from "../Core/Check.js";
+import defaultValue from "../Core/defaultValue.js";
+import defined from "../Core/defined.js";
+import Ellipsoid from "../Core/Ellipsoid.js";
+import CesiumMath from "../Core/Math.js";
+import Matrix3 from "../Core/Matrix3.js";
+import Matrix4 from "../Core/Matrix4.js";
+import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
+import Ray from "../Core/Ray.js";
+import Rectangle from "../Core/Rectangle.js";
+import VoxelShapeType from "./VoxelShapeType.js";
+
+/**
+ * A {@link VoxelShape} for an ellipsoid.
+ *
+ * @alias VoxelEllipsoidShape
+ * @constructor
+ *
+ * @param {Object} [options]
+ * @param {Ellipsoid} [options.ellipsoid]
+ * @param {Rectangle} [options.rectangle]
+ * @param {Number} [options.minimumHeight]
+ * @param {Number} [options.maximumHeight]
+ * @param {Cartesian3} [options.translation]
+ * @param {Cartesian3} [options.scale]
+ * @param {Matrix3} [options.rotation]
+ *
+ * @see VoxelShape
+ * @see VoxelShapeType
+ */
+function VoxelEllipsoidShape(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ this._ellipsoid = Ellipsoid.clone(
+ defaultValue(options.ellipsoid, Ellipsoid.WGS84)
+ );
+ this._rectangle = Rectangle.clone(
+ defaultValue(options.rectangle, Rectangle.MAX_VALUE)
+ );
+ this._minimumHeight = defaultValue(options.minimumHeight, 0.0);
+ this._maximumHeight = defaultValue(options.maximumHeight, 1.0);
+ this._translation = Cartesian3.clone(
+ defaultValue(options.translation, Cartesian3.ZERO)
+ );
+ this._scale = Cartesian3.clone(defaultValue(options.scale, Cartesian3.ONE));
+ this._rotation = Matrix3.clone(
+ defaultValue(options.rotation, Matrix3.IDENTITY)
+ );
+
+ this._firstUpdate = true;
+ this._ellipsoidOld = Ellipsoid.clone(this._ellipsoid);
+ this._rectangleOld = Rectangle.clone(this._rectangle);
+ this._minimumHeightOld = this._minimumHeight;
+ this._maximumHeightOld = this._maximumHeight;
+ this._translationOld = Cartesian3.clone(this._translation);
+ this._scaleOld = Cartesian3.clone(this._scale);
+ this._rotationOld = Matrix3.clone(this._rotation);
+
+ this._boundTransform = Matrix4.clone(Matrix4.IDENTITY);
+ this._shapeTransform = Matrix4.clone(Matrix4.IDENTITY);
+ this._orientedBoundingBox = new OrientedBoundingBox();
+ this._boundingSphere = new BoundingSphere();
+
+ this._ellipsoidHeightDifferenceUv = 1.0;
+ this._ellipsoidOuterRadiiLocal = new Cartesian3();
+ this._ellipsoidLongitudeBounds = new Cartesian3();
+ this._ellipsoidLatitudeBounds = new Cartesian3();
+
+ this._type = VoxelShapeType.ELLIPSOID;
+
+ this._phiMin = undefined;
+ this._phiMax = undefined;
+ this._thetaMin = undefined;
+ this._thetaMax = undefined;
+ setThetaAndPhiExtrema(this);
+}
+
+function setThetaAndPhiExtrema(that) {
+ const rectangle = that.rectangle;
+ // Converting from cartographic lat, lon, height to spherical theta, phi, and radius for math
+
+ // phi is the angle with the x axis in the counter clockwise direction. It is like
+ // longitude, but when it gets halfway around the globe, instead of going to negative
+ // pi it continues to positive 2 pi. [0, 2pi]
+ that._phiMin = rectangle.west;
+ if (rectangle.west < 0) {
+ // from [-pi, pi] to [0, 2pi]
+ that._phiMin = CesiumMath.TWO_PI + rectangle.west;
+ }
+ that._phiMax = that._phiMin + rectangle.width;
+ if (that._phiMax > CesiumMath.TWO_PI) {
+ that._phiMax -= CesiumMath.TWO_PI;
+ }
+
+ // theta is angle from z axis to point. It is like latitude except zero is the north pole
+ // and it increases as it points more and more south.
+ // From [-pi/2, pi/2] centered on equator to [0, pi] from z axis. This swaps min/max
+ that._thetaMin = CesiumMath.PI_OVER_TWO - rectangle.north;
+ that._thetaMax = that._thetaMin + rectangle.height;
+}
+
+Object.defineProperties(VoxelEllipsoidShape.prototype, {
+ /**
+ * Gets or sets the ellipsoid.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Ellipsoid}
+ */
+ ellipsoid: {
+ get: function () {
+ return this._ellipsoid;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("ellipsoid", value);
+ //>>includeEnd('debug');
+
+ this._ellipsoid = Ellipsoid.clone(value, this._translation);
+ },
+ },
+ /**
+ * Gets or sets the rectangle relative to the ellipsoid.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Rectangle}
+ */
+ rectangle: {
+ get: function () {
+ return this._rectangle;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("rectangle", value);
+ //>>includeEnd('debug');
+
+ this._rectangle = Rectangle.clone(value, this._rectangle);
+ setThetaAndPhiExtrema(this);
+ },
+ },
+ /**
+ * Gets or sets the minimum height relative to the ellipsoid surface.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Number}
+ */
+ minimumHeight: {
+ get: function () {
+ return this._minimumHeight;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("minimumHeight", value);
+ //>>includeEnd('debug');
+
+ this._minimumHeight = value;
+ },
+ },
+ /**
+ * Gets or sets the maximum height relative to the ellipsoid surface.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Number}
+ */
+ maximumHeight: {
+ get: function () {
+ return this._maximumHeight;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("maximumHeight", value);
+ //>>includeEnd('debug');
+
+ this._maximumHeight = value;
+ },
+ },
+ /**
+ * Gets or sets the translation.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Cartesian3}
+ */
+ translation: {
+ get: function () {
+ return this._translation;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("translation", value);
+ //>>includeEnd('debug');
+
+ this._translation = Cartesian3.clone(value, this._translation);
+ },
+ },
+ /**
+ * Gets or sets the scale.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Cartesian3}
+ */
+ scale: {
+ get: function () {
+ return this._scale;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("scale", value);
+ //>>includeEnd('debug');
+
+ this._scale = Cartesian3.clone(value, this._scale);
+ },
+ },
+ /**
+ * Gets or sets the rotation.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {Matrix3}
+ */
+ rotation: {
+ get: function () {
+ return this._rotation;
+ },
+ set: function (value) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("rotation", value);
+ //>>includeEnd('debug');
+
+ this._rotation = Matrix3.clone(value, this._rotation);
+ },
+ },
+ /**
+ * Gets the bounding sphere.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {BoundingSphere}
+ * @readonly
+ */
+ boundingSphere: {
+ get: function () {
+ if (isDirty(this)) {
+ computeTransforms(this);
+ }
+ return this._boundingSphere;
+ },
+ },
+ /**
+ * Gets the oriented bounding box.
+ * @memberof VoxelEllipsoidShape.prototype
+ * @type {OrientedBoundingBox}
+ * @readonly
+ */
+ orientedBoundingBox: {
+ get: function () {
+ if (isDirty(this)) {
+ computeTransforms(this);
+ }
+ return this._orientedBoundingBox;
+ },
+ },
+});
+
+VoxelEllipsoidShape.clone = function (shape, result) {
+ if (!defined(shape)) {
+ return undefined;
+ }
+
+ if (!defined(result)) {
+ result = new VoxelEllipsoidShape();
+ }
+
+ result._minimumHeight = shape._minimumHeight;
+ result._maximumHeight = shape._maximumHeight;
+ result._ellipsoid = shape._ellipsoid;
+ result._rectangle = shape._rectangle;
+
+ result._translation = Cartesian3.clone(shape._translation);
+ result._scale = Cartesian3.clone(shape._scale);
+ result._rotation = Matrix3.clone(shape._rotation);
+
+ result._firstUpdate = shape._firstUpdate;
+
+ result._minimumHeightOld = shape._minimumHeightOld;
+ result._maximumHeightOld = shape._maximumHeightOld;
+ result._ellipsoidOld = shape._ellipsoidOld;
+ result._rectangleOld = shape._rectangleOld;
+ result._translationOld = Cartesian3.clone(shape._translationOld);
+ result._scaleOld = Cartesian3.clone(shape._scaleOld);
+ result._rotationOld = Matrix3.clone(shape._rotationOld);
+
+ result._boundTransform = Matrix4.clone(shape._boundTransform);
+ result._shapeTransform = Matrix4.clone(shape._shapeTransform);
+ result._orientedBoundingBox = OrientedBoundingBox.clone(
+ shape._orientedBoundingBox
+ );
+ result._boundingSphere = BoundingSphere.clone(shape._boundingSphere);
+
+ result._type = shape._type;
+
+ return result;
+};
+
+VoxelEllipsoidShape.prototype.clone = function (result) {
+ return VoxelEllipsoidShape.clone(this, result);
+};
+
+const scratchScale = new Cartesian3();
+const scratchOrientedBoundingBox = new OrientedBoundingBox();
+const scratchRectangle = new Rectangle();
+
+function isDirty(that) {
+ const dirtyEllipsoid = !that._ellipsoid.equals(that._ellipsoidOld);
+ const dirtyRectangle = !Rectangle.equals(that._rectangle, that._rectangleOld);
+ const dirtyMinimumHeight = that._minimumHeight !== that._minimumHeightOld;
+ const dirtyMaximumHeight = that._maximumHeight !== that._maximumHeightOld;
+ const dirtyTranslation = !Cartesian3.equals(
+ that._translation,
+ that._translationOld
+ );
+ const dirtyScale = !Cartesian3.equals(that._scale, that._scaleOld);
+ const dirtyRotation = !Matrix3.equals(that._rotation, that._rotationOld);
+
+ return (
+ that._firstUpdate ||
+ dirtyEllipsoid ||
+ dirtyRectangle ||
+ dirtyMinimumHeight ||
+ dirtyMaximumHeight ||
+ dirtyTranslation ||
+ dirtyScale ||
+ dirtyRotation
+ );
+}
+
+function computeTransforms(that) {
+ const ellipsoid = that._ellipsoid;
+ const minimumHeight = that._minimumHeight;
+ const maximumHeight = that._maximumHeight;
+ const rectangle = that._rectangle;
+
+ const radii = ellipsoid.radii;
+ const scaleX = 2.0 * (radii.x + maximumHeight);
+ const scaleY = 2.0 * (radii.y + maximumHeight);
+ const scaleZ = 2.0 * (radii.z + maximumHeight);
+ const scale = Cartesian3.fromElements(scaleX, scaleY, scaleZ, scratchScale);
+
+ that._shapeTransform = Matrix4.fromScale(scale, that._shapeTransform);
+
+ if (rectangle.equals(Rectangle.MAX_VALUE)) {
+ that._boundTransform = Matrix4.clone(
+ that._shapeTransform,
+ that._boundTransform
+ );
+ } else {
+ // Convert the obb to a model matrix
+ const obb = OrientedBoundingBox.fromRectangle(
+ rectangle,
+ minimumHeight,
+ maximumHeight,
+ ellipsoid,
+ scratchOrientedBoundingBox
+ );
+ that._boundTransform = OrientedBoundingBox.computeTransformation(
+ obb,
+ that._boundTransform
+ );
+ }
+
+ that._orientedBoundingBox = OrientedBoundingBox.fromTransformation(
+ that._boundTransform,
+ that._orientedBoundingBox
+ );
+ that._boundingSphere = BoundingSphere.fromTransformation(
+ that._boundTransform,
+ that._boundingSphere
+ );
+
+ const boundedWest = rectangle.west;
+ const boundedEast = rectangle.east;
+ const boundedSouth = rectangle.south;
+ const boundedNorth = rectangle.north;
+ const longitudeRange = rectangle.width;
+ const latitudeRange = rectangle.height;
+
+ const ellipsoidRadii = ellipsoid.radii;
+ const ellipsoidMaximumRadius = ellipsoid.maximumRadius;
+ that._ellipsoidHeightDifferenceUv =
+ 1.0 -
+ (ellipsoidMaximumRadius + minimumHeight) /
+ (ellipsoidMaximumRadius + maximumHeight);
+ that._ellipsoidOuterRadiiLocal = Cartesian3.divideByScalar(
+ ellipsoidRadii,
+ ellipsoidMaximumRadius,
+ that._ellipsoidOuterRadiiLocal
+ );
+ that._ellipsoidLongitudeBounds = Cartesian3.fromElements(
+ boundedWest,
+ boundedEast,
+ longitudeRange,
+ that._ellipsoidLongitudeBounds
+ );
+ that._ellipsoidLatitudeBounds = Cartesian3.fromElements(
+ boundedSouth,
+ boundedNorth,
+ latitudeRange,
+ that._ellipsoidLatitudeBounds
+ );
+}
+
+VoxelEllipsoidShape.prototype.update = function () {
+ if (isDirty(this)) {
+ computeTransforms(this);
+ this._firstUpdate = false;
+ this._ellipsoidOld = Ellipsoid.clone(this._ellipsoid, this._ellipsoidOld);
+ this._rectangleOld = Rectangle.clone(this._rectangle, this._rectangleOld);
+ this._minimumHeightOld = this._minimumHeight;
+ this._maximumHeightOld = this._maximumHeight;
+ this._translationOld = Cartesian3.clone(
+ this._translation,
+ this._translationOld
+ );
+ this._scaleOld = Cartesian3.clone(this._scale, this._scaleOld);
+ this._rotationOld = Matrix3.clone(this._rotation, this._rotationOld);
+ return true;
+ }
+
+ return false;
+};
+
+VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ result
+) {
+ const ellipsoid = this._ellipsoid;
+ const rectangle = this._rectangle;
+ const minimumHeight = this._minimumHeight;
+ const maximumHeight = this._maximumHeight;
+
+ const sizeAtLevel = 1.0 / Math.pow(2, tileLevel);
+ const tileMinimumHeight = CesiumMath.lerp(
+ minimumHeight,
+ maximumHeight,
+ tileZ * sizeAtLevel
+ );
+ const tileMaximumHeight = CesiumMath.lerp(
+ minimumHeight,
+ maximumHeight,
+ (tileZ + 1) * sizeAtLevel
+ );
+
+ const westLerp = tileX * sizeAtLevel;
+ const eastLerp = (tileX + 1) * sizeAtLevel;
+ const southLerp = tileY * sizeAtLevel;
+ const northLerp = (tileY + 1) * sizeAtLevel;
+ const tileRectangle = Rectangle.subsection(
+ rectangle,
+ westLerp,
+ southLerp,
+ eastLerp,
+ northLerp,
+ scratchRectangle
+ );
+ return OrientedBoundingBox.fromRectangle(
+ tileRectangle,
+ tileMinimumHeight,
+ tileMaximumHeight,
+ ellipsoid,
+ result
+ );
+};
+
+VoxelEllipsoidShape.prototype.computeApproximateStepSize = function (
+ voxelDimensions
+) {
+ const ellipsoid = this._ellipsoid;
+ const ellipsoidMaximumRadius = ellipsoid.maximumRadius;
+ const minimumHeight = this._minimumHeight;
+ const maximumHeight = this._maximumHeight;
+
+ const shellToEllipsoidRatio =
+ (maximumHeight - minimumHeight) / (ellipsoidMaximumRadius + maximumHeight);
+ const stepSize = (0.5 * shellToEllipsoidRatio) / voxelDimensions.z;
+ return stepSize;
+};
+
+const scratchSphericalMinima = new Cartesian3();
+const scratchSphericalMaxima = new Cartesian3();
+VoxelEllipsoidShape.prototype.localPointInsideShape = function (
+ point,
+ clippingMinimum,
+ clippingMaximum
+) {
+ const pointUv = Cartesian3.multiplyByScalar(point, 2.0, new Cartesian3());
+ const pointEllipsoidSpace = Cartesian3.multiplyComponents(
+ pointUv,
+ this._ellipsoidOuterRadiiLocal,
+ new Cartesian3()
+ ); // now we work as if the ellipsoid is a sphere
+
+ getSphericalExtremaFromClippingExtrema(
+ this,
+ clippingMinimum,
+ clippingMaximum,
+ scratchSphericalMinima,
+ scratchSphericalMaxima
+ );
+ return pointIsWithinClippingParameters(
+ pointEllipsoidSpace,
+ scratchSphericalMinima.z,
+ scratchSphericalMaxima.z,
+ scratchSphericalMinima.y,
+ scratchSphericalMaxima.y,
+ scratchSphericalMinima.x,
+ scratchSphericalMaxima.x,
+ this._ellipsoidLongitudeBounds
+ );
+};
+
+VoxelEllipsoidShape.prototype.transformFromLocalToShapeSpace = function (
+ localCartesian,
+ result
+) {
+ const pos3D = new Cartesian3();
+ pos3D.x = localCartesian.x * 2.0;
+ pos3D.y = localCartesian.y * 2.0;
+ pos3D.z = localCartesian.z * 2.0;
+ const ellipsoidRadiiLocal = this._ellipsoidOuterRadiiLocal;
+ Cartesian3.multiplyComponents(pos3D, ellipsoidRadiiLocal, pos3D);
+
+ const cartographic = Ellipsoid.fromCartesian3(
+ Cartesian3.fromElements(1.0, 1.0, 1.0)
+ ).cartesianToCartographic(pos3D);
+ let longitudeMin = this._ellipsoidLongitudeBounds.x;
+ const longitudeMax = this._ellipsoidLongitudeBounds.y;
+ const longitudeWidth = this._ellipsoidLongitudeBounds.z;
+
+ const latitudeMin = this._ellipsoidLatitudeBounds.x;
+ const latitudeWidth = this._ellipsoidLatitudeBounds.z;
+
+ if (longitudeMin > longitudeMax) {
+ longitudeMin -= CesiumMath.TWO_PI;
+ if (cartographic.longitude > longitudeMax) {
+ cartographic.longitude -= CesiumMath.TWO_PI;
+ }
+ }
+
+ // normalize to be within min and max of ellipsoid slice
+ const distMin = -this._ellipsoidHeightDifferenceUv;
+ cartographic.longitude =
+ (cartographic.longitude - longitudeMin) / longitudeWidth;
+ cartographic.latitude = (cartographic.latitude - latitudeMin) / latitudeWidth;
+ cartographic.height = (cartographic.height - distMin) / -distMin;
+ result.x = cartographic.longitude;
+ result.y = cartographic.latitude;
+ result.z = cartographic.height;
+ return result;
+};
+
+const scratchIntersectionTs = new Cartesian2();
+VoxelEllipsoidShape.prototype.intersectRay = function (
+ ray,
+ clippingMinimum,
+ clippingMaximum
+) {
+ if (!defined(clippingMinimum)) {
+ clippingMinimum = Cartesian3.ZERO;
+ }
+ if (!defined(clippingMaximum)) {
+ clippingMinimum = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ }
+
+ const epsilon = CesiumMath.EPSILON6;
+
+ getSphericalExtremaFromClippingExtrema(
+ this,
+ clippingMinimum,
+ clippingMaximum,
+ scratchSphericalMinima,
+ scratchSphericalMaxima
+ );
+ const phiMinClipping = scratchSphericalMinima.x;
+ const phiMaxClipping = scratchSphericalMaxima.x;
+ const thetaMinClipping = scratchSphericalMinima.y;
+ const thetaMaxClipping = scratchSphericalMaxima.y;
+ const radiusMinClipping = scratchSphericalMinima.z;
+ const radiusMaxClipping = scratchSphericalMaxima.z;
+
+ // convert to ellipsoid space. This simplifies analytical solutions since in ellipsoid space we are working with a sphere.
+ const ellipsoidRadiiLocal = this._ellipsoidOuterRadiiLocal;
+ const origin = Cartesian3.multiplyComponents(
+ ray.origin,
+ ellipsoidRadiiLocal,
+ new Cartesian3()
+ );
+ let direction = Cartesian3.multiplyComponents(
+ ray.direction,
+ ellipsoidRadiiLocal,
+ new Cartesian3()
+ );
+ direction = Cartesian3.normalize(direction, direction);
+ const rayEllipsoidSpace = new Ray(origin, direction);
+
+ const a = Cartesian3.dot(direction, direction);
+ const b = Cartesian3.dot(origin, direction);
+ const originDot = Cartesian3.dot(origin, origin);
+
+ let returnT = Number.MAX_VALUE;
+
+ const tsOuter = findTsAtRadus(
+ radiusMaxClipping,
+ a,
+ b,
+ originDot,
+ scratchIntersectionTs
+ );
+
+ const ellipsoidLongitudeBounds = this._ellipsoidLongitudeBounds;
+ if (
+ tsOuter.x >= 0.0 &&
+ tIsWithinClippingPlanes(
+ tsOuter.x,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = tsOuter.x;
+ }
+ const tsInner = findTsAtRadus(
+ radiusMinClipping,
+ a,
+ b,
+ originDot,
+ scratchIntersectionTs
+ );
+ if (
+ tsInner.y >= 0.0 &&
+ tIsWithinClippingPlanes(
+ tsInner.y,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tsInner.y);
+ }
+
+ const tPhiMinClipping = findTAtPhi(phiMinClipping, rayEllipsoidSpace);
+ if (
+ tPhiMinClipping >= 0 &&
+ tIsWithinClippingPlanes(
+ tPhiMinClipping,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tPhiMinClipping);
+ }
+ const tPhiMaxClipping = findTAtPhi(phiMaxClipping, rayEllipsoidSpace);
+ if (
+ tPhiMinClipping >= 0 &&
+ tIsWithinClippingPlanes(
+ tPhiMaxClipping,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tPhiMaxClipping);
+ }
+
+ if (phiMinClipping < phiMaxClipping) {
+ const tPhiMinAbsolute = findTAtPhi(this._phiMin, rayEllipsoidSpace);
+ if (
+ tPhiMinClipping >= 0 &&
+ tIsWithinClippingPlanes(
+ tPhiMinAbsolute,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tPhiMinAbsolute);
+ }
+ const tPhiMaxAbsolute = findTAtPhi(this._phiMax, rayEllipsoidSpace);
+ if (
+ tPhiMinAbsolute >= 0 &&
+ tIsWithinClippingPlanes(
+ tPhiMaxAbsolute,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tPhiMaxAbsolute);
+ }
+ }
+
+ const tsThetaMin = findTAtTheta(
+ thetaMinClipping,
+ rayEllipsoidSpace,
+ scratchIntersectionTs
+ );
+ if (
+ tsThetaMin.x >= 0.0 &&
+ tIsWithinClippingPlanes(
+ tsThetaMin.x,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tsThetaMin.x);
+ }
+ if (
+ tsThetaMin.y >= 0.0 &&
+ tIsWithinClippingPlanes(
+ tsThetaMin.y,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tsThetaMin.y);
+ }
+ const tsThetaMax = findTAtTheta(
+ thetaMaxClipping,
+ rayEllipsoidSpace,
+ scratchIntersectionTs
+ );
+ if (
+ tsThetaMax.x >= 0.0 &&
+ tIsWithinClippingPlanes(
+ tsThetaMax.x,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tsThetaMax.x);
+ }
+ if (
+ tsThetaMax.y >= 0.0 &&
+ tIsWithinClippingPlanes(
+ tsThetaMax.y,
+ rayEllipsoidSpace,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ )
+ ) {
+ returnT = Math.min(returnT, tsThetaMax.y);
+ }
+ if (returnT === Number.MAX_VALUE) {
+ return -1.0;
+ }
+
+ const intersection = Ray.getPoint(rayEllipsoidSpace, returnT);
+ // transfor point to non ellipsoid space
+ Cartesian3.divideComponents(intersection, ellipsoidRadiiLocal, intersection);
+ // get t for non ellipsoid space
+ let t;
+ if (ray.direction.x !== 0) {
+ t = (intersection.x - ray.origin.x) / ray.direction.x;
+ } else if (ray.direction.y !== 0) {
+ t = (intersection.y - ray.origin.y) / ray.direction.y;
+ } else if (ray.direction.z !== 0) {
+ t = (intersection.z - ray.origin.z) / ray.direction.z;
+ }
+ return t + epsilon;
+};
+
+function pointIsWithinClippingParameters(
+ point,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+) {
+ const radius = Math.sqrt(Cartesian3.dot(point, point));
+ let phi = Math.atan2(point.y, point.x);
+ if (phi < 0.0) {
+ phi += CesiumMath.TWO_PI;
+ }
+ let theta = Math.atan(
+ Math.abs(Math.sqrt(point.x * point.x + point.y * point.y) / point.z)
+ );
+ if (point.z < 0) {
+ theta = CesiumMath.PI - theta;
+ }
+
+ let absolutePhiMin = ellipsoidLongitudeBounds.x;
+ if (absolutePhiMin < 0.0) {
+ absolutePhiMin += CesiumMath.TWO_PI;
+ }
+ let absolutePhiMax = ellipsoidLongitudeBounds.y;
+ if (absolutePhiMax < 0.0) {
+ absolutePhiMax += CesiumMath.TWO_PI;
+ }
+ let withinAbsolutePhiBounds = phi >= absolutePhiMin && phi <= absolutePhiMax;
+ if (absolutePhiMin >= absolutePhiMax) {
+ withinAbsolutePhiBounds = phi >= absolutePhiMin || phi <= absolutePhiMax;
+ }
+ let withinPhiRange = phi >= phiMinClipping && phi <= phiMaxClipping;
+ if (phiMinClipping >= phiMaxClipping) {
+ // wrap around zero
+ withinPhiRange = phi >= phiMinClipping || phi <= phiMaxClipping;
+ }
+ return (
+ radius >= radiusMinClipping &&
+ radius <= radiusMaxClipping &&
+ withinPhiRange &&
+ withinAbsolutePhiBounds &&
+ theta >= thetaMinClipping &&
+ theta <= thetaMaxClipping
+ );
+}
+
+/**
+ * Takes the clipping minima and maxima and converts them to spherical minima and maxima
+ * (phi in radians, theta in radians, radius) for the ellipsoid
+ * @param {VoxelEllipsoidShape} that
+ * @param {Cartesian3} clippingMinimum The minimum values for shape parameters that the shader renders. Shape space [0, 1].
+ * @param {Cartesian3} clippingMaximum The minimum values for shape parameters that the shader renders. Shape space [0, 1].
+ * @param {Cartesian3} sphericalMinimum Result parameter. The minimum values in unit sphere space. (phi [0, 2pi], theta [0, pi], radius [0, inf]).
+ * @param {Cartesian3} sphericalMaximum Result parameter. The maximum values in unit sphere space. (phi [0, 2pi], theta [0, pi], radius [0, inf]).
+ * @private
+ */
+function getSphericalExtremaFromClippingExtrema(
+ that,
+ clippingMinimum,
+ clippingMaximum,
+ sphericalMinimum,
+ sphericalMaximum
+) {
+ const rectangle = that._rectangle;
+ const phiWidth = rectangle.width;
+ let phiMin = that._phiMin + phiWidth * clippingMinimum.x;
+ if (phiMin >= CesiumMath.TWO_PI) {
+ // wrap around zero
+ phiMin -= CesiumMath.TWO_PI;
+ }
+ sphericalMinimum.x = phiMin;
+ let phiMax = that._phiMin + phiWidth * clippingMaximum.x;
+ if (phiMax >= CesiumMath.TWO_PI) {
+ // wrap around zero
+ phiMax -= CesiumMath.TWO_PI;
+ }
+ sphericalMaximum.x = phiMax;
+
+ const thetaWidth = rectangle.height;
+ sphericalMinimum.y = that._thetaMin + thetaWidth * (1 - clippingMaximum.y);
+ sphericalMaximum.y = that._thetaMin + thetaWidth * (1 - clippingMinimum.y);
+
+ const radiusWidth = that._ellipsoidHeightDifferenceUv;
+ sphericalMinimum.z = 1.0 + (clippingMinimum.z - 1.0) * radiusWidth;
+ sphericalMaximum.z = 1.0 + (clippingMaximum.z - 1.0) * radiusWidth;
+}
+
+/**
+ * Get the ray parameter t of the intersection with a sphere of given radius
+ * @param {Number} radius The radius of the sphere you are intersecting with
+ * @param {Number} a The first coefficient of the quadratic equation that we are solving. This should be ray direction dot ray direction.
+ * @param {Number} b The second coefficient of the quadratic equation that we are solving. This should be ray direction dot ray origin.
+ * @param {Number} c The third coefficient of the quadratic equation that we are solving. This should be ray origin dot ray origin.
+ * @param {Cartesian2} tsAtRadius Return parameter. The first intersection will be stored in x while the second will be stored in y.
+ * @returns {Cartesian2} The return parameter tsAtRadius with the ts for the intersections in order first to second in x and y.
+ * @private
+ */
+function findTsAtRadus(radius, a, b, c, tsAtRadius) {
+ // no 2 in quadratic formula because it cancels out by taking 2 out of b
+ c -= radius * radius;
+ let discrim = b * b - a * c;
+ tsAtRadius.x = -1.0;
+ tsAtRadius.y = -1.0;
+ if (discrim >= 0.0) {
+ discrim = Math.sqrt(discrim);
+ tsAtRadius.x = (-b - discrim) / a;
+ tsAtRadius.y = (-b + discrim) / a;
+ }
+ return tsAtRadius;
+}
+
+const epsilon = CesiumMath.EPSILON6;
+function tIsWithinClippingPlanes(
+ t,
+ ray,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+) {
+ const intersectionPoint = Ray.getPoint(ray, t + epsilon);
+ return pointIsWithinClippingParameters(
+ intersectionPoint,
+ radiusMinClipping,
+ radiusMaxClipping,
+ thetaMinClipping,
+ thetaMaxClipping,
+ phiMinClipping,
+ phiMaxClipping,
+ ellipsoidLongitudeBounds
+ );
+}
+
+/**
+ * Finds the ray parameter t for the intersection with a plane created by a constant phi and a ray
+ * @param {Number} phi The angle that your want to intersect with. [0, 2pi]
+ * @param {Ray} ray The ray you are intersecting
+ * @returns {Number} The ray parameter that of the intersection of the ray and the angle phi
+ * @private
+ */
+function findTAtPhi(phi, ray) {
+ // tan(phi) = y/x, solve for t
+ const tanPhi = Math.tan(phi);
+ const origin = ray.origin;
+ const numerator = tanPhi * origin.x - origin.y;
+ const direction = ray.direction;
+ const denominator = direction.y - tanPhi * direction.x;
+ return numerator / denominator;
+}
+
+/**
+ * Finds the ray parameters t for the intersections with a ray and the cone created by a constant theta angle
+ * @param {Number} theta The theta angle that you want to intersect with. [0, pi]
+ * @param {Ray} ray The ray you are intersecting
+ * @param {Cartesian2} returnTs Return parameter. The first intersection will be stored in x while the second will be stored in y.
+ * @returns {Cartesian2} The return parameter tsAtRadius with the ts for the intersections in order first to second in x and y.
+ * @private
+ */
+function findTAtTheta(theta, ray, returnTs) {
+ const d = ray.direction;
+ const o = ray.origin;
+ if (theta === CesiumMath.PI_OVER_TWO) {
+ // intersect with z = 0
+ const intersectionT = -o.z / d.z;
+ returnTs.x = intersectionT;
+ returnTs.y = intersectionT;
+ } else {
+ // tan(theta) = sqrt(x^2 + y^2) / z, solve for t
+ const tanTheta = Math.tan(theta);
+ const tan2Theta = tanTheta * tanTheta;
+ const a = d.x * d.x + d.y * d.y - tan2Theta * d.z * d.z;
+ const b = o.x * d.x + o.y * d.y - o.z * d.z * tan2Theta;
+ const c = o.x * o.x + o.y * o.y - o.z * o.z * tan2Theta;
+ let discrim = b * b - a * c;
+ if (discrim >= 0.0) {
+ discrim = Math.sqrt(discrim);
+ returnTs.x = (-b - discrim) / a;
+ returnTs.y = (-b + discrim) / a;
+ }
+ }
+ return returnTs;
+}
+
+/**
+ * @type {Cartesian3}
+ * @private
+ */
+VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
+ -CesiumMath.PI,
+ -CesiumMath.PI_OVER_TWO,
+ 0.0
+);
+
+/**
+ * @type {Cartesian3}
+ * @private
+ */
+VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
+ +CesiumMath.PI,
+ +CesiumMath.PI_OVER_TWO,
+ 1.0
+);
+
+export default VoxelEllipsoidShape;
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
new file mode 100644
index 00000000000..5742baf92bf
--- /dev/null
+++ b/Source/Scene/VoxelPrimitive.js
@@ -0,0 +1,2695 @@
+import Cartesian2 from "../Core/Cartesian2.js";
+import Cartesian3 from "../Core/Cartesian3.js";
+import Cartesian4 from "../Core/Cartesian4.js";
+import CesiumMath from "../Core/Math.js";
+import Check from "../Core/Check.js";
+import Color from "../Core/Color.js";
+import defaultValue from "../Core/defaultValue.js";
+import defer from "../Core/defer.js";
+import defined from "../Core/defined.js";
+import destroyObject from "../Core/destroyObject.js";
+import DeveloperError from "../Core/DeveloperError.js";
+import Event from "../Core/Event.js";
+import JulianDate from "../Core/JulianDate.js";
+import Matrix3 from "../Core/Matrix3.js";
+import Matrix4 from "../Core/Matrix4.js";
+import PrimitiveType from "../Core/PrimitiveType.js";
+import RenderState from "../Renderer/RenderState.js";
+import ShaderDestination from "../Renderer/ShaderDestination.js";
+import BlendingState from "./BlendingState.js";
+import CullFace from "./CullFace.js";
+import CustomShader from "./ModelExperimental/CustomShader.js";
+import CustomShaderPipelineStage from "./ModelExperimental/CustomShaderPipelineStage.js";
+import Material from "./Material.js";
+import PolylineCollection from "./PolylineCollection.js";
+import VoxelShapeType from "./VoxelShapeType.js";
+import VoxelTraversal from "./VoxelTraversal.js";
+import DrawCommand from "../Renderer/DrawCommand.js";
+import Pass from "../Renderer/Pass.js";
+import ShaderBuilder from "../Renderer/ShaderBuilder.js";
+// import VoxelFS from "../Shaders/VoxelFS.js";
+import VoxelFS_String from "./VoxelFS_String.js";
+import VoxelVS from "../Shaders/VoxelVS.js";
+import MetadataType from "./MetadataType.js";
+/**
+ * A primitive that renders voxel data from a {@link VoxelProvider}.
+ *
+ * TODO: make sure the following terms/definitions are consistent across all files
+ * world space: Cartesian WGS84
+ * local space: Cartesian [-0.5, 0.5] aligned with shape.
+ * For box, the origin is the center of the box, and the six sides sit on the planes x = -0.5, x = 0.5 etc.
+ * For cylinder, the origin is the center of the cylinder with the cylinder enclosed by the [-0.5, 0.5] box on xy-plane. Positive x-axis points to theta = 0. The top and bottom caps sit at planes z = -0.5, z = 0.5. Positive y points to theta = pi/2
+ * For ellipsoid, the origin is the center of the ellipsoid. The maximum height of the ellipsoid touches -0.5, 0.5 in xyz directions.
+ * intersection space: local space times 2 to be [-1, 1]. Used for ray intersection calculation
+ * UV space: local space plus 0.5 to be [0, 1].
+ * shape space: In the coordinate system of the shape [0, 1]
+ * For box, this is the same as UV space
+ * For cylinder, the coordinate system is (radius, theta, z). theta = 0 is aligned with x axis
+ * For ellipsoid, the coordinate system is (longitude, latitude, height). where 0 is the minimum value in each dimension, and 1 is the max.
+ *
+ * @alias VoxelPrimitive
+ * @constructor
+ *
+ * @param {Object} options Object with the following properties:
+ * @param {VoxelProvider} options.provider The voxel provider that supplies the primitive with tile data.
+ * @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix used to transform the primitive.
+ * @param {CustomShader} [options.customShader] The custom shader used to style the primitive.
+ * @param {Clock} [options.clock] The clock used to control time dynamic behavior.
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @see VoxelProvider
+ * @see Cesium3DTilesVoxelProvider
+ * @see GltfVoxelProvider
+ * @see VoxelShapeType
+ */
+function VoxelPrimitive(options) {
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("options.provider", options.provider);
+ //>>includeEnd('debug');
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
+ this._ready = false;
+
+ /**
+ * @type {Promise.}
+ * @private
+ */
+ this._readyPromise = defer();
+
+ /**
+ * @type {VoxelProvider}
+ * @private
+ */
+ this._provider = options.provider;
+
+ // If the provider fails to initialize the primitive will fail too.
+ const that = this;
+ this._provider.readyPromise.catch(function (error) {
+ that._readyPromise.reject(`provider failed with error:\n${error}`);
+ });
+
+ /**
+ * This member is not created until the provider and shape are ready.
+ * @type {VoxelTraversal}
+ * @private
+ */
+ this._traversal = undefined;
+
+ /**
+ * This member is not created until the provider is ready.
+ * @type {VoxelShape}
+ * @private
+ */
+ this._shape = undefined;
+
+ /**
+ * This member is not created until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._paddingBefore = new Cartesian3();
+
+ /**
+ * This member is not created until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._paddingAfter = new Cartesian3();
+
+ /**
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._minBounds = new Cartesian3();
+
+ /**
+ * Used to detect if the shape is dirty.
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._minBoundsOld = new Cartesian3();
+
+ /**
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._maxBounds = new Cartesian3();
+
+ /**
+ * Used to detect if the shape is dirty.
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._maxBoundsOld = new Cartesian3();
+
+ /**
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._minClippingBounds = new Cartesian3();
+
+ /**
+ * Used to detect if the clipping is dirty.
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._minClippingBoundsOld = new Cartesian3();
+
+ /**
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._maxClippingBounds = new Cartesian3();
+
+ /**
+ * Used to detect if the clipping is dirty.
+ * This member is not known until the provider is ready.
+ * @type {Cartesian3}
+ * @private
+ */
+ this._maxClippingBoundsOld = new Cartesian3();
+
+ /**
+ * The primitive's model matrix
+ * @type {Matrix4}
+ * @private
+ */
+ this._modelMatrix = defaultValue(
+ options.modelMatrix,
+ Matrix4.clone(Matrix4.IDENTITY, new Matrix4())
+ );
+
+ /**
+ * The primitive's model matrix multiplied by the provider's model matrix.
+ * This member is not known until the provider is ready.
+ * @type {Matrix4}
+ * @private
+ */
+ this._compoundModelMatrix = new Matrix4();
+
+ /**
+ * Used to detect if the shape is dirty.
+ * This member is not known until the provider is ready.
+ * @type {Matrix4}
+ * @private
+ */
+ this._compoundModelMatrixOld = new Matrix4();
+
+ /**
+ * @type {CustomShader}
+ * @private
+ */
+ this._customShader = defaultValue(
+ options.customShader,
+ VoxelPrimitive.DefaultCustomShader
+ );
+
+ /**
+ * @type {Event}
+ * @private
+ */
+ this._customShaderCompilationEvent = new Event();
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
+ this._shaderDirty = true;
+
+ /**
+ * @type {DrawCommand}
+ * @private
+ */
+ this._drawCommand = undefined;
+
+ /**
+ * @type {DrawCommand}
+ * @private
+ */
+ this._drawCommandPick = undefined;
+
+ /**
+ * @type {Object}
+ * @private
+ */
+ this._pickId = undefined;
+
+ // /**
+ // * @private
+ // * @type {TimeIntervalCollection}
+ // */
+ // this._timeIntervalCollection = undefined;
+
+ // /**
+ // * @private
+ // * @type {Clock}
+ // */
+ // this._clock = options.clock;
+
+ // /**
+ // * @private
+ // * @type {Number}
+ // */
+ // this._keyframeCount = 1;
+
+ // Transforms and other values that are computed when the shape changes
+
+ /**
+ * @type {Matrix4}
+ */
+ this._transformPositionWorldToUv = new Matrix4();
+
+ /**
+ * @type {Matrix4}
+ */
+ this._transformPositionUvToWorld = new Matrix4();
+
+ /**
+ * @type {Matrix3}
+ */
+ this._transformDirectionWorldToLocal = new Matrix3();
+
+ /**
+ * @type {Matrix3}
+ */
+ this._transformNormalLocalToWorld = new Matrix3();
+
+ /**
+ * @type {Number}
+ */
+ this._stepSizeUv = 1.0;
+
+ // Rendering
+ this._jitter = true;
+ this._nearestSampling = false;
+ this._stepSizeMultiplier = 1.0;
+ this._despeckle = false;
+ this._depthTest = true;
+ this._useLogDepth = undefined;
+ this._screenSpaceError = 4.0; // in pixels
+
+ // Debug / statistics
+ this._debugPolylines = new PolylineCollection();
+ this._debugDraw = false;
+ this._disableRender = false;
+ this._disableUpdate = false;
+
+ // Uniforms
+ this._uniformMapValues = {
+ /**
+ * @ignore
+ * @type {Texture}
+ */
+ octreeInternalNodeTexture: undefined,
+ octreeInternalNodeTilesPerRow: 0,
+ octreeInternalNodeTexelSizeUv: new Cartesian2(),
+ /**
+ * @ignore
+ * @type {Texture}
+ */
+ octreeLeafNodeTexture: undefined,
+ octreeLeafNodeTilesPerRow: 0,
+ octreeLeafNodeTexelSizeUv: new Cartesian2(),
+ /**
+ * @ignore
+ * @type {Texture[]}
+ */
+ megatextureTextures: [],
+ megatextureSliceDimensions: new Cartesian2(),
+ megatextureTileDimensions: new Cartesian2(),
+ megatextureVoxelSizeUv: new Cartesian2(),
+ megatextureSliceSizeUv: new Cartesian2(),
+ megatextureTileSizeUv: new Cartesian2(),
+ dimensions: new Cartesian3(),
+ paddingBefore: new Cartesian3(),
+ paddingAfter: new Cartesian3(),
+ minimumValues: [],
+ maximumValues: [],
+ transformPositionViewToUv: new Matrix4(),
+ transformPositionUvToView: new Matrix4(),
+ transformDirectionViewToLocal: new Matrix3(),
+ transformNormalLocalToWorld: new Matrix3(),
+ cameraPositionUv: new Cartesian3(),
+ ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
+ stepSize: 1.0,
+ ellipsoidHeightDifferenceUv: 1.0,
+ ellipsoidOuterRadiiLocal: new Cartesian3(),
+ ellipsoidInverseRadiiSquaredLocal: new Cartesian3(),
+ minBounds: new Cartesian3(),
+ maxBounds: new Cartesian3(),
+ minBoundsUv: new Cartesian3(),
+ maxBoundsUv: new Cartesian3(),
+ inverseBounds: new Cartesian3(),
+ inverseBoundsUv: new Cartesian3(),
+ minClippingBounds: new Cartesian3(),
+ maxClippingBounds: new Cartesian3(),
+ pickColor: new Color(),
+ };
+
+ // Automatically generate uniform map from the uniform values
+ /**
+ * @private
+ * @type {Object.}
+ */
+ this._uniformMap = {};
+ const uniformMapValues = this._uniformMapValues;
+ function getUniformFunction(key) {
+ return function () {
+ return uniformMapValues[key];
+ };
+ }
+ for (const key in uniformMapValues) {
+ if (uniformMapValues.hasOwnProperty(key)) {
+ this._uniformMap[`u_${key}`] = getUniformFunction(key);
+ }
+ }
+}
+
+Object.defineProperties(VoxelPrimitive.prototype, {
+ /**
+ * Gets a value indicating whether or not the primitive is ready for use.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ * @readonly
+ */
+ ready: {
+ get: function () {
+ return this._ready;
+ },
+ },
+
+ /**
+ * Gets the promise that will be resolved when the primitive is ready for use.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Promise.}
+ * @readonly
+ */
+ readyPromise: {
+ get: function () {
+ return this._readyPromise.promise;
+ },
+ },
+
+ /**
+ * Gets the {@link VoxelProvider} associated with this primitive.
+ * @type {VoxelProvider}
+ * @readonly
+ */
+ provider: {
+ get: function () {
+ return this._provider;
+ },
+ },
+
+ /**
+ * Gets the bounding sphere.
+ * @memberof VoxelPrimitive.prototype
+ * @type {BoundingSphere}
+ * @throws {DeveloperError} If the primitive is not ready.
+ * @readonly
+ */
+ boundingSphere: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "boundingSphere must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._shape.boundingSphere;
+ },
+ },
+
+ /**
+ * Gets the oriented bounding box.
+ * @memberof VoxelPrimitive.prototype
+ * @type {OrientedBoundingBox}
+ * @throws {DeveloperError} If the primitive is not ready.
+ * @readonly
+ */
+ orientedBoundingBox: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "orientedBoudingBox must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._shape.orientedBoudingBox;
+ },
+ },
+
+ /**
+ * @memberof VoxelPrimitive.prototype
+ * @type {Matrix4}
+ * @readonly
+ */
+ modelMatrix: {
+ get: function () {
+ return this._modelMatrix;
+ },
+ set: function (modelMatrix) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("modelMatrix", modelMatrix);
+ //>>includeEnd('debug');
+
+ this._modelMatrix = Matrix4.clone(modelMatrix, new Matrix4());
+ },
+ },
+
+ /**
+ * @memberof VoxelPrimitive.prototype
+ * @type {Matrix4}
+ * @throws {DeveloperError} If the primitive is not ready.
+ * @readonly
+ */
+ compoundModelMatrix: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "compoundModelMatrix must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._compoundModelMatrix;
+ },
+ },
+
+ /**
+ * Gets the shape type.
+ * @memberof VoxelPrimitive.prototype
+ * @type {VoxelShapeType}
+ * @throws {DeveloperError} If the primitive is not ready.
+ * @readonly
+ */
+ shape: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "shape must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._provider.shape;
+ },
+ },
+
+ /**
+ * @memberof VoxelPrimitive.prototype
+ * @type {Cartesian3}
+ * @throws {DeveloperError} If the primitive is not ready.
+ * @readonly
+ */
+ dimensions: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "dimensions must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._provider.dimensions;
+ },
+ },
+
+ /**
+ * Gets the minimum value per channel of the voxel data.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Number[]}
+ * @throws {DeveloperError} If the primitive is not ready.
+ * @readonly
+ */
+ minimumValues: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "minimumValues must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._provider.minimumValues;
+ },
+ },
+
+ /**
+ * Gets the maximum value per channel of the voxel data.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Number[]}
+ * @throws {DeveloperError} If the primitive is not ready.
+ * @readonly
+ */
+ maximumValues: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "maximumValues must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._provider.maximumValues;
+ },
+ },
+
+ /**
+ * Gets or sets whether or not this primitive should be displayed.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ */
+ show: {
+ get: function () {
+ return !this._disableRender;
+ },
+ set: function (show) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("show", show);
+ //>>includeEnd('debug');
+
+ this._disableRender = !show;
+ },
+ },
+
+ /**
+ * Gets or sets whether or not the primitive should update when the view changes.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ */
+ disableUpdate: {
+ get: function () {
+ return this._disableUpdate;
+ },
+ set: function (disableUpdate) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("disableUpdate", disableUpdate);
+ //>>includeEnd('debug');
+
+ this._disableUpdate = disableUpdate;
+ },
+ },
+
+ /**
+ * Gets or sets whether or not to render debug visualizations.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ */
+ debugDraw: {
+ get: function () {
+ return this._debugDraw;
+ },
+ set: function (debugDraw) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("debugDraw", debugDraw);
+ //>>includeEnd('debug');
+
+ this._debugDraw = debugDraw;
+ },
+ },
+
+ /**
+ * Gets or sets whether or not to test against depth when rendering.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ */
+ depthTest: {
+ get: function () {
+ return this._depthTest;
+ },
+ set: function (depthTest) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("depthTest", depthTest);
+ //>>includeEnd('debug');
+
+ if (this._depthTest !== depthTest) {
+ this._depthTest = depthTest;
+ this._shaderDirty = true;
+ }
+ },
+ },
+
+ /**
+ * Gets or sets whether or not to jitter the view ray during the raymarch.
+ * This reduces stair-step artifacts but introduces noise.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ */
+ jitter: {
+ get: function () {
+ return this._jitter;
+ },
+ set: function (jitter) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("jitter", jitter);
+ //>>includeEnd('debug');
+
+ if (this._jitter !== jitter) {
+ this._jitter = jitter;
+ this._shaderDirty = true;
+ }
+ },
+ },
+
+ /**
+ *
+ */
+ nearestSampling: {
+ get: function () {
+ return this._nearestSampling;
+ },
+ set: function (nearestSampling) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("nearestSampling", nearestSampling);
+ //>>includeEnd('debug');
+
+ if (this._nearestSampling !== nearestSampling) {
+ this._nearestSampling = nearestSampling;
+ this._shaderDirty = true;
+ }
+ },
+ },
+
+ /**
+ * Gets or sets the screen space error in pixels. If the screen space size
+ * of a voxel is greater than the screen space error, the tile is subdivided.
+ * Lower screen space error corresponds with higher detail rendering, but could
+ * result in worse performance and higher memory consumption.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Number}
+ */
+ screenSpaceError: {
+ get: function () {
+ return this._screenSpaceError;
+ },
+ set: function (screenSpaceError) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number("screenSpaceError", screenSpaceError);
+ //>>includeEnd('debug');
+
+ this._screenSpaceError = screenSpaceError;
+ },
+ },
+
+ /**
+ * Gets or sets the step size multiplier used during raymarching.
+ * The lower the value, the higher the rendering quality, but
+ * also the worse the performance.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Number}
+ */
+ stepSize: {
+ get: function () {
+ return this._stepSizeMultiplier;
+ },
+ set: function (stepSize) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number("stepSize", stepSize);
+ //>>includeEnd('debug');
+
+ this._stepSizeMultiplier = stepSize;
+ },
+ },
+
+ /**
+ * Gets or sets whether to reduce thin and noisy details.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ */
+ despeckle: {
+ get: function () {
+ return this._despeckle;
+ },
+ set: function (despeckle) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("despeckle", despeckle);
+ //>>includeEnd('debug');
+
+ if (this._despeckle !== despeckle) {
+ this._despeckle = despeckle;
+ this._shaderDirty = true;
+ }
+ },
+ },
+
+ /**
+ * Gets or sets the minimum bounds. TODO: fill in the rest later
+ * @memberof VoxelPrimitive.prototype
+ * @type {Cartesian3}
+ * @throws {DeveloperError} If the primitive is not ready.
+ */
+ minBounds: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "minBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._minBounds;
+ },
+ set: function (minBounds) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("minBounds", minBounds);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "minBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
+ },
+ },
+
+ /**
+ * Gets or sets the maximum bounds. TODO: fill in the rest later
+ * @memberof VoxelPrimitive.prototype
+ * @type {Cartesian3}
+ * @throws {DeveloperError} If the primitive is not ready.
+ */
+ maxBounds: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "maxBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._maxBounds;
+ },
+ set: function (maxBounds) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("maxBounds", maxBounds);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "maxBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
+ },
+ },
+
+ /**
+ * Gets or sets the minimum clipping location in the shape's local coordinate system.
+ * Any voxel content outside the range is clipped.
+ * The minimum value is 0 and the maximum value is 1.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Cartesian3}
+ * @throws {DeveloperError} If the primitive is not ready.
+ */
+ minClippingBounds: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug)
+ if (!this._ready) {
+ throw new DeveloperError(
+ "minClippingBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._minClippingBounds;
+ },
+ set: function (minClippingBounds) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("minClippingBounds", minClippingBounds);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "minClippingBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ this._minClippingBounds = Cartesian3.clone(
+ minClippingBounds,
+ this._minClippingBounds
+ );
+ },
+ },
+
+ /**
+ * Gets or sets the maximum clipping location in the shape's local coordinate system.
+ * Any voxel content outside the range is clipped.
+ * The minimum value is 0 and the maximum value is 1.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Cartesian3}
+ * @throws {DeveloperError} If the primitive is not ready.
+ */
+ maxClippingBounds: {
+ get: function () {
+ //>>includeStart('debug', pragmas.debug)
+ if (!this._ready) {
+ throw new DeveloperError(
+ "maxClippingBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ return this._maxClippingBounds;
+ },
+ set: function (maxClippingBounds) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("maxClippingBounds", maxClippingBounds);
+ if (!this._ready) {
+ throw new DeveloperError(
+ "maxClippingBounds must not be called before the primitive is ready."
+ );
+ }
+ //>>includeEnd('debug');
+
+ this._maxClippingBounds = Cartesian3.clone(
+ maxClippingBounds,
+ this._maxClippingBounds
+ );
+ },
+ },
+
+ /**
+ * Gets or sets the custom shader. If undefined, {@link VoxelPrimitive.DefaultCustomShader} is set.
+ * @memberof VoxelPrimitive.prototype
+ * @type {CustomShader}
+ */
+ customShader: {
+ get: function () {
+ return this._customShader;
+ },
+ set: function (customShader) {
+ if (this._customShader !== customShader) {
+ if (!defined(customShader)) {
+ this._customShader = VoxelPrimitive.DefaultCustomShader;
+ } else {
+ this._customShader = customShader;
+ }
+ this._shaderDirty = true;
+ }
+ },
+ },
+
+ /**
+ * Gets an event that is raised whenever a custom shader is compiled.
+ * @memberof VoxelPrimitive.prototype
+ * @type {Event}
+ * @readonly
+ */
+ customShaderCompilationEvent: {
+ get: function () {
+ return this._customShaderCompilationEvent;
+ },
+ },
+});
+
+// TODO 3-channel + 1-channel metadata is a problem right now
+// Individually, they both work, but together the 1-channel is messed up
+
+const scratchTotalDimensions = new Cartesian3();
+const scratchIntersect = new Cartesian4();
+const scratchNdcAabb = new Cartesian4();
+const scratchScale = new Cartesian3();
+const scratchLocalScale = new Cartesian3();
+const scratchInverseLocalScale = new Cartesian3();
+const scratchRotation = new Matrix3();
+const scratchInverseRotation = new Matrix3();
+const scratchRotationAndLocalScale = new Matrix3();
+const scratchTransformPositionWorldToLocal = new Matrix4();
+const scratchTransformPositionLocalToWorld = new Matrix4();
+const scratchTransformPositionLocalToProjection = new Matrix4();
+
+const transformPositionLocalToUv = Matrix4.fromRotationTranslation(
+ Matrix3.fromUniformScale(0.5, new Matrix3()),
+ new Cartesian3(0.5, 0.5, 0.5),
+ new Matrix4()
+);
+const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
+ Matrix3.fromUniformScale(2.0, new Matrix3()),
+ new Cartesian3(-1.0, -1.0, -1.0),
+ new Matrix4()
+);
+
+/**
+ * @private
+ * @param {FrameState} frameState
+ */
+VoxelPrimitive.prototype.update = function (frameState) {
+ const context = frameState.context;
+ const provider = this._provider;
+ const uniforms = this._uniformMapValues;
+
+ // Update the provider, if applicable.
+ if (defined(provider.update)) {
+ provider.update(frameState);
+ }
+
+ // Exit early if it's not ready yet.
+ if (!this._ready && !provider.ready) {
+ return;
+ }
+
+ // Initialize from the ready provider. This only happens once.
+ if (!this._ready) {
+ // Don't make the primitive ready until after its first update because
+ // external code may want to change some of its properties before it's rendered.
+ const that = this;
+ frameState.afterRender.push(function () {
+ that._ready = true;
+ that._readyPromise.resolve(that);
+ });
+
+ // Create pickId here instead of the constructor because it needs the context object.
+ this._pickId = context.createPickId({
+ primitive: this,
+ });
+
+ // Set uniforms for picking
+ uniforms.pickColor = Color.clone(this._pickId.color, uniforms.pickColor);
+
+ // Set member variables that come from the provider.
+
+ // const keyframeCount = defaultValue(provider.keyframeCount, 1);
+ // // TODO remove?
+ // that._keyframeCount = defaultValue(
+ // provider.keyframeCount,
+ // that._keyframeCount
+ // );
+ // // TODO remove?
+ // that._timeIntervalCollection = defaultValue(
+ // provider.timeIntervalCollection,
+ // that._timeIntervalCollection
+ // );
+
+ const dimensions = provider.dimensions;
+ const shapeType = provider.shape;
+ const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
+ const minBounds = defaultValue(
+ provider.minBounds,
+ ShapeConstructor.DefaultMinBounds
+ );
+ const maxBounds = defaultValue(
+ provider.maxBounds,
+ ShapeConstructor.DefaultMaxBounds
+ );
+ const minimumValues = provider.minimumValues;
+ const maximumValues = provider.maximumValues;
+
+ this._shape = new ShapeConstructor();
+ this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
+ this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
+ this._minClippingBounds = Cartesian3.clone(
+ ShapeConstructor.DefaultMinBounds,
+ this._minClippingBounds
+ );
+ this._maxClippingBounds = Cartesian3.clone(
+ ShapeConstructor.DefaultMaxBounds,
+ this._maxClippingBounds
+ );
+ this._paddingBefore = Cartesian3.clone(
+ defaultValue(provider.paddingBefore, Cartesian3.ZERO),
+ this._paddingBefore
+ );
+ this._paddingAfter = Cartesian3.clone(
+ defaultValue(provider.paddingAfter, Cartesian3.ZERO),
+ this._paddingBefore
+ );
+
+ // Set uniforms that come from the provider.
+ // Note that minBounds and maxBounds can be set dynamically, so their uniforms aren't set here.
+ uniforms.dimensions = Cartesian3.clone(dimensions, uniforms.dimensions);
+ uniforms.paddingBefore = Cartesian3.clone(
+ this._paddingBefore,
+ uniforms.paddingBefore
+ );
+ uniforms.paddingAfter = Cartesian3.clone(
+ this._paddingAfter,
+ uniforms.paddingAfter
+ );
+ if (defined(minimumValues) && defined(maximumValues)) {
+ uniforms.minimumValues = minimumValues.slice();
+ uniforms.maximumValues = maximumValues.slice();
+ }
+ }
+
+ // Check if the shape is dirty before updating it. This needs to happen every
+ // frame because the member variables can be modified externally via the
+ // getters.
+ const primitiveModelMatrix = this._modelMatrix;
+ const providerModelMatrix = defaultValue(
+ provider.modelMatrix,
+ Matrix4.IDENTITY
+ );
+ const compoundModelMatrix = Matrix4.multiplyTransformation(
+ providerModelMatrix,
+ primitiveModelMatrix,
+ this._compoundModelMatrix
+ );
+ const compoundModelMatrixOld = this._compoundModelMatrixOld;
+ const compoundModelMatrixDirty = !Matrix4.equals(
+ compoundModelMatrix,
+ compoundModelMatrixOld
+ );
+ const shape = this._shape;
+ const shapeType = provider.shape;
+
+ const minBounds = this._minBounds;
+ const maxBounds = this._maxBounds;
+ const minBoundsOld = this._minBoundsOld;
+ const maxBoundsOld = this._maxBoundsOld;
+ const minBoundsDirty = !Cartesian3.equals(minBounds, minBoundsOld);
+ const maxBoundsDirty = !Cartesian3.equals(maxBounds, maxBoundsOld);
+ const shapeIsDirty =
+ compoundModelMatrixDirty || minBoundsDirty || maxBoundsDirty;
+
+ // Update the shape if dirty or the first frame.
+ if (!this._ready || shapeIsDirty) {
+ shape.update(compoundModelMatrix, minBounds, maxBounds);
+
+ if (compoundModelMatrixDirty) {
+ this._compoundModelMatrixOld = Matrix4.clone(
+ compoundModelMatrix,
+ this._compoundModelMatrixOld
+ );
+ }
+
+ if (minBoundsDirty || maxBoundsDirty) {
+ const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
+ const defaultMinBounds = ShapeConstructor.DefaultMinBounds;
+ const defaultMaxBounds = ShapeConstructor.DefaultMaxBounds;
+ const isDefaultBoundsMinX = minBounds.x === defaultMinBounds.x;
+ const isDefaultBoundsMinY = minBounds.y === defaultMinBounds.y;
+ const isDefaultBoundsMinZ = minBounds.z === defaultMinBounds.z;
+ const isDefaultBoundsMaxX = maxBounds.x === defaultMaxBounds.x;
+ const isDefaultBoundsMaxY = maxBounds.y === defaultMaxBounds.y;
+ const isDefaultBoundsMaxZ = maxBounds.z === defaultMaxBounds.z;
+
+ if (minBoundsDirty) {
+ const isDefaultOldBoundsMinX = minBoundsOld.x === defaultMinBounds.x;
+ const isDefaultOldBoundsMinY = minBoundsOld.y === defaultMinBounds.y;
+ const isDefaultOldBoundsMinZ = minBoundsOld.z === defaultMinBounds.z;
+ if (
+ isDefaultBoundsMinX !== isDefaultOldBoundsMinX ||
+ isDefaultBoundsMinY !== isDefaultOldBoundsMinY ||
+ isDefaultBoundsMinZ !== isDefaultOldBoundsMinZ
+ ) {
+ this._shaderDirty = true;
+ }
+ this._minBoundsOld = Cartesian3.clone(minBounds, this._minBoundsOld);
+ }
+
+ if (maxBoundsDirty) {
+ const isDefaultOldBoundsMaxX = maxBoundsOld.x === defaultMaxBounds.x;
+ const isDefaultOldBoundsMaxY = maxBoundsOld.y === defaultMaxBounds.y;
+ const isDefaultOldBoundsMaxZ = maxBoundsOld.z === defaultMaxBounds.z;
+ if (
+ isDefaultBoundsMaxX !== isDefaultOldBoundsMaxX ||
+ isDefaultBoundsMaxY !== isDefaultOldBoundsMaxY ||
+ isDefaultBoundsMaxZ !== isDefaultOldBoundsMaxZ
+ ) {
+ this._shaderDirty = true;
+ }
+ this._maxBoundsOld = Cartesian3.clone(maxBounds, this._maxBoundsOld);
+ }
+
+ // Set uniforms for bounds.
+ if (
+ !isDefaultBoundsMinX ||
+ !isDefaultBoundsMinY ||
+ !isDefaultBoundsMinZ ||
+ !isDefaultBoundsMaxX ||
+ !isDefaultBoundsMaxY ||
+ !isDefaultBoundsMaxZ
+ ) {
+ uniforms.minBounds = Cartesian3.clone(minBounds, uniforms.minBounds);
+ uniforms.maxBounds = Cartesian3.clone(maxBounds, uniforms.maxBounds);
+ uniforms.inverseBounds = Cartesian3.divideComponents(
+ Cartesian3.ONE,
+ Cartesian3.subtract(maxBounds, minBounds, uniforms.inverseBounds),
+ uniforms.inverseBounds
+ );
+
+ if (shapeType === VoxelShapeType.BOX) {
+ const minXUv = minBounds.x * 0.5 + 0.5;
+ const maxXUv = maxBounds.x * 0.5 + 0.5;
+ const minYUv = minBounds.y * 0.5 + 0.5;
+ const maxYUv = maxBounds.y * 0.5 + 0.5;
+ const minZUv = minBounds.z * 0.5 + 0.5;
+ const maxZUv = maxBounds.z * 0.5 + 0.5;
+ uniforms.minBoundsUv = Cartesian3.fromElements(
+ minXUv,
+ minYUv,
+ minZUv,
+ uniforms.minBoundsUv
+ );
+ uniforms.maxBoundsUv = Cartesian3.fromElements(
+ maxXUv,
+ maxYUv,
+ maxZUv,
+ uniforms.maxBoundsUv
+ );
+ } else if (shapeType === VoxelShapeType.ELLIPSOID) {
+ uniforms.minBoundsUv = Cartesian3.clone(
+ minBounds,
+ uniforms.minBoundsUv
+ );
+ uniforms.maxBoundsUv = Cartesian3.clone(
+ maxBounds,
+ uniforms.maxBoundsUv
+ );
+ } else if (shapeType === VoxelShapeType.CYLINDER) {
+ const minRadiusUv = minBounds.x;
+ const maxRadiusUv = maxBounds.x;
+ const minHeightUv = minBounds.y * 0.5 + 0.5;
+ const maxHeightUv = maxBounds.y * 0.5 + 0.5;
+ const minAngleUv = (minBounds.z + CesiumMath.PI) / CesiumMath.TWO_PI;
+ const maxAngleUv = (maxBounds.z + CesiumMath.PI) / CesiumMath.TWO_PI;
+ uniforms.minBoundsUv = Cartesian3.fromElements(
+ minRadiusUv,
+ minHeightUv,
+ minAngleUv,
+ uniforms.minBoundsUv
+ );
+ uniforms.maxBoundsUv = Cartesian3.fromElements(
+ maxRadiusUv,
+ maxHeightUv,
+ maxAngleUv,
+ uniforms.maxBoundsUv
+ );
+ }
+
+ uniforms.inverseBoundsUv = Cartesian3.divideComponents(
+ Cartesian3.ONE,
+ Cartesian3.subtract(
+ uniforms.maxBoundsUv,
+ uniforms.minBoundsUv,
+ uniforms.inverseBoundsUv
+ ),
+ uniforms.inverseBoundsUv
+ );
+ }
+ }
+
+ // Set other uniforms when the shape is dirty
+ if (shapeType === VoxelShapeType.ELLIPSOID) {
+ uniforms.ellipsoidHeightDifferenceUv = shape._ellipsoidHeightDifferenceUv;
+ uniforms.ellipsoidOuterRadiiLocal = Cartesian3.clone(
+ shape._ellipsoidOuterRadiiLocal,
+ uniforms.ellipsoidOuterRadiiLocal
+ );
+ uniforms.ellipsoidInverseRadiiSquaredLocal = Cartesian3.multiplyComponents(
+ shape._ellipsoidOuterRadiiLocal,
+ shape._ellipsoidOuterRadiiLocal,
+ uniforms.ellipsoidInverseRadiiSquaredLocal
+ );
+ }
+
+ // Math that's only valid if the shape is visible.
+ if (shape.isVisible) {
+ const transformPositionLocalToWorld = shape.shapeTransform;
+ const transformPositionWorldToLocal = Matrix4.inverse(
+ transformPositionLocalToWorld,
+ scratchTransformPositionWorldToLocal
+ );
+ const rotation = Matrix4.getRotation(
+ transformPositionLocalToWorld,
+ scratchRotation
+ );
+ // Note that inverse(rotation) is the same as transpose(rotation)
+ const inverseRotation = Matrix3.transpose(
+ rotation,
+ scratchInverseRotation
+ );
+ const scale = Matrix4.getScale(
+ transformPositionLocalToWorld,
+ scratchScale
+ );
+ const maximumScaleComponent = Cartesian3.maximumComponent(scale);
+ const localScale = Cartesian3.divideByScalar(
+ scale,
+ maximumScaleComponent,
+ scratchLocalScale
+ );
+ const inverseLocalScale = Cartesian3.divideComponents(
+ Cartesian3.ONE,
+ localScale,
+ scratchInverseLocalScale
+ );
+ const rotationAndLocalScale = Matrix3.multiplyByScale(
+ rotation,
+ localScale,
+ scratchRotationAndLocalScale
+ );
+
+ // Set member variables when the shape is dirty
+ const dimensions = provider.dimensions;
+ this._stepSizeUv = shape.computeApproximateStepSize(dimensions);
+ this._transformPositionWorldToUv = Matrix4.multiply(
+ transformPositionLocalToUv,
+ transformPositionWorldToLocal,
+ this._transformPositionWorldToUv
+ );
+ this._transformPositionUvToWorld = Matrix4.multiply(
+ transformPositionLocalToWorld,
+ transformPositionUvToLocal,
+ this._transformPositionUvToWorld
+ );
+ this._transformDirectionWorldToLocal = Matrix3.setScale(
+ inverseRotation,
+ inverseLocalScale,
+ this._transformDirectionWorldToLocal
+ );
+ this._transformNormalLocalToWorld = Matrix3.inverseTranspose(
+ rotationAndLocalScale,
+ this._transformNormalLocalToWorld
+ );
+ }
+ }
+
+ // Initialize the voxel traversal now that the shape is ready to use. This only happens once.
+ if (!this._ready) {
+ const dimensions = provider.dimensions;
+ const paddingBefore = this._paddingBefore;
+ const paddingAfter = this._paddingAfter;
+ const totalDimensions = Cartesian3.clone(
+ dimensions,
+ scratchTotalDimensions
+ );
+ Cartesian3.add(totalDimensions, paddingBefore, totalDimensions);
+ Cartesian3.add(totalDimensions, paddingAfter, totalDimensions);
+
+ const types = provider.types;
+ const componentTypes = provider.componentTypes;
+ const keyframeCount = 1; //this._keyframeCount;
+
+ // Traversal setup
+ // It's ok for memory byte length to be undefined.
+ // The system will choose a default memory size.
+ const maximumTileCount = provider.maximumTileCount;
+ const maximumTextureMemoryByteLength = defined(maximumTileCount)
+ ? VoxelTraversal.getApproximateTextureMemoryByteLength(
+ maximumTileCount,
+ totalDimensions,
+ types,
+ componentTypes
+ )
+ : undefined;
+
+ this._traversal = new VoxelTraversal(
+ this,
+ context,
+ totalDimensions,
+ types,
+ componentTypes,
+ keyframeCount,
+ maximumTextureMemoryByteLength
+ );
+
+ // Set uniforms that come from the traversal.
+ const traversal = this._traversal;
+
+ uniforms.octreeInternalNodeTexture = traversal.internalNodeTexture;
+ uniforms.octreeInternalNodeTilesPerRow = traversal.internalNodeTilesPerRow;
+ uniforms.octreeInternalNodeTexelSizeUv = traversal.internalNodeTexelSizeUv;
+ uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
+ uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
+ uniforms.octreeLeafNodeTexelSizeUv = traversal.leafNodeTexelSizeUv;
+
+ uniforms.megatextureTextures = [];
+ const megatextures = traversal.megatextures;
+ const megatexture = megatextures[0];
+ const megatextureLength = megatextures.length;
+ for (let i = 0; i < megatextureLength; i++) {
+ uniforms.megatextureTextures.push(megatextures[i].texture);
+ }
+
+ uniforms.megatextureSliceDimensions = Cartesian2.clone(
+ megatexture.sliceCountPerRegion,
+ uniforms.megatextureSliceDimensions
+ );
+ uniforms.megatextureTileDimensions = Cartesian2.clone(
+ megatexture.regionCountPerMegatexture,
+ uniforms.megatextureTileDimensions
+ );
+ uniforms.megatextureVoxelSizeUv = Cartesian2.clone(
+ megatexture.voxelSizeUv,
+ uniforms.megatextureVoxelSizeUv
+ );
+ uniforms.megatextureSliceSizeUv = Cartesian2.clone(
+ megatexture.sliceSizeUv,
+ uniforms.megatextureSliceSizeUv
+ );
+ uniforms.megatextureTileSizeUv = Cartesian2.clone(
+ megatexture.regionSizeUv,
+ uniforms.megatextureTileSizeUv
+ );
+ } else {
+ // Update the voxel traversal
+ const traversal = this._traversal;
+ if (shape.isVisible) {
+ // Find the keyframe location to render at. Doesn't need to be a whole number.
+ let keyframeLocation = 0.0;
+ const clock = this._clock;
+ const timeIntervalCollection = this._timeIntervalCollection;
+ if (defined(timeIntervalCollection) && defined(clock)) {
+ let date = clock.currentTime;
+ let timeInterval;
+ let timeIntervalIndex = timeIntervalCollection.indexOf(date);
+ if (timeIntervalIndex >= 0) {
+ timeInterval = timeIntervalCollection.get(timeIntervalIndex);
+ } else {
+ // Date fell outside the range
+ timeIntervalIndex = ~timeIntervalIndex;
+ if (timeIntervalIndex === timeIntervalCollection.length) {
+ // Date past range
+ timeIntervalIndex = timeIntervalCollection.length - 1;
+ timeInterval = timeIntervalCollection.get(timeIntervalIndex);
+ date = timeInterval.stop;
+ } else {
+ // Date before range
+ timeInterval = timeIntervalCollection.get(timeIntervalIndex);
+ date = timeInterval.start;
+ }
+ }
+
+ // De-lerp between the start and end of the interval
+ const totalSeconds = JulianDate.secondsDifference(
+ timeInterval.stop,
+ timeInterval.start
+ );
+ const secondsDifferenceStart = JulianDate.secondsDifference(
+ date,
+ timeInterval.start
+ );
+ const t = secondsDifferenceStart / totalSeconds;
+ keyframeLocation = timeIntervalIndex + t;
+ }
+
+ traversal.update(
+ frameState,
+ keyframeLocation,
+ shapeIsDirty, // recomputeBoundingVolumes
+ this._disableUpdate // pauseUpdate
+ );
+ }
+
+ // Debug draw bounding boxes and other things. Must go after traversal update
+ // because that's what updates the tile bounding boxes.
+ if (this._debugDraw) {
+ debugDraw(this, frameState);
+ }
+
+ const rootNodeLoaded = traversal.rootNode.isRenderable(
+ traversal.frameNumber
+ );
+
+ if (shape.isVisible && !this._disableRender && rootNodeLoaded) {
+ // Process clipping bounds.
+ const minClip = this._minClippingBounds;
+ const maxClip = this._maxClippingBounds;
+ const minClipOld = this._minClippingBoundsOld;
+ const maxClipOld = this._maxClippingBoundsOld;
+ const minClipDirty = !Cartesian3.equals(minClip, minClipOld);
+ const maxClipDirty = !Cartesian3.equals(maxClip, maxClipOld);
+ const clippingBoundsDirty = minClipDirty || maxClipDirty;
+ if (clippingBoundsDirty) {
+ const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
+ const defaultMinBounds = ShapeConstructor.DefaultMinBounds;
+ const defaultMaxBounds = ShapeConstructor.DefaultMaxBounds;
+ const isDefaultClippingBoundsMinX = minClip.x === defaultMinBounds.x;
+ const isDefaultClippingBoundsMinY = minClip.y === defaultMinBounds.y;
+ const isDefaultClippingBoundsMinZ = minClip.z === defaultMinBounds.z;
+ const isDefaultClippingBoundsMaxX = maxClip.x === defaultMaxBounds.x;
+ const isDefaultClippingBoundsMaxY = maxClip.y === defaultMaxBounds.y;
+ const isDefaultClippingBoundsMaxZ = maxClip.z === defaultMaxBounds.z;
+
+ if (minClipDirty) {
+ const isDefaultOldClipMinX = minClipOld.x === defaultMinBounds.x;
+ const isDefaultOldClipMinY = minClipOld.y === defaultMinBounds.y;
+ const isDefaultOldClipMinZ = minClipOld.z === defaultMinBounds.z;
+ if (
+ isDefaultClippingBoundsMinX !== isDefaultOldClipMinX ||
+ isDefaultClippingBoundsMinY !== isDefaultOldClipMinY ||
+ isDefaultClippingBoundsMinZ !== isDefaultOldClipMinZ
+ ) {
+ this._shaderDirty = true;
+ }
+ this._minClippingBoundsOld = Cartesian3.clone(
+ minClip,
+ this._minClippingBoundsOld
+ );
+ }
+ if (maxClipDirty) {
+ const isDefaultOldClipMaxX = maxClipOld.x === defaultMaxBounds.x;
+ const isDefaultOldClipMaxY = maxClipOld.y === defaultMaxBounds.y;
+ const isDefaultOldClipMaxZ = maxClipOld.z === defaultMaxBounds.z;
+ if (
+ isDefaultClippingBoundsMaxX !== isDefaultOldClipMaxX ||
+ isDefaultClippingBoundsMaxY !== isDefaultOldClipMaxY ||
+ isDefaultClippingBoundsMaxZ !== isDefaultOldClipMaxZ
+ ) {
+ this._shaderDirty = true;
+ }
+ this._maxClippingBoundsOld = Cartesian3.clone(
+ maxClip,
+ this._maxClippingBoundsOld
+ );
+ }
+ if (
+ !isDefaultClippingBoundsMinX ||
+ !isDefaultClippingBoundsMinY ||
+ !isDefaultClippingBoundsMinZ ||
+ !isDefaultClippingBoundsMaxX ||
+ !isDefaultClippingBoundsMaxY ||
+ !isDefaultClippingBoundsMaxZ
+ ) {
+ // Set clipping uniforms
+ uniforms.minClippingBounds = Cartesian3.clone(
+ minClip,
+ uniforms.minClippingBounds
+ );
+ uniforms.maxClippingBounds = Cartesian3.clone(
+ maxClip,
+ uniforms.maxClippingBounds
+ );
+ }
+ }
+
+ // Check if log depth changed
+ if (this._useLogDepth !== frameState.useLogDepth) {
+ this._useLogDepth = frameState.useLogDepth;
+ this._shaderDirty = true;
+ }
+
+ // Rebuild shaders
+ if (this._shaderDirty) {
+ buildDrawCommands(this, context);
+ this._shaderDirty = false;
+ }
+
+ // Calculate the NDC-space AABB to "scissor" the fullscreen quad
+ const transformPositionWorldToProjection =
+ context.uniformState.viewProjection;
+ const orientedBoundingBox = shape.orientedBoundingBox;
+ const ndcAabb = orientedBoundingBoxToNdcAabb(
+ orientedBoundingBox,
+ transformPositionWorldToProjection,
+ scratchNdcAabb
+ );
+
+ // If the object is offscreen, don't render it.
+ const offscreen =
+ ndcAabb.x === +1.0 ||
+ ndcAabb.y === +1.0 ||
+ ndcAabb.z === -1.0 ||
+ ndcAabb.w === -1.0;
+
+ if (!offscreen) {
+ const transformPositionWorldToView = context.uniformState.view;
+ const transformPositionViewToWorld = context.uniformState.inverseView;
+ const transformDirectionViewToWorld =
+ context.uniformState.inverseViewRotation;
+ const transformDirectionWorldToLocal = this
+ ._transformDirectionWorldToLocal;
+ const transformPositionUvToWorld = this._transformPositionUvToWorld;
+ const transformPositionWorldToUv = this._transformPositionWorldToUv;
+ const transformNormalLocalToWorld = this._transformNormalLocalToWorld;
+ const cameraPositionWorld = frameState.camera.positionWC;
+
+ // Update uniforms that can change every frame
+ const uniforms = this._uniformMapValues;
+ uniforms.transformPositionViewToUv = Matrix4.multiply(
+ transformPositionWorldToUv,
+ transformPositionViewToWorld,
+ uniforms.transformPositionViewToUv
+ );
+ uniforms.transformPositionUvToView = Matrix4.multiply(
+ transformPositionWorldToView,
+ transformPositionUvToWorld,
+ uniforms.transformPositionUvToView
+ );
+ uniforms.transformDirectionViewToLocal = Matrix3.multiply(
+ transformDirectionWorldToLocal,
+ transformDirectionViewToWorld,
+ uniforms.transformDirectionViewToLocal
+ );
+ uniforms.transformNormalLocalToWorld = Matrix3.clone(
+ transformNormalLocalToWorld,
+ uniforms.transformNormalLocalToWorld
+ );
+ uniforms.cameraPositionUv = Matrix4.multiplyByPoint(
+ transformPositionWorldToUv,
+ cameraPositionWorld,
+ uniforms.cameraPositionUv
+ );
+ uniforms.stepSize = this._stepSizeUv * this._stepSizeMultiplier;
+
+ // Using a uniform instead of going through RenderState's scissor because the viewport is not accessible here, and the scissor command needs pixel coordinates.
+ uniforms.ndcSpaceAxisAlignedBoundingBox = Cartesian4.clone(
+ ndcAabb,
+ uniforms.ndcSpaceAxisAlignedBoundingBox
+ );
+
+ // Render the primitive
+ const command = frameState.passes.pick
+ ? this._drawCommandPick
+ : this._drawCommand;
+ command.boundingVolume = shape.boundingSphere;
+ frameState.commandList.push(command);
+ }
+ }
+ }
+};
+
+/**
+ * @param {VoxelPrimitive} that
+ * @param {Context} context
+ * @private
+ */
+function buildDrawCommands(that, context) {
+ // const renderResources
+ // CustomShaderPipelineStage.process(renderResources, primitive);
+
+ // TODO: questions about custom shaders:
+ // - where should attribute min/max go?
+ // - should shader builder call `fromCache` or `replaceCache`
+
+ // return;
+
+ const provider = that._provider;
+ const shapeType = provider.shape;
+ const names = provider.names;
+ const types = provider.types;
+ const componentTypes = provider.componentTypes;
+ const depthTest = that._depthTest;
+ const useLogDepth = that._useLogDepth;
+ const paddingBefore = that.paddingBefore;
+ const paddingAfter = that.paddingAfter;
+ const minBounds = that._minBounds;
+ const maxBounds = that._maxBounds;
+ const minimumValues = provider.minimumValues;
+ const maximumValues = provider.maximumValues;
+ const minClippingBounds = that._minClippingBounds;
+ const maxClippingBounds = that._maxClippingBounds;
+ const keyframeCount = that._keyframeCount;
+ const despeckle = that._despeckle;
+ const jitter = that._jitter;
+ const nearestSampling = that._nearestSampling;
+ const customShader = that._customShader;
+ const attributeLength = types.length;
+
+ // Vertex shader
+
+ const shaderBuilder = new ShaderBuilder();
+ shaderBuilder.addVertexLines([VoxelVS]);
+
+ // Fragment shader
+
+ const fragmentStructId = CustomShaderPipelineStage.STRUCT_ID_FRAGMENT_INPUT;
+ const fragmentStructName =
+ CustomShaderPipelineStage.STRUCT_NAME_FRAGMENT_INPUT;
+
+ const attributeStructId = CustomShaderPipelineStage.STRUCT_ID_ATTRIBUTES_FS;
+ const attributeStructName = CustomShaderPipelineStage.STRUCT_NAME_ATTRIBUTES;
+ const attributeFieldName = "attributes";
+
+ const voxelStructId = "VoxelFS";
+ const voxelStructName = "Voxel";
+ const voxelFieldName = "voxel";
+
+ // Attribute struct
+ shaderBuilder.addStruct(
+ attributeStructId,
+ attributeStructName,
+ ShaderDestination.FRAGMENT
+ );
+
+ /**
+ *
+ * @param {MetadataType} type
+ */
+ function getGlslType(type) {
+ if (type === MetadataType.SCALAR) {
+ return "float";
+ } else if (type === MetadataType.VEC2) {
+ return "vec2";
+ } else if (type === MetadataType.VEC3) {
+ return "vec3";
+ } else if (type === MetadataType.VEC4) {
+ return "vec4";
+ }
+ return "vec4";
+ }
+ /**
+ * @param {MetadataType} type
+ */
+ function getGlslTextureSwizzle(type) {
+ if (type === MetadataType.SCALAR) {
+ return ".r";
+ } else if (type === MetadataType.VEC2) {
+ return ".ra";
+ } else if (type === MetadataType.VEC3) {
+ return ".rgb";
+ } else if (type === MetadataType.VEC4) {
+ return "";
+ }
+ return "";
+ }
+
+ /**
+ * @param {MetadataType} type
+ * @param {Number} index
+ */
+ function getGlslField(type, index) {
+ if (type === MetadataType.SCALAR) {
+ return "";
+ }
+ return `.[${index}]`;
+ }
+
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const glslType = getGlslType(type);
+ shaderBuilder.addStructField(attributeStructId, glslType, name);
+ }
+
+ // Voxel struct
+ shaderBuilder.addStruct(
+ voxelStructId,
+ voxelStructName,
+ ShaderDestination.FRAGMENT
+ );
+
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const glslType = getGlslType(type);
+ shaderBuilder.addStructField(voxelStructId, glslType, `${name}Minimum`);
+ shaderBuilder.addStructField(voxelStructId, glslType, `${name}Maximum`);
+ shaderBuilder.addStructField(voxelStructId, "vec3", `${name}NormalLocal`);
+ shaderBuilder.addStructField(voxelStructId, "vec3", `${name}NormalWorld`);
+ shaderBuilder.addStructField(voxelStructId, "vec3", `${name}NormalView`);
+ shaderBuilder.addStructField(voxelStructId, "bool", `${name}NormalValid`);
+ }
+
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionEC");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionUv");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvShapeSpace");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvLocal");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirUv");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirWorld");
+ shaderBuilder.addStructField(voxelStructId, "float", "travelDistance");
+
+ // FragmentInput struct
+ shaderBuilder.addStruct(
+ fragmentStructId,
+ fragmentStructName,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addStructField(
+ fragmentStructId,
+ attributeStructName,
+ attributeFieldName
+ );
+
+ shaderBuilder.addStructField(
+ fragmentStructId,
+ voxelStructName,
+ voxelFieldName
+ );
+
+ // Custom shader
+ shaderBuilder.addFragmentLines([customShader.fragmentShaderText]);
+
+ // Voxel shader
+ shaderBuilder.addFragmentLines(["#line 0", VoxelFS_String]);
+ shaderBuilder.addDefine(
+ "METADATA_COUNT",
+ attributeLength,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addUniform(
+ "sampler2D",
+ "u_megatextureTextures[METADATA_COUNT]",
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addDefine(
+ `SHAPE_${shapeType}`,
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+
+ if (
+ !Cartesian3.equals(paddingBefore, Cartesian3.ZERO) ||
+ !Cartesian3.equals(paddingAfter, Cartesian3.ZERO)
+ ) {
+ shaderBuilder.addDefine("PADDING", undefined, ShaderDestination.FRAGMENT);
+ }
+
+ if (depthTest) {
+ shaderBuilder.addDefine(
+ "DEPTH_TEST",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
+ // Allow reading from log depth texture, but don't write log depth anywhere.
+ // Note: This needs to be set even if depthTest is off because it affects the
+ // derived command system.
+ if (useLogDepth) {
+ shaderBuilder.addDefine(
+ "LOG_DEPTH_READ_ONLY",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
+ if (jitter) {
+ shaderBuilder.addDefine("JITTER", undefined, ShaderDestination.FRAGMENT);
+ }
+
+ if (nearestSampling) {
+ shaderBuilder.addDefine(
+ "NEAREST_SAMPLING",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
+ if (despeckle) {
+ shaderBuilder.addDefine("DESPECKLE", undefined, ShaderDestination.FRAGMENT);
+ }
+
+ const sampleCount = keyframeCount > 1 ? 2 : 1;
+ shaderBuilder.addDefine(
+ "SAMPLE_COUNT",
+ `${sampleCount}`,
+ ShaderDestination.FRAGMENT
+ );
+
+ const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
+ const defaultMinBounds = ShapeConstructor.DefaultMinBounds;
+ const defaultMaxBounds = ShapeConstructor.DefaultMaxBounds;
+
+ const isDefaultMinX = minBounds.x === defaultMinBounds.x;
+ const isDefaultMinY = minBounds.y === defaultMinBounds.y;
+ const isDefaultMinZ = minBounds.z === defaultMinBounds.z;
+ const isDefaultMaxX = maxBounds.x === defaultMaxBounds.x;
+ const isDefaultMaxY = maxBounds.y === defaultMaxBounds.y;
+ const isDefaultMaxZ = maxBounds.z === defaultMaxBounds.z;
+
+ if (
+ !isDefaultMinX ||
+ !isDefaultMinY ||
+ !isDefaultMinZ ||
+ !isDefaultMaxX ||
+ !isDefaultMaxY ||
+ !isDefaultMaxZ
+ ) {
+ shaderBuilder.addDefine("BOUNDS", undefined, ShaderDestination.FRAGMENT);
+ }
+
+ if (!isDefaultMinX) {
+ shaderBuilder.addDefine(
+ "BOUNDS_0_MIN",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+ if (!isDefaultMaxX) {
+ shaderBuilder.addDefine(
+ "BOUNDS_0_MAX",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
+ if (!isDefaultMinY) {
+ shaderBuilder.addDefine(
+ "BOUNDS_1_MIN",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+ if (!isDefaultMaxY) {
+ shaderBuilder.addDefine(
+ "BOUNDS_1_MAX",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
+ if (!isDefaultMinZ) {
+ shaderBuilder.addDefine(
+ "BOUNDS_2_MIN",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+ if (!isDefaultMaxZ) {
+ shaderBuilder.addDefine(
+ "BOUNDS_2_MAX",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
+ let intersectionCount = 0;
+ if (shapeType === VoxelShapeType.BOX) {
+ // A bounded box is still a box, so it has the same number of shape intersections: 1
+ intersectionCount = 1;
+ } else if (shapeType === VoxelShapeType.ELLIPSOID) {
+ // Intersects an outer ellipsoid for the max radius
+ {
+ shaderBuilder.addDefine(
+ "BOUNDS_2_MAX_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+
+ // Intersects an inner ellipsoid for the min radius
+ if (!isDefaultMinZ) {
+ shaderBuilder.addDefine(
+ "BOUNDS_2_MIN_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+
+ // Intersects a cone for min latitude
+ if (!isDefaultMinY) {
+ shaderBuilder.addDefine(
+ "BOUNDS_1_MIN_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+
+ // Intersects a cone for max latitude
+ if (!isDefaultMaxY) {
+ shaderBuilder.addDefine(
+ "BOUNDS_1_MAX_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+
+ // Intersects a wedge for the min and max longitude
+ if (!isDefaultMinX || !isDefaultMaxX) {
+ shaderBuilder.addDefine(
+ "BOUNDS_0_MIN_MAX_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+ } else if (shapeType === VoxelShapeType.CYLINDER) {
+ // Intersects a capped cylinder for the max radius
+ // The min and max height are handled as part of the capped cylinder intersection
+ {
+ shaderBuilder.addDefine(
+ "BOUNDS_0_MAX_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+
+ // Intersects an inner infinite cylinder for the min radius
+ if (!isDefaultMinX) {
+ shaderBuilder.addDefine(
+ "BOUNDS_0_MIN_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+
+ // Intersects a wedge for the min and max theta
+ if (!isDefaultMinZ || !isDefaultMaxZ) {
+ shaderBuilder.addDefine(
+ "BOUNDS_2_MIN_MAX_IDX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount++;
+ }
+ }
+
+ // The intersection count is multiplied by 2 because there is an enter and exit for each intersection
+ shaderBuilder.addDefine(
+ "SHAPE_INTERSECTION_COUNT",
+ intersectionCount * 2,
+ ShaderDestination.FRAGMENT
+ );
+
+ if (
+ minClippingBounds.x !== defaultMinBounds.x ||
+ minClippingBounds.y !== defaultMinBounds.y ||
+ minClippingBounds.z !== defaultMinBounds.z ||
+ maxClippingBounds.x !== defaultMaxBounds.x ||
+ maxClippingBounds.y !== defaultMaxBounds.y ||
+ maxClippingBounds.z !== defaultMaxBounds.z
+ ) {
+ shaderBuilder.addDefine(
+ "CLIPPING_BOUNDS",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
+ // clearAttributes function
+ {
+ const clearAttributesFunctionId = "clearAttributes";
+ const clearAttributesFunctionName = "clearAttributes";
+
+ shaderBuilder.addFunction(
+ clearAttributesFunctionId,
+ `${attributeStructName} ${clearAttributesFunctionName}()`,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
+ `${attributeStructName} attributes;`,
+ ]);
+
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const glslType = getGlslType(type, componentType);
+ shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
+ `attributes.${name} = ${glslType}(0.0);`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
+ `return attributes;`,
+ ]);
+ }
+
+ // sumAttributes function
+ {
+ const sumAttributesFunctionId = "sumAttributes";
+ const sumAttributesFunctionName = "sumAttributes";
+ const sumAttributesFunctionDeclaration = `${attributeStructName} ${sumAttributesFunctionName}(${attributeStructName} attributesA, ${attributeStructName} attributesB)`;
+
+ shaderBuilder.addFunction(
+ sumAttributesFunctionId,
+ sumAttributesFunctionDeclaration,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
+ `${attributeStructName} attributes;`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
+ `${attributeFieldName}.${name} = attributesA.${name} + attributesB.${name};`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
+ `return attributes;`,
+ ]);
+ }
+
+ // mixAttributes
+ {
+ const mixAttributesFunctionId = "mixAttributes";
+ const mixAttributesFunctionName = "mixAttributes";
+ const mixAttributesFieldAttributesA = "attributesA";
+ const mixAttributesFieldAttributesB = "attributesB";
+ const mixAttributesFieldMixAmount = "mixFactor";
+ shaderBuilder.addFunction(
+ mixAttributesFunctionId,
+ `${attributeStructName} ${mixAttributesFunctionName}(${attributeStructName} ${mixAttributesFieldAttributesA}, ${attributeStructName} ${mixAttributesFieldAttributesB}, float ${mixAttributesFieldMixAmount})`,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
+ `${attributeStructName} attributes;`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
+ `attributes.${name} = mix(${mixAttributesFieldAttributesA}.${name}, ${mixAttributesFieldAttributesB}.${name}, ${mixAttributesFieldMixAmount});`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
+ `return attributes;`,
+ ]);
+ }
+
+ // setMinMaxAttributes function
+ if (defined(minimumValues) && defined(maximumValues)) {
+ shaderBuilder.addDefine(
+ "HAS_MIN_MAX",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ const minMaxAttributesFunctionId = "setMinMaxAttributes";
+ const minMaxAttributesFunctionName = "setMinMaxAttributes";
+ shaderBuilder.addFunction(
+ minMaxAttributesFunctionId,
+ `void ${minMaxAttributesFunctionName}(inout ${voxelStructName} ${voxelFieldName})`,
+ ShaderDestination.FRAGMENT
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentCount = MetadataType.getComponentCount(type);
+ for (let j = 0; j < componentCount; j++) {
+ const minimumValue = minimumValues[i][j];
+ const maximumValue = maximumValues[i][j];
+
+ // glsl needs to have `.0` at the end of whole numbers floats.
+ let minimumValueString = minimumValue.toString();
+ if (minimumValueString.indexOf(".") === -1) {
+ minimumValueString = `${minimumValue}.0`;
+ }
+ let maximumValueString = maximumValue.toString();
+ if (maximumValueString.indexOf(".") === -1) {
+ maximumValueString = `${maximumValue}.0`;
+ }
+
+ const glslField = getGlslField(type, j);
+ const minLine = `${voxelFieldName}.${name}Minimum${glslField} = ${minimumValueString};`;
+ const maxLine = `${voxelFieldName}.${name}Maximum${glslField} = ${maximumValueString};`;
+ shaderBuilder.addFunctionLines(minMaxAttributesFunctionId, [
+ minLine,
+ maxLine,
+ ]);
+ }
+ }
+ }
+
+ // for (i = 0; i < propertiesLength; i++) {
+ // property = properties[i];
+ // type = property.type;
+ // name = property.name;
+ // channelCount = AttributeType.getNumberOfComponents(type);
+ // minValue = property.min;
+ // maxValue = property.max;
+ // sampleType = getStyleInputSampleType(property);
+ // sampleScale = getTypeScale(property);
+
+ // const mins = [];
+ // const maxs = [];
+
+ // if (channelCount === 1) {
+ // mins.push(`${sampleScale} * ${toTypedString(minValue, property)}`);
+ // maxs.push(`${sampleScale} * ${toTypedString(maxValue, property)}`);
+ // } else {
+ // mins.push(`${sampleScale} * ${toTypedString(minValue.x, property)}`);
+ // maxs.push(`${sampleScale} * ${toTypedString(maxValue.x, property)}`);
+
+ // mins.push(`${sampleScale} * ${toTypedString(minValue.y, property)}`);
+ // maxs.push(`${sampleScale} * ${toTypedString(maxValue.y, property)}`);
+
+ // if (channelCount >= 3) {
+ // mins.push(`${sampleScale} * ${toTypedString(minValue.z, property)}`);
+ // maxs.push(`${sampleScale} * ${toTypedString(maxValue.z, property)}`);
+ // }
+ // if (channelCount >= 4) {
+ // mins.push(`${sampleScale} * ${toTypedString(minValue.w, property)}`);
+ // maxs.push(`${sampleScale} * ${toTypedString(maxValue.w, property)}`);
+ // }
+ // }
+
+ // const minVec = `${sampleType}(${mins.join(", ")})`;
+ // const maxVec = `${sampleType}(${maxs.join(", ")})`;
+
+ // shaderBuilder.addFunctionLines("setMinMax", [
+ // `styleInput.${name}Minimum = ${minVec};`,
+ // `styleInput.${name}Maximum = ${maxVec};`,
+ // ]);
+ // }
+
+ // const styleShaderSource = primitive._styleMaterial.shaderSource;
+ // shaderBuilder.addDefine("STYLE_USE_NORMAL");
+ // if (styleShaderSource.includes("styleInput.positionEC")) {
+ // shaderBuilder.addDefine("STYLE_USE_POSITION_EC");
+ // }
+ // shaderBuilder.addFragmentLines([styleShaderSource]);
+
+ // Generate shaders
+ shaderBuilder.setPositionAttribute("vec4", "a_position");
+
+ // // set normals
+ // const setNormalsArgs = [
+ // `in vec3 normalLocal[${propertiesLength}]`,
+ // `in vec3 normalWorld[${propertiesLength}]`,
+ // `in vec3 normalView[${propertiesLength}]`,
+ // `in bool normalValid[${propertiesLength}]`,
+ // "inout StyleInput styleInput",
+ // ];
+
+ // const setNormalsSig = `void setStyleInputNormals(${setNormalsArgs.join(
+ // ", "
+ // )})`;
+
+ // shaderBuilder.addFunction(
+ // "setNormals",
+ // setNormalsSig,
+ // ShaderDestination.FRAGMENT
+ // );
+
+ // for (i = 0; i < propertiesLength; i++) {
+ // property = properties[i];
+ // name = property.name;
+ // shaderBuilder.addFunctionLines("setNormals", [
+ // `styleInput.${name}NormalLocal` + ` = normalLocal[${i}];`,
+ // `styleInput.${name}NormalWorld` + ` = normalWorld[${i}];`,
+ // `styleInput.${name}NormalView` + ` = normalView[${i}];`,
+ // `styleInput.${name}NormalValid` + ` = normalValid[${i}];`,
+ // ]);
+ // }
+
+ // // set samples
+ // const setSamplesFunctionId = "setSamples";
+ // const setSamplesDefinition = `void setSamples(in vec4 samples[${attributeLength}], inout ${CustomShaderPipelineStage.STRUCT_NAME_ATTRIBUTES} ${attributesFieldName})`;
+
+ // shaderBuilder.addFunction(
+ // setSamplesFunctionId,
+ // setSamplesDefinition,
+ // ShaderDestination.FRAGMENT
+ // );
+
+ // for (let i = 0; i < attributeLength; i++) {
+ // const name = names[i];
+ // // const scaleVar = `scale_${name}`;
+
+ // shaderBuilder.addFunctionLines(setSamplesFunctionId, [
+ // // `vec4 ${scaleVar} = vec4(${sampleScale});`,
+ // // `attributes.${name} = ${sampleType}(${scaleVar} * samples[${i}]${swizzler});`,
+ // `${attributesFieldName}.${name} = `,
+ // ]);
+ // }
+
+ // shaderBuilder.addFunction(
+ // "decodeSamples",
+ // "void decodeTextureSamples(inout vec4 samples[METADATA_COUNT])",
+ // ShaderDestination.FRAGMENT
+ // );
+
+ // shaderBuilder.addFunctionLines("decodeSamples", ["vec4 sample;"]);
+
+ // for (i = 0; i < propertiesLength; i++) {
+ // property = properties[i];
+ // type = property.type;
+ // channelCount = AttributeType.getNumberOfComponents(type);
+
+ // shaderBuilder.addFunctionLines("decodeSamples", [
+ // `sample = samples[${i}];`,
+ // ]);
+
+ // if (!defined(channelCount) || channelCount === 1) {
+ // shaderBuilder.addFunctionLines("decodeSamples", [
+ // "sample = vec4(1.0, 1.0, 1.0, sample.r);",
+ // ]);
+ // } else if (channelCount === 2) {
+ // shaderBuilder.addFunctionLines("decodeSamples", [
+ // "sample = vec4(sample.r, sample.a, 1.0, 1.0);",
+ // ]);
+ // }
+
+ // if (type === MetadataType.UINT8) {
+ // shaderBuilder.addFunctionLines("decodeSamples", [
+ // `sample = mix(u_minimumValues[${i}], u_maximumValues[${i}], sample);`,
+ // ]);
+ // }
+
+ // shaderBuilder.addFunctionLines("decodeSamples", [
+ // `samples[${i}] = sample;`,
+ // ]);
+ // }
+
+ // Looping over the sampler array was causing strange rendering artifacts even though the shader compiled fine.
+ // Unroling the for loop fixed the problem.
+ // for (int i = 0; i < METADATA_COUNT; i++)
+ // {
+ // vec4 value0 = texture2D(u_megatextureTextures[i], uv0);
+ // vec4 value1 = texture2D(u_megatextureTextures[i], uv1);
+ // samples[i] = mix(value0, value1, lerp);
+ // }
+
+ const sampleFrom2DMegatextureId = "sampleFrom2DMegatextureAtUv";
+ const sampleFrom2DMegatextureName = "sampleFrom2DMegatextureAtUv";
+ shaderBuilder.addFunction(
+ sampleFrom2DMegatextureId,
+ `${attributeStructName} ${sampleFrom2DMegatextureName}(vec2 uv)`,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
+ `${attributeStructName} attributes;`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const glslTextureSwizzle = getGlslTextureSwizzle(type, componentType);
+ shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
+ `attributes.${name} = texture2D(u_megatextureTextures[${i}], uv)${glslTextureSwizzle};`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
+ `return attributes;`,
+ ]);
+
+ // shaderBuilder.addFunctionLines("sampleUv", [
+ // "decodeTextureSamples(samples);",
+ // ]);
+
+ // const voxelPrimitive = renderResources.model.voxelPrimitive;
+ // addRuntimeVoxelProperties(shaderBuilder, voxelPrimitive);
+ // // primitive.update(frameState);
+ const shaderBuilderPick = shaderBuilder.clone();
+ shaderBuilderPick.addDefine("PICKING", undefined, ShaderDestination.FRAGMENT);
+
+ const shaderProgram = shaderBuilder.buildShaderProgram(context);
+ const shaderProgramPick = shaderBuilderPick.buildShaderProgram(context);
+
+ const renderState = RenderState.fromCache({
+ cull: {
+ enabled: true,
+ face: CullFace.BACK,
+ },
+ depthTest: {
+ enabled: false,
+ },
+ depthMask: false,
+ blending: BlendingState.ALPHA_BLEND,
+ });
+
+ // var baseUniformMap = that._uniformMap;
+ // var uniformMap = combine(baseUniformMap, styleUniformMap);
+ const uniformMap = that._uniformMap;
+
+ const viewportQuadVertexArray = context.getViewportQuadVertexArray();
+ const drawCommand = new DrawCommand({
+ vertexArray: viewportQuadVertexArray,
+ primitiveType: PrimitiveType.TRIANGLES,
+ renderState: renderState,
+ shaderProgram: shaderProgram,
+ uniformMap: uniformMap,
+ pass: Pass.VOXELS,
+ executeInClosestFrustum: true,
+ owner: this,
+ // boundingVolume: primitiveRenderResources.boundingSphere,
+ // modelMatrix: primitiveRenderResources.modelMatrix,
+ // debugShowBoundingVolume : true
+ });
+
+ const drawCommandPick = DrawCommand.shallowClone(
+ drawCommand,
+ new DrawCommand()
+ );
+ drawCommandPick.shaderProgram = shaderProgramPick;
+ drawCommandPick.pickOnly = true;
+
+ // Delete the old shader programs
+ if (defined(that._drawCommand)) {
+ const command = that._drawCommand;
+ command.shaderProgram =
+ command.shaderProgram && command.shaderProgram.destroy();
+ }
+ if (defined(that._drawCommandPick)) {
+ const command = that._drawCommandPick;
+ command.shaderProgram =
+ command.shaderProgram && command.shaderProgram.destroy();
+ }
+
+ that._drawCommand = drawCommand;
+ that._drawCommandPick = drawCommandPick;
+
+ console.log(drawCommand.shaderProgram._fragmentShaderText);
+}
+
+/**
+ * Returns true if this object was destroyed; otherwise, false.
+ *
+ * If this object was destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception.
+ *
+ * @returns {Boolean} true
if this object was destroyed; otherwise, false
.
+ *
+ * @see VoxelPrimitive#destroy
+ */
+VoxelPrimitive.prototype.isDestroyed = function () {
+ return false;
+};
+
+/**
+ * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
+ * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
+ *
+ * Once an object is destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
+ * assign the return value (undefined
) to the object as done in the example.
+ *
+ * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
+ *
+ * @example
+ * voxelPrimitive = voxelPrimitive && voxelPrimitive.destroy();
+ *
+ * @see VoxelPrimitive#isDestroyed
+ */
+VoxelPrimitive.prototype.destroy = function () {
+ const drawCommand = this._drawCommand;
+ if (defined(drawCommand)) {
+ drawCommand.shaderProgram =
+ drawCommand.shaderProgram && drawCommand.shaderProgram.destroy();
+ }
+ const drawCommandPick = this._drawCommandPick;
+ if (defined(drawCommandPick)) {
+ drawCommandPick.shaderProgram =
+ drawCommandPick.shaderProgram && drawCommandPick.shaderProgram.destroy();
+ }
+
+ this._pickId = this._pickId && this._pickId.destroy();
+
+ if (defined(this._traversal)) {
+ const megatextures = this._traversal.megatextures;
+ const length = megatextures.length;
+ for (let i = 0; i < length; i++) {
+ const megatexture = megatextures[i];
+ // TODO: when would megatexture not be defined?
+ if (defined(megatexture)) {
+ megatexture.destroy();
+ }
+ }
+ this._traversal = undefined;
+ }
+
+ // TODO: I assume the custom shader does not have to be destroyed
+
+ return destroyObject(this);
+};
+
+VoxelPrimitive.prototype.queryMetadataAtCartographic = function (
+ cartographic,
+ metadata
+) {
+ return this._traversal.getMetadataAtCartographic(cartographic, metadata);
+};
+
+VoxelPrimitive.prototype.queryMetadataAtCartesian = function (
+ cartesian,
+ metadata
+) {
+ return this._traversal.getMetadataAtWorldCartesian(cartesian, metadata);
+};
+
+const corners = new Array(
+ new Cartesian4(-1.0, -1.0, -1.0, 1.0),
+ new Cartesian4(+1.0, -1.0, -1.0, 1.0),
+ new Cartesian4(-1.0, +1.0, -1.0, 1.0),
+ new Cartesian4(+1.0, +1.0, -1.0, 1.0),
+ new Cartesian4(-1.0, -1.0, +1.0, 1.0),
+ new Cartesian4(+1.0, -1.0, +1.0, 1.0),
+ new Cartesian4(-1.0, +1.0, +1.0, 1.0),
+ new Cartesian4(+1.0, +1.0, +1.0, 1.0)
+);
+const vertexNeighborIndices = new Array(
+ 1,
+ 2,
+ 4,
+ 0,
+ 3,
+ 5,
+ 0,
+ 3,
+ 6,
+ 1,
+ 2,
+ 7,
+ 0,
+ 5,
+ 6,
+ 1,
+ 4,
+ 7,
+ 2,
+ 4,
+ 7,
+ 3,
+ 5,
+ 6
+);
+
+const scratchCornersClipSpace = new Array(
+ new Cartesian4(),
+ new Cartesian4(),
+ new Cartesian4(),
+ new Cartesian4(),
+ new Cartesian4(),
+ new Cartesian4(),
+ new Cartesian4(),
+ new Cartesian4()
+);
+
+/**
+ * Projects all 8 corners of the oriented bounding box to NDC space and finds the
+ * resulting NDC axis aligned bounding box. To avoid projecting a vertex that is
+ * behind the near plane, it uses the intersection point of each of the vertex's
+ * edges against the near plane as part of the AABB calculation. This is done in
+ * clip space prior to perspective division.
+ *
+ * @function
+ * @param {OrientedBoundingBox} orientedBoundingBox
+ * @param {Matrix4} worldToProjection
+ * @param {Cartesian4} result
+ * @returns {Cartesian4}
+ *
+ * @private
+ */
+function orientedBoundingBoxToNdcAabb(
+ orientedBoundingBox,
+ worldToProjection,
+ result
+) {
+ const transformPositionLocalToWorld = Matrix4.fromRotationTranslation(
+ orientedBoundingBox.halfAxes,
+ orientedBoundingBox.center,
+ scratchTransformPositionLocalToWorld
+ );
+ const transformPositionLocalToProjection = Matrix4.multiply(
+ worldToProjection,
+ transformPositionLocalToWorld,
+ scratchTransformPositionLocalToProjection
+ );
+
+ let ndcMinX = +Number.MAX_VALUE;
+ let ndcMaxX = -Number.MAX_VALUE;
+ let ndcMinY = +Number.MAX_VALUE;
+ let ndcMaxY = -Number.MAX_VALUE;
+ let cornerIndex;
+
+ // Convert all points to clip space
+ const cornersClipSpace = scratchCornersClipSpace;
+ const cornersLength = corners.length;
+ for (cornerIndex = 0; cornerIndex < cornersLength; cornerIndex++) {
+ Matrix4.multiplyByVector(
+ transformPositionLocalToProjection,
+ corners[cornerIndex],
+ cornersClipSpace[cornerIndex]
+ );
+ }
+
+ for (cornerIndex = 0; cornerIndex < cornersLength; cornerIndex++) {
+ const position = cornersClipSpace[cornerIndex];
+ if (position.z >= -position.w) {
+ // Position is past near plane, so there's no need to clip.
+ const ndcX = position.x / position.w;
+ const ndcY = position.y / position.w;
+ ndcMinX = Math.min(ndcMinX, ndcX);
+ ndcMaxX = Math.max(ndcMaxX, ndcX);
+ ndcMinY = Math.min(ndcMinY, ndcY);
+ ndcMaxY = Math.max(ndcMaxY, ndcY);
+ } else {
+ for (let neighborIndex = 0; neighborIndex < 3; neighborIndex++) {
+ const neighborVertexIndex =
+ vertexNeighborIndices[cornerIndex * 3 + neighborIndex];
+ const neighborPosition = cornersClipSpace[neighborVertexIndex];
+ if (neighborPosition.z >= -neighborPosition.w) {
+ // Position is behind the near plane and neighbor is after, so get intersection point on the near plane.
+ const distanceToPlaneFromPosition = position.z + position.w;
+ const distanceToPlaneFromNeighbor =
+ neighborPosition.z + neighborPosition.w;
+ const t =
+ distanceToPlaneFromPosition /
+ (distanceToPlaneFromPosition - distanceToPlaneFromNeighbor);
+
+ const intersect = Cartesian4.lerp(
+ position,
+ neighborPosition,
+ t,
+ scratchIntersect
+ );
+ const intersectNdcX = intersect.x / intersect.w;
+ const intersectNdcY = intersect.y / intersect.w;
+ ndcMinX = Math.min(ndcMinX, intersectNdcX);
+ ndcMaxX = Math.max(ndcMaxX, intersectNdcX);
+ ndcMinY = Math.min(ndcMinY, intersectNdcY);
+ ndcMaxY = Math.max(ndcMaxY, intersectNdcY);
+ }
+ }
+ }
+ }
+
+ // Clamp the NDC values to -1 to +1 range even if they extend much further.
+ ndcMinX = CesiumMath.clamp(ndcMinX, -1.0, +1.0);
+ ndcMinY = CesiumMath.clamp(ndcMinY, -1.0, +1.0);
+ ndcMaxX = CesiumMath.clamp(ndcMaxX, -1.0, +1.0);
+ ndcMaxY = CesiumMath.clamp(ndcMaxY, -1.0, +1.0);
+ result = Cartesian4.fromElements(ndcMinX, ndcMinY, ndcMaxX, ndcMaxY, result);
+
+ return result;
+}
+
+const colorRed = new Color(1.0, 0.0, 0.0);
+const colorGreen = new Color(0.0, 1.0, 0.0);
+const colorBlue = new Color(0.0, 0.0, 1.0);
+
+const polylineAxisDistance = 30000000.0;
+const polylineXAxis = new Cartesian3(polylineAxisDistance, 0.0, 0.0);
+const polylineYAxis = new Cartesian3(0.0, polylineAxisDistance, 0.0);
+const polylineZAxis = new Cartesian3(0.0, 0.0, polylineAxisDistance);
+
+/**
+ * @ignore
+ * @param {VoxelPrimitive} that
+ * @param {FrameState} frameState
+ */
+function debugDraw(that, frameState) {
+ const traversal = that._traversal;
+ const polylines = that._debugPolylines;
+ const shapeVisible = that._shape.isVisible;
+ polylines.removeAll();
+
+ function makePolylineLineSegment(startPos, endPos, color, thickness) {
+ polylines.add({
+ positions: [startPos, endPos],
+ width: thickness,
+ material: Material.fromType("Color", {
+ color: color,
+ }),
+ });
+ }
+
+ function makePolylineBox(orientedBoundingBox, color, thickness) {
+ // Normally would want to use a scratch variable to store the corners, but
+ // polylines don't clone the positions.
+ const corners = orientedBoundingBox.computeCorners();
+ makePolylineLineSegment(corners[0], corners[1], color, thickness);
+ makePolylineLineSegment(corners[2], corners[3], color, thickness);
+ makePolylineLineSegment(corners[4], corners[5], color, thickness);
+ makePolylineLineSegment(corners[6], corners[7], color, thickness);
+ makePolylineLineSegment(corners[0], corners[2], color, thickness);
+ makePolylineLineSegment(corners[4], corners[6], color, thickness);
+ makePolylineLineSegment(corners[1], corners[3], color, thickness);
+ makePolylineLineSegment(corners[5], corners[7], color, thickness);
+ makePolylineLineSegment(corners[0], corners[4], color, thickness);
+ makePolylineLineSegment(corners[2], corners[6], color, thickness);
+ makePolylineLineSegment(corners[1], corners[5], color, thickness);
+ makePolylineLineSegment(corners[3], corners[7], color, thickness);
+ }
+
+ function drawTile(tile) {
+ const frameNumber = traversal.frameNumber;
+ if (!tile.isRenderable(frameNumber)) {
+ return;
+ }
+
+ const level = tile.level;
+ const startThickness = 5.0;
+ const thickness = Math.max(1.0, startThickness / Math.pow(2.0, level));
+ const colors = [colorRed, colorGreen, colorBlue];
+ const color = colors[level % 3];
+
+ makePolylineBox(tile.orientedBoundingBox, color, thickness);
+
+ if (defined(tile.children)) {
+ for (let i = 0; i < 8; i++) {
+ drawTile(tile.children[i]);
+ }
+ }
+ }
+
+ if (shapeVisible) {
+ drawTile(traversal.rootNode);
+ }
+
+ const axisThickness = 10.0;
+ makePolylineLineSegment(
+ Cartesian3.ZERO,
+ polylineXAxis,
+ colorRed,
+ axisThickness
+ );
+ makePolylineLineSegment(
+ Cartesian3.ZERO,
+ polylineYAxis,
+ colorGreen,
+ axisThickness
+ );
+ makePolylineLineSegment(
+ Cartesian3.ZERO,
+ polylineZAxis,
+ colorBlue,
+ axisThickness
+ );
+
+ polylines.update(frameState);
+}
+
+/**
+ * The default custom shader used by the primitive.
+ * @private
+ * @type {CustomShader}
+ */
+VoxelPrimitive.DefaultCustomShader = new CustomShader({
+ // TODO what should happen when lightingModel undefined?
+ // lightingModel: Cesium.LightingModel.UNLIT,
+ fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
+{
+ material.diffuse = vec3(1.0);
+ material.alpha = 1.0;
+}`,
+});
+
+export default VoxelPrimitive;
diff --git a/Source/Scene/VoxelProvider.js b/Source/Scene/VoxelProvider.js
new file mode 100644
index 00000000000..ece761b2dce
--- /dev/null
+++ b/Source/Scene/VoxelProvider.js
@@ -0,0 +1,191 @@
+import DeveloperError from "../Core/DeveloperError.js";
+
+/**
+ * Provides voxel data. Intended to be used with {@link VoxelPrimitive}.
+ * This type describes an interface and is not intended to be instantiated directly.
+ *
+ * @alias VoxelProvider
+ * @constructor
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @see Cesium3DTilesVoxelProvider
+ * @see GltfVoxelProvider
+ * @see VoxelPrimitive
+ * @see VoxelShapeType
+ */
+function VoxelProvider() {
+ DeveloperError.throwInstantiationError();
+}
+
+Object.defineProperties(VoxelProvider.prototype, {
+ /**
+ * Gets a value indicating whether or not the provider is ready for use.
+ * @type {Boolean}
+ * @readonly
+ */
+ ready: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the promise that will be resolved when the provider is ready for use.
+ * @type {Promise.}
+ * @readonly
+ */
+ readyPromise: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * A model matrix that is applied to all tiles. If undefined, the identity matrix will be used instead.
+ * @type {Matrix4}
+ * @readonly
+ */
+ modelMatrix: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the {@link VoxelShapeType}
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {VoxelShapeType}
+ * @readonly
+ */
+ shape: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the minimum bounds.
+ * If undefined, the shape's default minimum bounds will be used instead.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ minBounds: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the maximum bounds.
+ * If undefined, the shape's default maximum bounds will be used instead.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ maxBounds: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ dimensions: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the number of padding voxels before the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage. If
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ paddingBefore: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the number of padding voxels after the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage. If
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Cartesian3}
+ * @readonly
+ */
+ paddingAfter: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the metadata names.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {String[]}
+ */
+ names: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the metadata types
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {MetadataType[]}
+ */
+ types: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the metadata component types
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {MetadataComponentType[]}
+ */
+ componentTypes: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the metadata minimum values.
+ * @type {Number[]}
+ */
+ minimumValues: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the metadata maximum values.
+ * @type {Number[]}
+ */
+ maximumValues: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be undefined.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ * @type {Number}
+ */
+ maximumTileCount: {
+ get: DeveloperError.throwInstantiationError,
+ },
+});
+
+/**
+ * A hook to update the provider every frame, called from {@link VoxelPrimitive.update}.
+ * If the provider doesn't need this functionality it should leave this function undefined.
+ * @function
+ * @param {FrameState} frameState
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelProvider.prototype.update = DeveloperError.throwInstantiationError;
+
+/**
+ * Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z.
+ * This function should not be called before {@link VoxelProvider#ready} returns true.
+ * @function
+ * @param {Object} [options] Object with the following properties:
+ * @param {Number} [options.tileLevel=0] The tile's level.
+ * @param {Number} [options.tileX=0] The tile's X coordinate.
+ * @param {Number} [options.tileY=0] The tile's Y coordinate.
+ * @param {Number} [options.tileZ=0] The tile's Z coordinate.
+ * @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelProvider.prototype.requestData = DeveloperError.throwInstantiationError;
+
+export default VoxelProvider;
diff --git a/Source/Scene/VoxelShape.js b/Source/Scene/VoxelShape.js
new file mode 100644
index 00000000000..6588258a7db
--- /dev/null
+++ b/Source/Scene/VoxelShape.js
@@ -0,0 +1,129 @@
+import DeveloperError from "../Core/DeveloperError.js";
+
+/**
+ * Controls per-shape behavior for culling and rendering voxel grids.
+ * This type describes an interface and is not intended to be instantiated directly.
+ *
+ * @alias VoxelShape
+ * @constructor
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ *
+ * @see VoxelBoxShape
+ * @see VoxelEllipsoidShape
+ * @see VoxelCylinderShape
+ * @see VoxelShapeType
+ */
+function VoxelShape() {
+ DeveloperError.throwInstantiationError();
+}
+
+Object.defineProperties(VoxelShape.prototype, {
+ /**
+ * An oriented bounding box containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {OrientedBoundingBox}
+ */
+ orientedBoundingBox: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * A bounding sphere containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {BoundingSphere}
+ */
+ boundingSphere: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * A transformation matrix containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ */
+ boundTransform: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * A transformation matrix containing the shape, ignoring the bounds.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ */
+ shapeTransform: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
+ * The update function must be called before accessing this value.
+ * @type {Boolean}
+ */
+ isVisible: {
+ get: DeveloperError.throwInstantiationError,
+ },
+});
+
+/**
+ * Update the shape's state.
+ * @function
+ * @param {Matrix4} modelMatrix The model matrix.
+ * @param {Cartesian3} minBounds The minimum bounds.
+ * @param {Cartesian3} maxBounds The maximum bounds.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelShape.prototype.update = DeveloperError.throwInstantiationError;
+
+/**
+ * Computes an oriented bounding box for a specified tile.
+ * The update function must be called before calling this function.
+ * @function
+ * @param {Number} tileLevel The tile's level.
+ * @param {Number} tileX The tile's x coordinate.
+ * @param {Number} tileY The tile's y coordinate.
+ * @param {Number} tileZ The tile's z coordinate.
+ * @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile.
+ * @returns {OrientedBoundingBox} The oriented bounding box.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelShape.prototype.computeOrientedBoundingBoxForTile =
+ DeveloperError.throwInstantiationError;
+
+/**
+ * Computes an approximate step size for raymarching the root tile of a voxel grid.
+ * The update function must be called before calling this function.
+ * @function
+ * @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
+ * @returns {Number} The step size.
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelShape.prototype.computeApproximateStepSize =
+ DeveloperError.throwInstantiationError;
+
+/**
+ * Defines the minimum bounds of the shape. This can vary per-shape.
+ * @type {Cartesian3}
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelShape.DefaultMinBounds = DeveloperError.throwInstantiationError;
+
+/**
+ * Defines the maximum bounds of the shape. This can vary per-shape.
+ * @type {Cartesian3}
+ *
+ * @private
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ */
+VoxelShape.DefaultMaxBounds = DeveloperError.throwInstantiationError;
+
+export default VoxelShape;
diff --git a/Source/Scene/VoxelShapeType.js b/Source/Scene/VoxelShapeType.js
new file mode 100644
index 00000000000..8b0c4f6a515
--- /dev/null
+++ b/Source/Scene/VoxelShapeType.js
@@ -0,0 +1,90 @@
+import DeveloperError from "../Core/DeveloperError.js";
+import PrimitiveType from "../Core/PrimitiveType.js";
+import VoxelBoxShape from "./VoxelBoxShape.js";
+import VoxelCylinderShape from "./VoxelCylinderShape.js";
+import VoxelEllipsoidShape from "./VoxelEllipsoidShape.js";
+
+/**
+ * An enum of voxel shapes. The shape controls how the voxel grid is mapped to
+ * 3D space.
+ *
+ * @enum VoxelShapeType
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ * @see VoxelShape
+ * @see VoxelBoxShape
+ * @see VoxelEllipsoidShape
+ * @see VoxelCylinderShape
+ */
+const VoxelShapeType = {
+ /**
+ * A box shape.
+ *
+ * @type {String}
+ * @constant
+ * @private
+ */
+ BOX: "BOX",
+ /**
+ * An ellipsoid shape.
+ *
+ * @type {String}
+ * @constant
+ * @private
+ */
+ ELLIPSOID: "ELLIPSOID",
+ /**
+ * A cylinder shape.
+ *
+ * @type {String}
+ * @constant
+ * @private
+ */
+ CYLINDER: "CYLINDER",
+};
+
+/**
+ * Converts a primitive type to a voxel shape. glTF voxel primitive types are
+ * defined in EXT_primitive_voxels.
+ * @param {PrimitiveType} primitiveType The primitive type.
+ * @returns {VoxelShapeType} The shape type.
+ * @private
+ */
+VoxelShapeType.fromPrimitiveType = function (primitiveType) {
+ switch (primitiveType) {
+ case PrimitiveType.VOXEL_BOX:
+ return VoxelShapeType.BOX;
+ case PrimitiveType.VOXEL_CYLINDER:
+ return VoxelShapeType.CYLINDER;
+ case PrimitiveType.VOXEL_ELLIPSOID:
+ return VoxelShapeType.ELLIPSOID;
+ //>>includeStart('debug', pragmas.debug);
+ default:
+ throw new DeveloperError(`Invalid primitive type ${primitiveType}`);
+ //>>includeEnd('debug');
+ }
+};
+
+/**
+ * Converts a shape type to a constructor that can be used to create a shape
+ * object or get per-shape properties like DefaultMinBounds and
+ * DefaultMaxBounds.
+ * @param {VoxelShapeType} shapeType The shape type.
+ * @returns {Function} The shape's constructor.
+ * @private
+ */
+VoxelShapeType.toShapeConstructor = function (shapeType) {
+ switch (shapeType) {
+ case VoxelShapeType.BOX:
+ return VoxelBoxShape;
+ case VoxelShapeType.CYLINDER:
+ return VoxelCylinderShape;
+ case VoxelShapeType.ELLIPSOID:
+ return VoxelEllipsoidShape;
+ //>>includeStart('debug', pragmas.debug);
+ default:
+ throw new DeveloperError(`Invalid shape type ${shapeType}`);
+ //>>includeEnd('debug');
+ }
+};
+
+export default Object.freeze(VoxelShapeType);
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
new file mode 100644
index 00000000000..320b75538ad
--- /dev/null
+++ b/Source/Scene/VoxelTraversal.js
@@ -0,0 +1,2020 @@
+import binarySearch from "../Core/binarySearch.js";
+import Cartesian2 from "../Core/Cartesian2.js";
+import Cartesian3 from "../Core/Cartesian3.js";
+import CesiumMath from "../Core/Math.js";
+import CullingVolume from "../Core/CullingVolume.js";
+import defaultValue from "../Core/defaultValue.js";
+import defined from "../Core/defined.js";
+import destroyObject from "../Core/destroyObject.js";
+import DeveloperError from "../Core/DeveloperError.js";
+import DoubleEndedPriorityQueue from "../Core/DoubleEndedPriorityQueue.js";
+import getTimestamp from "../Core/getTimestamp.js";
+import Matrix3 from "../Core/Matrix3.js";
+import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
+import PixelFormat from "../Core/PixelFormat.js";
+import RuntimeError from "../Core/RuntimeError.js";
+import ContextLimits from "../Renderer/ContextLimits.js";
+import PixelDatatype from "../Renderer/PixelDatatype.js";
+import Sampler from "../Renderer/Sampler.js";
+import Texture from "../Renderer/Texture.js";
+import TextureWrap from "../Renderer/TextureWrap.js";
+import TextureMagnificationFilter from "../Renderer/TextureMagnificationFilter.js";
+import TextureMinificationFilter from "../Renderer/TextureMinificationFilter.js";
+import MetadataType from "./MetadataType.js";
+import MetadataComponentType from "./MetadataComponentType.js";
+
+/**
+ * Handles tileset traversal, tile requests, and GPU resources. Intended to be
+ * private and paired with a {@link VoxelPrimitive}, which has a user-facing API.
+ * @alias VoxelTraversal
+ * @constructor
+ *
+ * @param {VoxelPrimitive} primitive
+ * @param {Context} context
+ * @param {Cartesian3} dimensions
+ * @param {MetadataType[]} types
+ * @param {MetadataComponentType[]} componentTypes
+ * @param {Number} keyframeCount
+ * @param {Number} [maximumTextureMemoryByteLength]
+ *
+ * @private
+ */
+function VoxelTraversal(
+ primitive,
+ context,
+ dimensions,
+ types,
+ componentTypes,
+ keyframeCount,
+ maximumTextureMemoryByteLength
+) {
+ /**
+ * @private
+ * @type {VoxelPrimitive}
+ */
+ this.primitive = primitive;
+
+ const length = types.length;
+
+ /**
+ * @private
+ * @type {Megatexture[]}
+ */
+ this.megatextures = new Array(length);
+
+ // TODO make sure to split the maximumTextureMemoryByteLength across all the megatextures
+ for (let i = 0; i < length; i++) {
+ const type = types[i];
+ const componentCount = MetadataType.getComponentCount(type);
+ const componentType = componentTypes[i];
+
+ this.megatextures[i] = new Megatexture(
+ context,
+ dimensions,
+ componentCount,
+ componentType,
+ maximumTextureMemoryByteLength
+ );
+ }
+
+ const maximumTileCount = this.megatextures[0].maximumTileCount;
+
+ /**
+ * @private
+ * @type {Number}
+ */
+ this.simultaneousRequestCount = 0;
+
+ /**
+ * @private
+ * @type {Boolean}
+ */
+ this.debugPrint = false;
+
+ const shape = primitive._shape;
+ const rootLevel = 0;
+ const rootX = 0;
+ const rootY = 0;
+ const rootZ = 0;
+ const rootParent = undefined;
+
+ /**
+ * @private
+ * @type {SpatialNode}
+ */
+ this.rootNode = new SpatialNode(
+ rootLevel,
+ rootX,
+ rootY,
+ rootZ,
+ rootParent,
+ shape,
+ dimensions
+ );
+
+ /**
+ * @private
+ * @type {DoubleEndedPriorityQueue}
+ */
+ this.priorityQueue = new DoubleEndedPriorityQueue({
+ maximumLength: maximumTileCount,
+ comparator: KeyframeNode.priorityComparator,
+ });
+
+ /**
+ * @private
+ * @type {KeyframeNode[]}
+ */
+ this.highPriorityKeyframeNodes = new Array(maximumTileCount);
+
+ /**
+ * @private
+ * @type {KeyframeNode[]}
+ */
+ this.keyframeNodesInMegatexture = new Array(maximumTileCount);
+
+ /**
+ * @private
+ * @type {Number}
+ */
+ this.keyframeCount = keyframeCount;
+
+ /**
+ * @private
+ * @type {Number}
+ */
+ this.keyframeLocation = 0;
+
+ /**
+ * @private
+ * @type {Number}
+ */
+ this.frameNumber = -1;
+
+ function binaryTreeWeightingRecursive(arr, start, end, depth) {
+ if (start > end) {
+ return;
+ }
+ const mid = Math.floor((start + end) / 2);
+ arr[mid] = depth;
+ binaryTreeWeightingRecursive(arr, start, mid - 1, depth + 1);
+ binaryTreeWeightingRecursive(arr, mid + 1, end, depth + 1);
+ }
+ this.binaryTreeKeyframeWeighting = new Array(keyframeCount);
+ this.binaryTreeKeyframeWeighting[0] = 0;
+ this.binaryTreeKeyframeWeighting[keyframeCount - 1] = 0;
+ binaryTreeWeightingRecursive(
+ this.binaryTreeKeyframeWeighting,
+ 1,
+ keyframeCount - 2,
+ 0
+ );
+
+ const internalNodeTexelCount = 9;
+ const internalNodeTextureDimensionX = 1024;
+ const internalNodeTilesPerRow = Math.floor(
+ internalNodeTextureDimensionX / internalNodeTexelCount
+ );
+ const internalNodeTextureDimensionY = Math.ceil(
+ maximumTileCount / internalNodeTilesPerRow
+ );
+
+ this.internalNodeTexture = new Texture({
+ context: context,
+ pixelFormat: PixelFormat.RGBA,
+ pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
+ flipY: false,
+ width: internalNodeTextureDimensionX,
+ height: internalNodeTextureDimensionY,
+ sampler: new Sampler({
+ minificationFilter: TextureMinificationFilter.NEAREST,
+ magnificationFilter: TextureMagnificationFilter.NEAREST,
+ }),
+ });
+ this.internalNodeTexelSizeUv = new Cartesian2(
+ 1.0 / internalNodeTextureDimensionX,
+ 1.0 / internalNodeTextureDimensionY
+ );
+ this.internalNodeTilesPerRow = internalNodeTilesPerRow;
+
+ this.useLeafNodeTexture = keyframeCount > 1;
+ if (this.useLeafNodeTexture) {
+ const leafNodeTexelCount = 2;
+ const leafNodeTextureDimensionX = 1024;
+ const leafNodeTilesPerRow = Math.floor(
+ leafNodeTextureDimensionX / leafNodeTexelCount
+ );
+ const leafNodeTextureDimensionY = Math.ceil(
+ maximumTileCount / leafNodeTilesPerRow
+ );
+
+ this.leafNodeTexture = new Texture({
+ context: context,
+ pixelFormat: PixelFormat.RGBA,
+ pixelDatatype: PixelDatatype.UNSIGNED_BYTE,
+ flipY: false,
+ width: leafNodeTextureDimensionX,
+ height: leafNodeTextureDimensionY,
+ sampler: new Sampler({
+ minificationFilter: TextureMinificationFilter.NEAREST,
+ magnificationFilter: TextureMagnificationFilter.NEAREST,
+ }),
+ });
+ this.leafNodeTexelSizeUv = new Cartesian2(
+ 1.0 / leafNodeTextureDimensionX,
+ 1.0 / leafNodeTextureDimensionY
+ );
+ this.leafNodeTilesPerRow = leafNodeTilesPerRow;
+ } else {
+ this.leafNodeTexture = undefined;
+ this.leafNodeTexelSizeUv = undefined;
+ this.leafNodeTilesPerRow = undefined;
+ }
+}
+
+VoxelTraversal.simultaneousRequestCountMaximum = 50;
+
+/**
+ * @private
+ * @param {FrameState} frameState
+ * @param {Number} keyframeLocation
+ * @param {Boolean} recomputeBoundingVolumes
+ * @param {Boolean} pauseUpdate
+ */
+VoxelTraversal.prototype.update = function (
+ frameState,
+ keyframeLocation,
+ recomputeBoundingVolumes,
+ pauseUpdate
+) {
+ const that = this;
+
+ const keyframeCount = that.keyframeCount;
+ that.keyframeLocation = CesiumMath.clamp(
+ keyframeLocation,
+ 0.0,
+ keyframeCount - 1
+ );
+
+ if (recomputeBoundingVolumes) {
+ recomputeBoundingVolumesRecursive(that, that.rootNode);
+ }
+
+ if (!pauseUpdate) {
+ that.frameNumber = frameState.frameNumber;
+
+ const timestamp0 = getTimestamp();
+ loadAndUnload(that, frameState);
+ const timestamp1 = getTimestamp();
+ generateOctree(that);
+ const timestamp2 = getTimestamp();
+
+ const debugStatistics = that.debugPrint;
+ if (debugStatistics) {
+ const loadAndUnloadTimeMs = timestamp1 - timestamp0;
+ const generateOctreeTimeMs = timestamp2 - timestamp1;
+ const totalTimeMs = timestamp2 - timestamp0;
+ printDebugInformation(
+ that,
+ loadAndUnloadTimeMs,
+ generateOctreeTimeMs,
+ totalTimeMs
+ );
+ }
+ }
+};
+
+/**
+ * @ignore
+ * @param {VoxelTraversal} that
+ * @param {SpatialNode} node
+ */
+function recomputeBoundingVolumesRecursive(that, node) {
+ const shape = that.primitive._shape;
+ const dimensions = that.primitive._provider.dimensions;
+ node.computeBoundingVolumes(shape, dimensions);
+ if (defined(node.children)) {
+ for (let i = 0; i < 8; i++) {
+ const child = node.children[i];
+ recomputeBoundingVolumesRecursive(that, child);
+ }
+ }
+}
+
+/**
+ * Call requestData for each metadata
+ * @ignore
+ * @param {VoxelTraversal} that
+ * @param {KeyframeNode} keyframeNode
+ */
+function requestTiles(that, keyframeNode) {
+ const keys = Object.keys(that.megatextures);
+ const length = keys.length;
+ for (let i = 0; i < length; i++) {
+ const metadataName = keys[i];
+ requestData(that, keyframeNode, metadataName);
+ }
+}
+/**
+ * @ignore
+ * @param {VoxelTraversal} that
+ * @param {KeyframeNode} keyframeNode
+ * @param {String} metadataName
+ */
+function requestData(that, keyframeNode, metadataName) {
+ if (
+ that.simultaneousRequestCount >=
+ VoxelTraversal.simultaneousRequestCountMaximum
+ ) {
+ return;
+ }
+
+ const primitive = that.primitive;
+ const provider = primitive._provider;
+ const keyframe = keyframeNode.keyframe;
+ const spatialNode = keyframeNode.spatialNode;
+ const tileLevel = spatialNode.level;
+ const tileX = spatialNode.x;
+ const tileY = spatialNode.y;
+ const tileZ = spatialNode.z;
+
+ const postRequestSuccess = function (result) {
+ that.simultaneousRequestCount--;
+ const length = primitive._provider.types.length;
+
+ if (!defined(result)) {
+ keyframeNode.state = VoxelTraversal.LoadState.UNAVAILABLE;
+ } else if (result === VoxelTraversal.LoadState.FAILED) {
+ keyframeNode.state = VoxelTraversal.LoadState.FAILED;
+ } else if (!Array.isArray(result) || result.length !== length) {
+ // TODO should this throw runtime error?
+ keyframeNode.state = VoxelTraversal.LoadState.FAILED;
+ } else {
+ const megatextures = that.megatextures;
+ for (let i = 0; i < length; i++) {
+ const megatexture = megatextures[i];
+ const tileVoxelCount =
+ megatexture.voxelCountPerTile.x *
+ megatexture.voxelCountPerTile.y *
+ megatexture.voxelCountPerTile.z;
+
+ const data = result[i];
+ const expectedLength = tileVoxelCount * megatexture.channelCount;
+ if (data.length === expectedLength) {
+ keyframeNode.metadatas[i] = data;
+ // State is received only when all metadata requests have been received
+ keyframeNode.state = VoxelTraversal.LoadState.RECEIVED;
+ } else {
+ keyframeNode.state = VoxelTraversal.LoadState.FAILED;
+ break;
+ }
+ }
+ }
+ };
+
+ const postRequestFailure = function () {
+ that.simultaneousRequestCount--;
+ keyframeNode.state = VoxelTraversal.LoadState.FAILED;
+ };
+
+ const promise = provider.requestData({
+ tileLevel: tileLevel,
+ tileX: tileX,
+ tileY: tileY,
+ tileZ: tileZ,
+ keyframe: keyframe,
+ metadataName: metadataName,
+ });
+
+ if (defined(promise)) {
+ that.simultaneousRequestCount++;
+ keyframeNode.state = VoxelTraversal.LoadState.RECEIVING;
+ promise.then(postRequestSuccess).catch(postRequestFailure);
+ } else {
+ keyframeNode.state = VoxelTraversal.LoadState.FAILED;
+ }
+}
+
+/**
+ * @ignore
+ * @param {Number} x
+ * @returns {Number}
+ */
+function mapInfiniteRangeToZeroOne(x) {
+ return x / (1.0 + x);
+}
+
+/**
+ * @ignore
+ * @param {VoxelTraversal} that
+ * @param {FrameState} frameState
+ */
+function loadAndUnload(that, frameState) {
+ const frameNumber = that.frameNumber;
+ const primitive = that.primitive;
+ const shape = primitive._shape;
+ const voxelDimensions = primitive._provider.dimensions;
+ const targetScreenSpaceError = primitive._screenSpaceError;
+ const priorityQueue = that.priorityQueue;
+ const keyframeLocation = that.keyframeLocation;
+ const keyframeCount = that.keyframeCount;
+ const rootNode = that.rootNode;
+
+ const cameraPosition = frameState.camera.positionWC;
+ const screenSpaceErrorDenominator = frameState.camera.frustum.sseDenominator;
+ const screenHeight =
+ frameState.context.drawingBufferHeight / frameState.pixelRatio;
+ const screenSpaceErrorMultiplier = screenHeight / screenSpaceErrorDenominator;
+
+ /**
+ * @ignore
+ * @param {SpatialNode} spatialNode
+ * @param {Number} visibilityPlaneMask
+ */
+ function addToQueueRecursive(spatialNode, visibilityPlaneMask) {
+ spatialNode.computeScreenSpaceError(
+ cameraPosition,
+ screenSpaceErrorMultiplier
+ );
+
+ visibilityPlaneMask = spatialNode.visibility(
+ frameState,
+ visibilityPlaneMask
+ );
+ if (visibilityPlaneMask === CullingVolume.MASK_OUTSIDE) {
+ return;
+ }
+ spatialNode.visitedFrameNumber = frameNumber;
+
+ const previousKeyframe = CesiumMath.clamp(
+ Math.floor(keyframeLocation),
+ 0,
+ keyframeCount - 2
+ );
+ const nextKeyframe = previousKeyframe + 1;
+
+ // Create keyframe nodes at the playhead.
+ // If they already exist, nothing will be created.
+ if (keyframeCount === 1) {
+ spatialNode.createKeyframeNode(0);
+ } else {
+ // // Always keep two keyframes loaded even if the playhead is directly on a keyframe.
+ // spatialNode.createKeyframeNode(previousKeyframe);
+ // spatialNode.createKeyframeNode(nextKeyframe);
+
+ // Create all keyframes
+ // eslint-disable-next-line no-lonely-if
+ if (spatialNode.keyframeNodes.length !== keyframeCount) {
+ for (let k = 0; k < keyframeCount; k++) {
+ spatialNode.createKeyframeNode(k);
+ }
+ }
+ }
+ const ssePriority = mapInfiniteRangeToZeroOne(spatialNode.screenSpaceError);
+
+ let hasLoadedKeyframe = false;
+ const keyframeNodes = spatialNode.keyframeNodes;
+ for (
+ let keyframeIndex = 0;
+ keyframeIndex < keyframeNodes.length;
+ keyframeIndex++
+ ) {
+ const keyframeNode = keyframeNodes[keyframeIndex];
+ const keyframe = keyframeNode.keyframe;
+
+ // // Prioritize all keyframes equally
+ // keyframeNode.priority = ssePriority;
+
+ // // Prioritize ONLY keyframes adjacent to the playhead
+ // keyframeNode.priority = ssePriority;
+ // if (keyframe !== previousKeyframe && keyframe !== nextKeyframe) {
+ // keyframeNode.priority = -Number.MAX_VALUE;
+ // }
+
+ // // Prioritize keyframes closest to the playhead
+ // keyframeNode.priority = ssePriority;
+ // const keyframeDifference = Math.min(Math.abs(keyframe - previousKeyframe), Math.abs(keyframe - nextKeyframe));
+ // keyframeNode.priority -= keyframeDifference;
+
+ // Balanced prioritization
+ const keyframeDifference = Math.min(
+ Math.abs(keyframe - previousKeyframe),
+ Math.abs(keyframe - nextKeyframe)
+ );
+ const maxKeyframeDifference = Math.max(
+ previousKeyframe,
+ keyframeCount - nextKeyframe - 1,
+ 1
+ );
+ const keyframeFactor = Math.pow(
+ 1.0 - keyframeDifference / maxKeyframeDifference,
+ 4.0
+ );
+ const binaryTreeFactor = Math.exp(
+ -that.binaryTreeKeyframeWeighting[keyframe]
+ );
+ keyframeNode.priority = 10.0 * ssePriority;
+ keyframeNode.priority += CesiumMath.lerp(
+ binaryTreeFactor,
+ keyframeFactor,
+ 0.15 + 0.85 * keyframeFactor
+ );
+
+ if (
+ keyframeNode.state !== VoxelTraversal.LoadState.UNAVAILABLE &&
+ keyframeNode.state !== VoxelTraversal.LoadState.FAILED &&
+ keyframeNode.priority !== -Number.MAX_VALUE
+ ) {
+ priorityQueue.insert(keyframeNode);
+ }
+ if (keyframeNode.state === VoxelTraversal.LoadState.LOADED) {
+ hasLoadedKeyframe = true;
+ }
+ }
+
+ const meetsScreenSpaceError =
+ spatialNode.screenSpaceError < targetScreenSpaceError;
+ if (!meetsScreenSpaceError && hasLoadedKeyframe) {
+ if (!defined(spatialNode.children)) {
+ const childLevel = spatialNode.level + 1;
+ const childXMin = spatialNode.x * 2 + 0;
+ const childXMax = spatialNode.x * 2 + 1;
+ const childYMin = spatialNode.y * 2 + 0;
+ const childYMax = spatialNode.y * 2 + 1;
+ const childZMin = spatialNode.z * 2 + 0;
+ const childZMax = spatialNode.z * 2 + 1;
+
+ spatialNode.children = new Array(
+ new SpatialNode(
+ childLevel,
+ childXMin,
+ childYMin,
+ childZMin,
+ spatialNode,
+ shape,
+ voxelDimensions
+ ),
+ new SpatialNode(
+ childLevel,
+ childXMax,
+ childYMin,
+ childZMin,
+ spatialNode,
+ shape,
+ voxelDimensions
+ ),
+ new SpatialNode(
+ childLevel,
+ childXMin,
+ childYMax,
+ childZMin,
+ spatialNode,
+ shape,
+ voxelDimensions
+ ),
+ new SpatialNode(
+ childLevel,
+ childXMax,
+ childYMax,
+ childZMin,
+ spatialNode,
+ shape,
+ voxelDimensions
+ ),
+ new SpatialNode(
+ childLevel,
+ childXMin,
+ childYMin,
+ childZMax,
+ spatialNode,
+ shape,
+ voxelDimensions
+ ),
+ new SpatialNode(
+ childLevel,
+ childXMax,
+ childYMin,
+ childZMax,
+ spatialNode,
+ shape,
+ voxelDimensions
+ ),
+ new SpatialNode(
+ childLevel,
+ childXMin,
+ childYMax,
+ childZMax,
+ spatialNode,
+ shape,
+ voxelDimensions
+ ),
+ new SpatialNode(
+ childLevel,
+ childXMax,
+ childYMax,
+ childZMax,
+ spatialNode,
+ shape,
+ voxelDimensions
+ )
+ );
+ }
+ for (let childIndex = 0; childIndex < 8; childIndex++) {
+ const child = spatialNode.children[childIndex];
+ addToQueueRecursive(child, visibilityPlaneMask);
+ }
+ } else {
+ // Free up memory
+ spatialNode.children = undefined;
+ }
+ }
+
+ priorityQueue.reset();
+ addToQueueRecursive(rootNode, CullingVolume.MASK_INDETERMINATE);
+
+ const highPriorityKeyframeNodes = that.highPriorityKeyframeNodes;
+ let highPriorityKeyframeNodeCount = 0;
+ let highPriorityKeyframeNode;
+ while (priorityQueue.length > 0) {
+ highPriorityKeyframeNode = priorityQueue.removeMaximum();
+ highPriorityKeyframeNode.highPriorityFrameNumber = frameNumber;
+ highPriorityKeyframeNodes[
+ highPriorityKeyframeNodeCount
+ ] = highPriorityKeyframeNode;
+ highPriorityKeyframeNodeCount++;
+ }
+
+ const keyframeNodesInMegatexture = that.keyframeNodesInMegatexture;
+ // TODO: some of the megatexture state should be stored once, not duplicate for each megatexture
+ const megatexture = that.megatextures[0];
+ const keyframeNodesInMegatextureCount = megatexture.occupiedCount;
+ keyframeNodesInMegatexture.length = keyframeNodesInMegatextureCount;
+ keyframeNodesInMegatexture.sort(function (a, b) {
+ if (a.highPriorityFrameNumber === b.highPriorityFrameNumber) {
+ return b.priority - a.priority;
+ }
+ return b.highPriorityFrameNumber - a.highPriorityFrameNumber;
+ });
+
+ let destroyedCount = 0;
+ let addedCount = 0;
+
+ for (
+ let highPriorityKeyframeNodeIndex = 0;
+ highPriorityKeyframeNodeIndex < highPriorityKeyframeNodeCount;
+ highPriorityKeyframeNodeIndex++
+ ) {
+ highPriorityKeyframeNode =
+ highPriorityKeyframeNodes[highPriorityKeyframeNodeIndex];
+
+ if (
+ highPriorityKeyframeNode.state === VoxelTraversal.LoadState.LOADED ||
+ highPriorityKeyframeNode.spatialNode === undefined
+ ) {
+ // Already loaded, so nothing to do.
+ // Or destroyed when adding a higher priority node
+ continue;
+ }
+ if (highPriorityKeyframeNode.state === VoxelTraversal.LoadState.UNLOADED) {
+ requestTiles(that, highPriorityKeyframeNode);
+ }
+ if (highPriorityKeyframeNode.state === VoxelTraversal.LoadState.RECEIVED) {
+ let addNodeIndex = 0;
+ if (megatexture.isFull()) {
+ // If the megatexture is full, try removing a discardable node with the lowest priority.
+ addNodeIndex = keyframeNodesInMegatextureCount - 1 - destroyedCount;
+ destroyedCount++;
+
+ const discardNode = keyframeNodesInMegatexture[addNodeIndex];
+ discardNode.spatialNode.destroyKeyframeNode(
+ discardNode,
+ that.megatextures
+ );
+ } else {
+ addNodeIndex = keyframeNodesInMegatextureCount + addedCount;
+ addedCount++;
+ }
+ highPriorityKeyframeNode.spatialNode.addKeyframeNodeToMegatextures(
+ highPriorityKeyframeNode,
+ that.megatextures
+ );
+ keyframeNodesInMegatexture[addNodeIndex] = highPriorityKeyframeNode;
+ }
+ }
+}
+
+/**
+ * @ignore
+ * @param {VoxelTraversal} that
+ */
+function printDebugInformation(
+ that,
+ loadAndUnloadTimeMs,
+ generateOctreeTimeMs,
+ totalTimeMs
+) {
+ const keyframeCount = that.keyframeCount;
+ const rootNode = that.rootNode;
+
+ const loadStateCount = Object.keys(VoxelTraversal.LoadState).length;
+ const loadStatesByKeyframe = new Array(loadStateCount);
+ const loadStateByCount = new Array(loadStateCount);
+ let nodeCountTotal = 0;
+
+ for (
+ let loadStateIndex = 0;
+ loadStateIndex < loadStateCount;
+ loadStateIndex++
+ ) {
+ const keyframeArray = new Array(keyframeCount);
+ loadStatesByKeyframe[loadStateIndex] = keyframeArray;
+ for (let i = 0; i < keyframeCount; i++) {
+ keyframeArray[i] = 0;
+ }
+ loadStateByCount[loadStateIndex] = 0;
+ }
+
+ /**
+ * @ignore
+ * @param {SpatialNode} node
+ */
+ function traverseRecursive(node) {
+ const keyframeNodes = node.keyframeNodes;
+ for (
+ let keyframeIndex = 0;
+ keyframeIndex < keyframeNodes.length;
+ keyframeIndex++
+ ) {
+ const keyframeNode = keyframeNodes[keyframeIndex];
+ const keyframe = keyframeNode.keyframe;
+ const state = keyframeNode.state;
+ loadStatesByKeyframe[state][keyframe] += 1;
+ loadStateByCount[state] += 1;
+ nodeCountTotal++;
+ }
+
+ if (defined(node.children)) {
+ for (let childIndex = 0; childIndex < 8; childIndex++) {
+ const child = node.children[childIndex];
+ traverseRecursive(child);
+ }
+ }
+ }
+ traverseRecursive(rootNode);
+
+ const loadedKeyframeStatistics = `KEYFRAMES: ${
+ loadStatesByKeyframe[VoxelTraversal.LoadState.LOADED]
+ }`;
+ const loadStateStatistics =
+ `UNLOADED: ${loadStateByCount[VoxelTraversal.LoadState.UNLOADED]} | ` +
+ `RECEIVING: ${loadStateByCount[VoxelTraversal.LoadState.RECEIVING]} | ` +
+ `RECEIVED: ${loadStateByCount[VoxelTraversal.LoadState.RECEIVED]} | ` +
+ `LOADED: ${loadStateByCount[VoxelTraversal.LoadState.LOADED]} | ` +
+ `FAILED: ${loadStateByCount[VoxelTraversal.LoadState.FAILED]} | ` +
+ `UNAVAILABLE: ${
+ loadStateByCount[VoxelTraversal.LoadState.UNAVAILABLE]
+ } | ` +
+ `TOTAL: ${nodeCountTotal}`;
+
+ const loadAndUnloadTimeMsRounded =
+ Math.round(loadAndUnloadTimeMs * 100) / 100;
+ const generateOctreeTimeMsRounded =
+ Math.round(generateOctreeTimeMs * 100) / 100;
+ const totalTimeMsRounded = Math.round(totalTimeMs * 100) / 100;
+
+ const timerStatistics =
+ `LOAD: ${loadAndUnloadTimeMsRounded} | ` +
+ `OCT: ${generateOctreeTimeMsRounded} | ` +
+ `ALL: ${totalTimeMsRounded}`;
+
+ console.log(
+ `${loadedKeyframeStatistics} || ${loadStateStatistics} || ${timerStatistics}`
+ );
+}
+
+VoxelTraversal.LoadState = {
+ UNLOADED: 0, // Has no data and is in dormant state
+ RECEIVING: 1, // Is waiting on data from the provider
+ RECEIVED: 2, // Received data from the provider
+ LOADED: 3, // Processed data from provider
+ FAILED: 4, // Failed to receive data from the provider
+ UNAVAILABLE: 5, // No data available for this tile
+};
+
+/**
+ * @ignore
+ * @constructor
+ * @param {Number} level
+ * @param {Number} x
+ * @param {Number} y
+ * @param {Number} z
+ * @param {SpatialNode} parent
+ * @param {VoxelShapeType} shape
+ */
+function SpatialNode(level, x, y, z, parent, shape, voxelDimensions) {
+ /**
+ * @ignore
+ * @type {SpatialNode[]}
+ */
+ this.children = undefined;
+ this.parent = parent;
+
+ this.level = level;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ /**
+ * @ignore
+ * @type {KeyframeNode[]}
+ */
+ this.keyframeNodes = [];
+ /**
+ * @ignore
+ * @type {KeyframeNode[]}
+ */
+ this.renderableKeyframeNodes = [];
+
+ this.renderableKeyframeNodeLerp = 0.0;
+ /**
+ * @ignore
+ * @type {KeyframeNode}
+ */
+ this.renderableKeyframeNodePrevious = undefined;
+ /**
+ * @ignore
+ * @type {KeyframeNode}
+ */
+ this.renderableKeyframeNodeNext = undefined;
+
+ this.orientedBoundingBox = new OrientedBoundingBox();
+ this.approximateVoxelSize = 0.0;
+ this.screenSpaceError = 0.0;
+ this.visitedFrameNumber = -1;
+
+ this.computeBoundingVolumes(shape, voxelDimensions);
+}
+
+SpatialNode.spatialComparator = function (a, b) {
+ // The higher of the two screen space errors is prioritized
+ return b.screenSpaceError - a.screenSpaceError;
+};
+
+const scratchObbHalfScale = new Cartesian3();
+
+/**
+ * @ignore
+ * @param {VoxelShape} shape
+ * @param {Cartesian3} voxelDimensions
+ */
+SpatialNode.prototype.computeBoundingVolumes = function (
+ shape,
+ voxelDimensions
+) {
+ this.orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ this.level,
+ this.x,
+ this.y,
+ this.z,
+ this.orientedBoundingBox
+ );
+
+ const halfScale = Matrix3.getScale(
+ this.orientedBoundingBox.halfAxes,
+ scratchObbHalfScale
+ );
+ const maximumScale = 2.0 * Cartesian3.maximumComponent(halfScale);
+ this.approximateVoxelSize =
+ maximumScale / Cartesian3.minimumComponent(voxelDimensions);
+};
+
+/**
+ * @ignore
+ * @param {FrameState} frameState
+ * @param {Number} visibilityPlaneMask
+ * @returns {Number} A plane mask as described in {@link CullingVolume#computeVisibilityWithPlaneMask}.
+ */
+SpatialNode.prototype.visibility = function (frameState, visibilityPlaneMask) {
+ const that = this;
+ const obb = that.orientedBoundingBox;
+ const cullingVolume = frameState.cullingVolume;
+ return cullingVolume.computeVisibilityWithPlaneMask(obb, visibilityPlaneMask);
+};
+
+/**
+ * @ignore
+ * @param {Cartesian3} cameraPosition
+ * @param {Number} screenSpaceErrorMultiplier
+ */
+SpatialNode.prototype.computeScreenSpaceError = function (
+ cameraPosition,
+ screenSpaceErrorMultiplier
+) {
+ const that = this;
+ const obb = that.orientedBoundingBox;
+
+ let distance = Math.sqrt(obb.distanceSquaredTo(cameraPosition));
+ // Avoid divide-by-zero when viewer is inside the tile.
+ distance = Math.max(distance, CesiumMath.EPSILON7);
+ const approximateVoxelSize = that.approximateVoxelSize;
+ const error = screenSpaceErrorMultiplier * (approximateVoxelSize / distance);
+ that.screenSpaceError = error;
+};
+
+// This object imitates a KeyframeNode. Only used for binary search function.
+const scratchBinarySearchKeyframeNode = {
+ keyframe: 0,
+};
+
+/**
+ * Finds the index of the keyframe if it exists, or the complement (~) of the index where it would be in the sorted array.
+ * @ignore
+ * @param {Number} keyframe
+ * @returns {Number}
+ */
+SpatialNode.prototype.findKeyframeIndex = function (keyframe) {
+ const that = this;
+ const keyframeNodes = that.keyframeNodes;
+ scratchBinarySearchKeyframeNode.keyframe = keyframe;
+ const index = binarySearch(
+ keyframeNodes,
+ scratchBinarySearchKeyframeNode,
+ KeyframeNode.searchComparator
+ );
+ return index;
+};
+/**
+ * Finds the index of the renderable keyframe if it exists, or the complement (~) of the index where it would be in the sorted array.
+ * @ignore
+ * @param {Number} keyframe
+ * @returns {Number}
+ */
+SpatialNode.prototype.findRenderableKeyframeIndex = function (keyframe) {
+ const that = this;
+ const renderableKeyframeNodes = that.renderableKeyframeNodes;
+ scratchBinarySearchKeyframeNode.keyframe = keyframe;
+ const index = binarySearch(
+ renderableKeyframeNodes,
+ scratchBinarySearchKeyframeNode,
+ KeyframeNode.searchComparator
+ );
+ return index;
+};
+
+/**
+ * Computes the most suitable keyframes for rendering, balancing between temporal and visual quality.
+ * @ignore
+ * @param {Number} keyframeLocation
+ */
+SpatialNode.prototype.computeSurroundingRenderableKeyframeNodes = function (
+ keyframeLocation
+) {
+ const that = this;
+
+ let spatialNode = that;
+ const startLevel = spatialNode.level;
+
+ const targetKeyframePrev = Math.floor(keyframeLocation);
+ const targetKeyframeNext = Math.ceil(keyframeLocation);
+
+ let bestKeyframeNodePrev;
+ let bestKeyframeNodeNext;
+ let minimumDistancePrev = +Number.MAX_VALUE;
+ let minimumDistanceNext = +Number.MAX_VALUE;
+
+ while (defined(spatialNode)) {
+ const renderableKeyframeNodes = spatialNode.renderableKeyframeNodes;
+
+ if (renderableKeyframeNodes.length >= 1) {
+ let keyframeNodeIndexPrev = spatialNode.findRenderableKeyframeIndex(
+ targetKeyframePrev
+ );
+ if (keyframeNodeIndexPrev < 0) {
+ keyframeNodeIndexPrev = CesiumMath.clamp(
+ ~keyframeNodeIndexPrev - 1,
+ 0,
+ renderableKeyframeNodes.length - 1
+ );
+ }
+ const keyframeNodePrev = renderableKeyframeNodes[keyframeNodeIndexPrev];
+ const keyframePrev = keyframeNodePrev.keyframe;
+
+ let keyframeNodeNext;
+ if (
+ targetKeyframeNext === targetKeyframePrev ||
+ targetKeyframePrev < keyframePrev
+ ) {
+ keyframeNodeNext = keyframeNodePrev;
+ } else {
+ const keyframeNodeIndexNext = Math.min(
+ keyframeNodeIndexPrev + 1,
+ renderableKeyframeNodes.length - 1
+ );
+ keyframeNodeNext = renderableKeyframeNodes[keyframeNodeIndexNext];
+ }
+ const keyframeNext = keyframeNodeNext.keyframe;
+
+ const keyframeDistancePrev = targetKeyframePrev - keyframePrev;
+ const keyframeDistanceNext = keyframeNext - targetKeyframeNext;
+ const levelDistance = startLevel - spatialNode.level;
+
+ // Balance temporal and visual quality
+ const levelWeight = Math.exp(levelDistance * 4.0);
+ const normalKeyframeWeight = 1.0;
+ const reverseKeyframeWeight = 200.0; // Keyframes on the opposite of the desired direction are deprioritized.
+ const distancePrev =
+ levelDistance * levelWeight +
+ (keyframeDistancePrev >= 0
+ ? keyframeDistancePrev * normalKeyframeWeight
+ : -keyframeDistancePrev * reverseKeyframeWeight);
+ const distanceNext =
+ levelDistance * levelWeight +
+ (keyframeDistanceNext >= 0
+ ? keyframeDistanceNext * normalKeyframeWeight
+ : -keyframeDistanceNext * reverseKeyframeWeight);
+
+ // // Prioritize visual quality
+ // const distancePrev = levelDistance === 0.0 ? 0.0 : Number.MAX_VALUE;
+ // const distanceNext = levelDistance === 0.0 ? 0.0 : Number.MAX_VALUE;
+
+ // // Prioritize temporal quality
+ // const distancePrev = keyframeDistancePrev >= 0 ? keyframeDistancePrev : Number.MAX_VALUE + keyframeDistancePrev;
+ // const distanceNext = keyframeDistanceNext >= 0 ? keyframeDistanceNext : Number.MAX_VALUE + keyframeDistanceNext;
+
+ if (distancePrev < minimumDistancePrev) {
+ minimumDistancePrev = distancePrev;
+ bestKeyframeNodePrev = keyframeNodePrev;
+ }
+ if (distanceNext < minimumDistanceNext) {
+ minimumDistanceNext = distanceNext;
+ bestKeyframeNodeNext = keyframeNodeNext;
+ }
+ if (keyframeDistancePrev === 0 && keyframeDistanceNext === 0) {
+ // Nothing higher up will be better, so break early.
+ break;
+ }
+ }
+
+ spatialNode = spatialNode.parent;
+ }
+
+ that.renderableKeyframeNodePrevious = bestKeyframeNodePrev;
+ that.renderableKeyframeNodeNext = bestKeyframeNodeNext;
+ if (defined(bestKeyframeNodePrev) && defined(bestKeyframeNodeNext)) {
+ const bestKeyframePrev = bestKeyframeNodePrev.keyframe;
+ const bestKeyframeNext = bestKeyframeNodeNext.keyframe;
+ that.renderableKeyframeNodeLerp =
+ bestKeyframePrev === bestKeyframeNext
+ ? 0.0
+ : CesiumMath.clamp(
+ (keyframeLocation - bestKeyframePrev) /
+ (bestKeyframeNext - bestKeyframePrev),
+ 0.0,
+ 1.0
+ );
+ }
+};
+
+/**
+ * @ignore
+ * @param {Number} frameNumber
+ * @returns {Boolean}
+ */
+SpatialNode.prototype.isVisited = function (frameNumber) {
+ const that = this;
+ return that.visitedFrameNumber === frameNumber;
+};
+
+/**
+ * @ignore
+ * @param {Number} keyframe
+ */
+SpatialNode.prototype.createKeyframeNode = function (keyframe) {
+ const that = this;
+ let index = that.findKeyframeIndex(keyframe);
+ if (index < 0) {
+ index = ~index; // convert to insertion index
+ const keyframeNode = new KeyframeNode(that, keyframe);
+ that.keyframeNodes.splice(index, 0, keyframeNode);
+ }
+};
+/**
+ * @ignore
+ * @param {KeyframeNode} keyframeNode
+ * @param {Megatexture} megatexture
+ */
+SpatialNode.prototype.destroyKeyframeNode = function (
+ keyframeNode,
+ megatextures
+) {
+ const that = this;
+
+ const keyframe = keyframeNode.keyframe;
+ const keyframeIndex = that.findKeyframeIndex(keyframe);
+ if (keyframeIndex < 0) {
+ throw new DeveloperError("Keyframe node does not exist.");
+ }
+
+ const keyframeNodes = that.keyframeNodes;
+ keyframeNodes.splice(keyframeIndex, 1);
+
+ if (keyframeNode.megatextureIndex !== -1) {
+ const megatextureArray = Object.keys(megatextures).map(function (key) {
+ return megatextures[key];
+ }); // Object.values workaround
+ const numberOfMegatextures = megatextureArray.length;
+ for (let i = 0; i < numberOfMegatextures; i++) {
+ megatextureArray[i].remove(keyframeNode.megatextureIndex);
+ }
+
+ const renderableKeyframeNodeIndex = that.findRenderableKeyframeIndex(
+ keyframe
+ );
+ if (renderableKeyframeNodeIndex < 0) {
+ throw new DeveloperError("Renderable keyframe node does not exist.");
+ }
+
+ const renderableKeyframeNodes = that.renderableKeyframeNodes;
+ renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 1);
+ }
+
+ keyframeNode.spatialNode = undefined;
+ keyframeNode.state = VoxelTraversal.LoadState.UNLOADED;
+ keyframeNode.metadatas = {};
+ keyframeNode.megatextureIndex = -1;
+ keyframeNode.priority = -Number.MAX_VALUE;
+ keyframeNode.highPriorityFrameNumber = -1;
+};
+
+SpatialNode.prototype.addKeyframeNodeToMegatextures = function (
+ keyframeNode,
+ megatextures
+) {
+ if (
+ keyframeNode.state !== VoxelTraversal.LoadState.RECEIVED ||
+ keyframeNode.megatextureIndex !== -1 ||
+ keyframeNode.metadatas.length !== megatextures.length
+ ) {
+ throw new DeveloperError("Keyframe node cannot be added to megatexture");
+ }
+
+ const length = megatextures.length;
+ for (let i = 0; i < length; i++) {
+ const megatexture = megatextures[i];
+ keyframeNode.megatextureIndex = megatexture.add(keyframeNode.metadatas[i]);
+ keyframeNode.metadatas[i] = undefined; // data is in megatexture so no need to hold onto it
+ }
+
+ keyframeNode.state = VoxelTraversal.LoadState.LOADED;
+
+ const renderableKeyframeNodes = this.renderableKeyframeNodes;
+ let renderableKeyframeNodeIndex = this.findRenderableKeyframeIndex(
+ keyframeNode.keyframe
+ );
+ if (renderableKeyframeNodeIndex >= 0) {
+ throw new DeveloperError("Keyframe already renderable");
+ }
+ renderableKeyframeNodeIndex = ~renderableKeyframeNodeIndex;
+ renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 0, keyframeNode);
+};
+/**
+ * @ignore
+ * @param {KeyframeNode} keyframeNode
+ * @param {Megatexture} megatexture
+ */
+SpatialNode.prototype.addKeyframeNodeToMegatexture = function (
+ keyframeNode,
+ megatexture
+) {
+ const that = this;
+
+ if (
+ keyframeNode.state !== VoxelTraversal.LoadState.RECEIVED ||
+ keyframeNode.megatextureIndex !== -1 ||
+ !defined(keyframeNode.metadatas[megatexture.metadataName])
+ ) {
+ throw new DeveloperError("Keyframe node cannot be added to megatexture");
+ }
+
+ keyframeNode.megatextureIndex = megatexture.add(
+ keyframeNode.metadatas[megatexture.metadataName]
+ );
+ keyframeNode.metadatas[megatexture.metadataName] = undefined; // data is in megatexture so no need to hold onto it
+ keyframeNode.state = VoxelTraversal.LoadState.LOADED;
+
+ const renderableKeyframeNodes = that.renderableKeyframeNodes;
+ let renderableKeyframeNodeIndex = that.findRenderableKeyframeIndex(
+ keyframeNode.keyframe
+ );
+ if (renderableKeyframeNodeIndex >= 0) {
+ throw new DeveloperError("Keyframe already renderable");
+ }
+ renderableKeyframeNodeIndex = ~renderableKeyframeNodeIndex;
+ renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 0, keyframeNode);
+};
+
+/**
+ * @ignore
+ * @param {Number} frameNumber
+ */
+SpatialNode.prototype.isRenderable = function (frameNumber) {
+ const that = this;
+
+ const previousNode = that.renderableKeyframeNodePrevious;
+ const nextNode = that.renderableKeyframeNodeNext;
+ const level = that.level;
+
+ return (
+ defined(previousNode) &&
+ defined(nextNode) &&
+ (previousNode.spatialNode.level === level ||
+ nextNode.spatialNode.level === level) &&
+ that.visitedFrameNumber === frameNumber
+ );
+};
+
+/**
+ * @ignore
+ * @constructor
+ * @param {SpatialNode} spatialNode
+ * @param {Number} keyframe
+ */
+function KeyframeNode(spatialNode, keyframe) {
+ this.spatialNode = spatialNode;
+ this.keyframe = keyframe;
+ this.state = VoxelTraversal.LoadState.UNLOADED;
+ this.metadatas = [];
+ this.megatextureIndex = -1;
+ this.priority = -Number.MAX_VALUE;
+ this.highPriorityFrameNumber = -1;
+}
+
+/**
+ * @ignore
+ * @param {KeyframeNode} a
+ * @param {KeyframeNode} b
+ */
+KeyframeNode.priorityComparator = function (a, b) {
+ return a.priority - b.priority;
+};
+/**
+ * @ignore
+ * @param {KeyframeNode} a
+ * @param {KeyframeNode} b
+ */
+KeyframeNode.searchComparator = function (a, b) {
+ return a.keyframe - b.keyframe;
+};
+
+// GPU Octree Layout
+// (shown as binary tree instead of octree for demonstration purposes)
+//
+// Tree representation:
+// 0
+// / \
+// / \
+// / \
+// 1 3
+// / \ / \
+// L0 2 L3 L4
+// / \
+// L1 L2
+//
+//
+// Array representation:
+// L = leaf index
+// * = index to parent node
+// index: 0_______ 1________ 2________ 3_________
+// array: [*0, 1, 3, *0, L0, 2, *1 L1, L2, *0, L3, L4]
+//
+// The array is generated from a depth-first traversal. The end result could be an unbalanced tree,
+// so the parent index is stored at each node to make it possible to traverse upwards.
+
+const GpuOctreeFlag = {
+ // Data is an octree index.
+ INTERNAL: 0,
+ // Data is a leaf node.
+ LEAF: 1,
+ // When leaf data is packed in the octree and there's a node that is forced to
+ // render but has no data of its own (such as when its siblings are renderable but it
+ // is not), signal that it's using its parent's data.
+ PACKED_LEAF_FROM_PARENT: 2,
+};
+
+/**
+ * @ignore
+ * @param {VoxelTraversal} that
+ */
+function generateOctree(that) {
+ const keyframeLocation = that.keyframeLocation;
+ const useLeafNodes = that.useLeafNodeTexture;
+ const frameNumber = that.frameNumber;
+
+ let internalNodeCount = 0;
+ let leafNodeCount = 0;
+ const internalNodeOctreeData = [];
+ const leafNodeOctreeData = [];
+
+ /**
+ * @ignore
+ * @param {SpatialNode} node
+ * @param {Number} childOctreeIndex
+ * @param {Number} childEntryIndex
+ * @param {Number} parentOctreeIndex
+ * @param {Number} parentEntryIndex
+ */
+ function buildOctree(
+ node,
+ childOctreeIndex,
+ childEntryIndex,
+ parentOctreeIndex,
+ parentEntryIndex
+ ) {
+ let hasRenderableChildren = false;
+ if (defined(node.children)) {
+ for (let c = 0; c < 8; c++) {
+ const childNode = node.children[c];
+ childNode.computeSurroundingRenderableKeyframeNodes(keyframeLocation);
+ if (childNode.isRenderable(frameNumber)) {
+ hasRenderableChildren = true;
+ }
+ }
+ }
+
+ if (hasRenderableChildren) {
+ // Point the parent and child octree indexes at each other
+ internalNodeOctreeData[parentEntryIndex] =
+ (GpuOctreeFlag.INTERNAL << 16) | childOctreeIndex;
+ internalNodeOctreeData[childEntryIndex] = parentOctreeIndex;
+ internalNodeCount++;
+
+ // Recurse over children
+ parentOctreeIndex = childOctreeIndex;
+ parentEntryIndex = parentOctreeIndex * 9 + 1;
+ for (let cc = 0; cc < 8; cc++) {
+ const child = node.children[cc];
+ childOctreeIndex = internalNodeCount;
+ childEntryIndex = childOctreeIndex * 9 + 0;
+ buildOctree(
+ child,
+ childOctreeIndex,
+ childEntryIndex,
+ parentOctreeIndex,
+ parentEntryIndex + cc
+ );
+ }
+ } else {
+ // Store the leaf node information instead
+ // Recursion stops here because there are no renderable children
+ if (useLeafNodes) {
+ const previousKeyframeNode = node.renderableKeyframeNodePrevious;
+ const nextKeyframeNode = node.renderableKeyframeNodeNext;
+ leafNodeOctreeData[leafNodeCount * 5 + 0] =
+ node.renderableKeyframeNodeLerp;
+ leafNodeOctreeData[leafNodeCount * 5 + 1] =
+ node.level - previousKeyframeNode.spatialNode.level;
+ leafNodeOctreeData[leafNodeCount * 5 + 2] =
+ node.level - nextKeyframeNode.spatialNode.level;
+ leafNodeOctreeData[leafNodeCount * 5 + 3] =
+ previousKeyframeNode.megatextureIndex;
+ leafNodeOctreeData[leafNodeCount * 5 + 4] =
+ nextKeyframeNode.megatextureIndex;
+ internalNodeOctreeData[parentEntryIndex] =
+ (GpuOctreeFlag.LEAF << 16) | leafNodeCount;
+ } else {
+ const keyframeNode = node.renderableKeyframeNodePrevious;
+ const levelDifference = node.level - keyframeNode.spatialNode.level;
+ const flag =
+ levelDifference === 0
+ ? GpuOctreeFlag.LEAF
+ : GpuOctreeFlag.PACKED_LEAF_FROM_PARENT;
+ internalNodeOctreeData[parentEntryIndex] =
+ (flag << 16) | keyframeNode.megatextureIndex;
+ }
+ leafNodeCount++;
+ }
+ }
+
+ const rootNode = that.rootNode;
+ rootNode.computeSurroundingRenderableKeyframeNodes(keyframeLocation);
+ if (rootNode.isRenderable(frameNumber)) {
+ buildOctree(rootNode, 0, 0, 0, 0);
+ }
+
+ /**
+ * @ignore
+ * @param {Number[]} data
+ * @param {Number} texelsPerTile
+ * @param {Number} tilesPerRow
+ * @param {Texture} texture
+ */
+ function copyToInternalNodeTexture(
+ data,
+ texelsPerTile,
+ tilesPerRow,
+ texture
+ ) {
+ const channelCount = PixelFormat.componentsLength(texture.pixelFormat);
+ const tileCount = Math.ceil(data.length / texelsPerTile);
+ const copyWidth = Math.max(
+ 1,
+ texelsPerTile * Math.min(tileCount, tilesPerRow)
+ );
+ const copyHeight = Math.max(1, Math.ceil(tileCount / tilesPerRow));
+
+ const textureData = new Uint8Array(copyWidth * copyHeight * channelCount);
+ for (let i = 0; i < data.length; i++) {
+ const val = data[i];
+ const startIndex = i * channelCount;
+ for (let j = 0; j < channelCount; j++) {
+ textureData[startIndex + j] = (val >>> (j * 8)) & 0xff;
+ }
+ }
+
+ const source = {
+ arrayBufferView: textureData,
+ width: copyWidth,
+ height: copyHeight,
+ };
+
+ const copyOptions = {
+ source: source,
+ xOffset: 0,
+ yOffset: 0,
+ };
+
+ texture.copyFrom(copyOptions);
+ }
+
+ /**
+ * @ignore
+ * @param {Number[]} data
+ * @param {Number} texelsPerTile
+ * @param {Number} tilesPerRow
+ * @param {Texture} texture
+ */
+ function copyToLeafNodeTexture(data, texelsPerTile, tilesPerRow, texture) {
+ const channelCount = PixelFormat.componentsLength(texture.pixelFormat);
+ const datasPerTile = 5;
+ const tileCount = Math.ceil(data.length / datasPerTile);
+ const copyWidth = Math.max(
+ 1,
+ texelsPerTile * Math.min(tileCount, tilesPerRow)
+ );
+ const copyHeight = Math.max(1, Math.ceil(tileCount / tilesPerRow));
+
+ const textureData = new Uint8Array(copyWidth * copyHeight * channelCount);
+ for (let tileIndex = 0; tileIndex < tileCount; tileIndex++) {
+ const timeLerp = data[tileIndex * datasPerTile + 0];
+ const previousKeyframeLevelsAbove = data[tileIndex * datasPerTile + 1];
+ const nextKeyframeLevelsAbove = data[tileIndex * datasPerTile + 2];
+ const previousKeyframeMegatextureIndex =
+ data[tileIndex * datasPerTile + 3];
+ const nextKeyframeMegatextureIndex = data[tileIndex * datasPerTile + 4];
+
+ const timeLerpCompressed = CesiumMath.clamp(
+ Math.floor(65536 * timeLerp),
+ 0,
+ 65535
+ );
+ textureData[tileIndex * 8 + 0] = (timeLerpCompressed >>> 0) & 0xff;
+ textureData[tileIndex * 8 + 1] = (timeLerpCompressed >>> 8) & 0xff;
+ textureData[tileIndex * 8 + 2] = previousKeyframeLevelsAbove & 0xff;
+ textureData[tileIndex * 8 + 3] = nextKeyframeLevelsAbove & 0xff;
+ textureData[tileIndex * 8 + 4] =
+ (previousKeyframeMegatextureIndex >>> 0) & 0xff;
+ textureData[tileIndex * 8 + 5] =
+ (previousKeyframeMegatextureIndex >>> 8) & 0xff;
+ textureData[tileIndex * 8 + 6] =
+ (nextKeyframeMegatextureIndex >>> 0) & 0xff;
+ textureData[tileIndex * 8 + 7] =
+ (nextKeyframeMegatextureIndex >>> 8) & 0xff;
+ }
+
+ const source = {
+ arrayBufferView: textureData,
+ width: copyWidth,
+ height: copyHeight,
+ };
+
+ const copyOptions = {
+ source: source,
+ xOffset: 0,
+ yOffset: 0,
+ };
+
+ texture.copyFrom(copyOptions);
+ }
+
+ copyToInternalNodeTexture(
+ internalNodeOctreeData,
+ 9,
+ that.internalNodeTilesPerRow,
+ that.internalNodeTexture
+ );
+ if (useLeafNodes) {
+ copyToLeafNodeTexture(
+ leafNodeOctreeData,
+ 2,
+ that.leafNodeTilesPerRow,
+ that.leafNodeTexture
+ );
+ }
+}
+
+/**
+ * @private
+ * @param {Number} tileCount
+ * @param {Cartesian3} dimensions
+ * @param {MetadataType[]} types
+ * @param {MetadataComponentType[]} componentTypes
+ */
+VoxelTraversal.getApproximateTextureMemoryByteLength = function (
+ tileCount,
+ dimensions,
+ types,
+ componentTypes
+) {
+ let textureMemoryByteLength = 0;
+
+ const length = types.length;
+ for (let i = 0; i < length; i++) {
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const componentCount = MetadataType.getComponentCount(type);
+
+ textureMemoryByteLength += Megatexture.getApproximateTextureMemoryByteLength(
+ tileCount,
+ dimensions,
+ componentCount,
+ componentType
+ );
+ }
+
+ return textureMemoryByteLength;
+};
+
+/**
+ * @ignore
+ * @constructor
+ * @param {Context} context
+ * @param {Cartesian3} dimensions
+ * @param {Number} channelCount
+ * @param {MetadataComponentType} componentType
+ * @param {Number} [textureMemoryByteLength]
+ */
+function Megatexture(
+ context,
+ dimensions,
+ channelCount,
+ componentType,
+ textureMemoryByteLength
+) {
+ // TODO there are a lot of texture packing rules, see https://github.com/CesiumGS/cesium/issues/9572
+ // Unsigned short textures not allowed in webgl 1, so treat as float
+ if (componentType === MetadataComponentType.UNSIGNED_SHORT) {
+ componentType = MetadataComponentType.FLOAT32;
+ }
+
+ const supportsFloatingPointTexture = context.floatingPointTexture;
+ if (
+ componentType === MetadataComponentType.FLOAT32 &&
+ !supportsFloatingPointTexture
+ ) {
+ throw new RuntimeError("Floating point texture not supported");
+ }
+
+ // TODO support more
+ let pixelType;
+ if (
+ componentType === MetadataComponentType.FLOAT32 ||
+ componentType === MetadataComponentType.FLOAT64
+ ) {
+ pixelType = PixelDatatype.FLOAT;
+ } else if (componentType === MetadataComponentType.UINT8) {
+ pixelType = PixelDatatype.UNSIGNED_BYTE;
+ }
+
+ let pixelFormat;
+ if (channelCount === 1) {
+ pixelFormat = PixelFormat.LUMINANCE;
+ } else if (channelCount === 2) {
+ pixelFormat = PixelFormat.LUMINANCE_ALPHA;
+ } else if (channelCount === 3) {
+ pixelFormat = PixelFormat.RGB;
+ } else if (channelCount === 4) {
+ pixelFormat = PixelFormat.RGBA;
+ }
+
+ const maximumTextureMemoryByteLength = 512 * 1024 * 1024;
+ const defaultTextureMemoryByteLength = 128 * 1024 * 1024;
+ textureMemoryByteLength = Math.min(
+ defaultValue(textureMemoryByteLength, defaultTextureMemoryByteLength),
+ maximumTextureMemoryByteLength
+ );
+ const maximumTextureDimensionContext = ContextLimits.maximumTextureSize;
+ const componentTypeByteLength = MetadataComponentType.getSizeInBytes(
+ componentType
+ );
+ const texelCount = Math.floor(
+ textureMemoryByteLength / (channelCount * componentTypeByteLength)
+ );
+ const textureDimension = Math.min(
+ maximumTextureDimensionContext,
+ CesiumMath.previousPowerOfTwo(Math.floor(Math.sqrt(texelCount)))
+ );
+
+ const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.x));
+ const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);
+ const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;
+ const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;
+ const regionCountPerMegatextureX = Math.floor(
+ textureDimension / voxelCountPerRegionX
+ );
+ const regionCountPerMegatextureY = Math.floor(
+ textureDimension / voxelCountPerRegionY
+ );
+
+ // TODO can this happen?
+ if (regionCountPerMegatextureX === 0 || regionCountPerMegatextureY === 0) {
+ throw new RuntimeError("Tileset is too large to fit into megatexture");
+ }
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this.channelCount = channelCount;
+
+ /**
+ * @type {MetadataComponentType}
+ * @private
+ */
+ this.componentType = componentType;
+
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
+ this.voxelCountPerTile = Cartesian3.clone(dimensions, new Cartesian3());
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this.maximumTileCount =
+ regionCountPerMegatextureX * regionCountPerMegatextureY;
+
+ /**
+ * @type {Cartesian2}
+ * @private
+ */
+ this.regionCountPerMegatexture = new Cartesian2(
+ regionCountPerMegatextureX,
+ regionCountPerMegatextureY
+ );
+
+ /**
+ * @type {Cartesian2}
+ * @private
+ */
+ this.voxelCountPerRegion = new Cartesian2(
+ voxelCountPerRegionX,
+ voxelCountPerRegionY
+ );
+
+ /**
+ * @type {Cartesian2}
+ * @private
+ */
+ this.sliceCountPerRegion = new Cartesian2(
+ sliceCountPerRegionX,
+ sliceCountPerRegionY
+ );
+
+ /**
+ * @type {Cartesian2}
+ * @private
+ */
+ this.voxelSizeUv = new Cartesian2(
+ 1.0 / textureDimension,
+ 1.0 / textureDimension
+ );
+
+ /**
+ * @type {Cartesian2}
+ * @private
+ */
+ this.sliceSizeUv = new Cartesian2(
+ dimensions.x / textureDimension,
+ dimensions.y / textureDimension
+ );
+
+ /**
+ * @type {Cartesian2}
+ * @private
+ */
+ this.regionSizeUv = new Cartesian2(
+ voxelCountPerRegionX / textureDimension,
+ voxelCountPerRegionY / textureDimension
+ );
+
+ /**
+ * @type {Texture}
+ * @private
+ */
+ this.texture = new Texture({
+ context: context,
+ pixelFormat: pixelFormat,
+ pixelDatatype: pixelType,
+ flipY: false,
+ width: textureDimension,
+ height: textureDimension,
+ sampler: new Sampler({
+ wrapS: TextureWrap.CLAMP_TO_EDGE,
+ wrapT: TextureWrap.CLAMP_TO_EDGE,
+ minificationFilter: TextureMinificationFilter.LINEAR,
+ magnificationFilter: TextureMagnificationFilter.LINEAR,
+ }),
+ });
+
+ const ArrayType = MetadataComponentType.toTypedArrayType(componentType);
+ this.tileVoxelDataTemp = new ArrayType(
+ voxelCountPerRegionX * voxelCountPerRegionY * channelCount
+ );
+
+ /**
+ * @type {MegatextureNode[]}
+ * @private
+ */
+ this.nodes = new Array(this.maximumTileCount);
+ for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
+ this.nodes[tileIndex] = new MegatextureNode(tileIndex);
+ }
+ for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
+ const node = this.nodes[tileIndex];
+ node.previousNode = tileIndex > 0 ? this.nodes[tileIndex - 1] : undefined;
+ node.nextNode =
+ tileIndex < this.maximumTileCount - 1
+ ? this.nodes[tileIndex + 1]
+ : undefined;
+ }
+
+ /**
+ * @type {MegatextureNode}
+ * @private
+ */
+ this.occupiedList = undefined;
+
+ /**
+ * @type {MegatextureNode}
+ * @private
+ */
+ this.emptyList = this.nodes[0];
+
+ /**
+ * @type {Number}
+ * @private
+ */
+ this.occupiedCount = 0;
+}
+
+/**
+ * @ignore
+ * @constructor
+ * @param {Number} index
+ */
+function MegatextureNode(index) {
+ this.index = index;
+
+ /**
+ * @ignore
+ * @type {MegatextureNode}
+ */
+ this.nextNode = undefined;
+
+ /**
+ * @ignore
+ * @type {MegatextureNode}
+ */
+ this.previousNode = undefined;
+}
+
+/**
+ * @ignore
+ * @param {Array} data
+ * @returns {Number}
+ */
+Megatexture.prototype.add = function (data) {
+ const that = this;
+
+ if (that.isFull()) {
+ throw new DeveloperError("Trying to add when there are no empty spots");
+ }
+
+ // remove head of empty list
+ const node = that.emptyList;
+ that.emptyList = that.emptyList.nextNode;
+ if (defined(that.emptyList)) {
+ that.emptyList.previousNode = undefined;
+ }
+
+ // make head of occupied list
+ node.nextNode = that.occupiedList;
+ if (defined(node.nextNode)) {
+ node.nextNode.previousNode = node;
+ }
+ that.occupiedList = node;
+
+ const index = node.index;
+ that.writeDataToTexture(index, data);
+
+ that.occupiedCount++;
+ return index;
+};
+
+/**
+ * @ignore
+ * @param {Number} index
+ */
+Megatexture.prototype.remove = function (index) {
+ const that = this;
+ if (index < 0 || index >= that.maximumTileCount) {
+ throw new DeveloperError("Megatexture index out of bounds");
+ }
+
+ // remove from list
+ const node = that.nodes[index];
+ if (defined(node.previousNode)) {
+ node.previousNode.nextNode = node.nextNode;
+ }
+ if (defined(node.nextNode)) {
+ node.nextNode.previousNode = node.previousNode;
+ }
+
+ // make head of empty list
+ node.nextNode = that.emptyList;
+ if (defined(node.nextNode)) {
+ node.nextNode.previousNode = node;
+ }
+ node.previousNode = undefined;
+ that.emptyList = node;
+ that.occupiedCount--;
+};
+
+/**
+ * @ignore
+ * @returns {Boolean}
+ */
+Megatexture.prototype.isFull = function () {
+ const that = this;
+ return that.emptyList === undefined;
+};
+
+/**
+ * @ignore
+ * @param {Number} tileCount
+ * @param {Cartesian3} dimensions
+ * @param {Number} channelCount number of channels in the metadata. Must be 1 to 4.
+ * @param {MetadataComponentType} componentType
+ * @returns {Number}
+ */
+Megatexture.getApproximateTextureMemoryByteLength = function (
+ tileCount,
+ dimensions,
+ channelCount,
+ componentType
+) {
+ // TODO there's a lot of code duplicate with Megatexture constructor
+
+ // Unsigned short textures not allowed in webgl 1, so treat as float
+ if (componentType === MetadataComponentType.UNSIGNED_SHORT) {
+ componentType = MetadataComponentType.FLOAT32;
+ }
+
+ const datatypeSizeInBytes = MetadataComponentType.getSizeInBytes(
+ componentType
+ );
+ const voxelCountTotal =
+ tileCount * dimensions.x * dimensions.y * dimensions.z;
+
+ const sliceCountPerRegionX = Math.ceil(Math.sqrt(dimensions.z));
+ const sliceCountPerRegionY = Math.ceil(dimensions.z / sliceCountPerRegionX);
+ const voxelCountPerRegionX = sliceCountPerRegionX * dimensions.x;
+ const voxelCountPerRegionY = sliceCountPerRegionY * dimensions.y;
+
+ // Find the power of two that can fit all tile data, accounting for slices.
+ // There's probably a non-iterative solution for this, but this is good enough for now.
+ let textureDimension = CesiumMath.previousPowerOfTwo(
+ Math.floor(Math.sqrt(voxelCountTotal))
+ );
+ for (;;) {
+ const regionCountX = Math.floor(textureDimension / voxelCountPerRegionX);
+ const regionCountY = Math.floor(textureDimension / voxelCountPerRegionY);
+ const regionCount = regionCountX * regionCountY;
+ if (regionCount >= tileCount) {
+ break;
+ } else {
+ textureDimension *= 2;
+ }
+ }
+
+ const textureMemoryByteLength =
+ textureDimension * textureDimension * channelCount * datatypeSizeInBytes;
+ return textureMemoryByteLength;
+};
+
+/**
+ * @ignore
+ * @param {Number} index
+ * @param {Float32Array|Uint16Array|Uint8Array} data
+ */
+Megatexture.prototype.writeDataToTexture = function (index, data) {
+ const that = this;
+ const texture = that.texture;
+ const channelCount = that.channelCount;
+ const regionDimensionsPerMegatexture = that.regionCountPerMegatexture;
+ const voxelDimensionsPerRegion = that.voxelCountPerRegion;
+ const voxelDimensionsPerTile = that.voxelCountPerTile;
+ const sliceDimensionsPerRegion = that.sliceCountPerRegion;
+
+ let tileData = data;
+
+ // Unsigned short textures not allowed in webgl 1, so treat as float
+ if (data.constructor === Uint16Array) {
+ const elementCount = data.length;
+ tileData = new Float32Array(elementCount);
+ for (let i = 0; i < elementCount / channelCount; i++) {
+ for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
+ const dataIndex = i * channelCount + channelIndex;
+ const minimumValue = that.minimumValues[channelIndex];
+ const maximumValue = that.maximumValues[channelIndex];
+ // TODO extrema are unnormalized, but we are normalizing to [0, 1] here. what do we want to do? will the user expect to get normalized samples and extrema in the style function?
+ tileData[dataIndex] =
+ (data[dataIndex] - minimumValue) / (maximumValue - minimumValue);
+ // tileData[dataIndex] = CesiumMath.lerp(
+ // minimumValue,
+ // maximumValue,
+ // data[dataIndex] / 65535
+ // );
+ }
+ }
+ }
+
+ const tileVoxelData = that.tileVoxelDataTemp;
+ for (let z = 0; z < voxelDimensionsPerTile.z; z++) {
+ const sliceVoxelOffsetX =
+ (z % sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.x;
+ const sliceVoxelOffsetY =
+ Math.floor(z / sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.y;
+ for (let y = 0; y < voxelDimensionsPerTile.y; y++) {
+ for (let x = 0; x < voxelDimensionsPerTile.x; x++) {
+ const readIndex =
+ z * voxelDimensionsPerTile.y * voxelDimensionsPerTile.x +
+ y * voxelDimensionsPerTile.x +
+ x;
+ const writeIndex =
+ (sliceVoxelOffsetY + y) * voxelDimensionsPerRegion.x +
+ (sliceVoxelOffsetX + x);
+ for (let c = 0; c < channelCount; c++) {
+ tileVoxelData[writeIndex * channelCount + c] =
+ tileData[readIndex * channelCount + c];
+ }
+ }
+ }
+ }
+
+ const voxelWidth = voxelDimensionsPerRegion.x;
+ const voxelHeight = voxelDimensionsPerRegion.y;
+ const voxelOffsetX =
+ (index % regionDimensionsPerMegatexture.x) * voxelDimensionsPerRegion.x;
+ const voxelOffsetY =
+ Math.floor(index / regionDimensionsPerMegatexture.x) *
+ voxelDimensionsPerRegion.y;
+
+ const source = {
+ arrayBufferView: tileVoxelData,
+ width: voxelWidth,
+ height: voxelHeight,
+ };
+
+ const copyOptions = {
+ source: source,
+ xOffset: voxelOffsetX,
+ yOffset: voxelOffsetY,
+ };
+
+ texture.copyFrom(copyOptions);
+};
+
+Megatexture.prototype.isDestroyed = function () {
+ return false;
+};
+
+Megatexture.prototype.destroy = function () {
+ const that = this;
+ that.texture = that.texture && that.texture.destroy();
+ return destroyObject(that);
+};
+
+export default VoxelTraversal;
diff --git a/Source/Shaders/Builtin/Constants/passOverlay.glsl b/Source/Shaders/Builtin/Constants/passOverlay.glsl
index e104cb08dd7..6aea11eb1ee 100644
--- a/Source/Shaders/Builtin/Constants/passOverlay.glsl
+++ b/Source/Shaders/Builtin/Constants/passOverlay.glsl
@@ -6,4 +6,4 @@
*
* @see czm_pass
*/
-const float czm_passOverlay = 9.0;
+const float czm_passOverlay = 10.0;
diff --git a/Source/Shaders/Builtin/Constants/passVoxels.glsl b/Source/Shaders/Builtin/Constants/passVoxels.glsl
new file mode 100644
index 00000000000..80c50735e88
--- /dev/null
+++ b/Source/Shaders/Builtin/Constants/passVoxels.glsl
@@ -0,0 +1,9 @@
+/**
+ * The automatic GLSL constant for {@link Pass#VOXELS}
+ *
+ * @name czm_passVoxels
+ * @glslConstant
+ *
+ * @see czm_pass
+ */
+const float czm_passVoxels = 9.0;
diff --git a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl
index 00bdd392994..b049c51f4ac 100644
--- a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl
+++ b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl
@@ -81,7 +81,7 @@ vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate)
vec4 czm_windowToEyeCoordinates(vec2 fragmentCoordinateXY, float depthOrLogDepth)
{
// See reverseLogDepth.glsl. This is separate to re-use the pow.
-#ifdef LOG_DEPTH
+#if defined(LOG_DEPTH) || defined(LOG_DEPTH_READ_ONLY)
float near = czm_currentFrustum.x;
float far = czm_currentFrustum.y;
float log2Depth = depthOrLogDepth * czm_log2FarDepthFromNearPlusOne;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
new file mode 100644
index 00000000000..3ff9e885c75
--- /dev/null
+++ b/Source/Shaders/VoxelFS.glsl
@@ -0,0 +1,1484 @@
+// world space: Cartesian WGS84
+// local space: Cartesian [-0.5, 0.5] aligned with shape.
+// For box, the origin is the center of the box, and the six sides sit on the planes x = -0.5, x = 0.5 etc.
+// For cylinder, the origin is the center of the cylinder with the cylinder enclosed by the [-0.5, 0.5] box on xy-plane. Positive x-axis points to theta = 0. The top and bottom caps sit at planes z = -0.5, z = 0.5. Positive y points to theta = pi/2
+// For ellipsoid, the origin is the center of the ellipsoid. The maximum height of the ellipsoid touches -0.5, 0.5 in xyz directions.
+// intersection space: local space times 2 to be [-1, 1]. Used for ray intersection calculation
+// UV space: local space plus 0.5 to be [0, 1].
+// shape space: In the coordinate system of the shape [0, 1]
+// For box, this is the same as UV space
+// For cylinder, the coordinate system is (radius, theta, z). theta = 0 is aligned with x axis
+// For ellipsoid, the coordinate system is (longitude, latitude, height). where 0 is the minimum value in each dimension, and 1 is the max.
+
+
+// TODO is this necessary? Or should it go somewhere else?
+precision highp int;
+
+// Defines that are filled in from VoxelPrimitive.js
+// #define METADATA_COUNT XYZ
+// #define SAMPLE_COUNT XYZ
+// #define NEAREST_SAMPLING
+
+// Uniforms that are filled in from VoxelPrimitive.js
+// uniform sampler2D u_megatextureTextures[METADATA_COUNT];
+
+// Functions that are filled in from VoxelPrimitive.js
+// Attributes sampleFrom2DMegatextureAtUv(vec2 uv);
+// Attributes clearAttributes();
+// Attributes sumAttributes(Attributes attributesA, Attributes attributesB);
+// Attributes mixAttributes(Attributes attributesA, Attributes attributesB, float mixFactor);
+// void setMinMaxAttributes(inout Voxel voxel);
+
+#define OCTREE_MAX_LEVELS 32
+
+#define OCTREE_FLAG_INTERNAL 0
+#define OCTREE_FLAG_LEAF 1
+#define OCTREE_FLAG_PACKED_LEAF_FROM_PARENT 2
+
+struct OctreeNodeData {
+ int data;
+ int flag;
+};
+
+struct SampleData {
+ int megatextureIndex;
+ int levelsAbove;
+ #if (SAMPLE_COUNT > 1)
+ float weight;
+ #endif
+};
+
+#if defined(SHAPE_ELLIPSOID)
+uniform float u_ellipsoidHeightDifferenceUv;
+uniform vec3 u_ellipsoidOuterRadiiLocal; // [0,1]
+uniform vec3 u_ellipsoidInverseRadiiSquaredLocal;
+#endif
+
+// 2D megatexture
+uniform ivec2 u_megatextureSliceDimensions; // number of slices per tile, in two dimensions
+uniform ivec2 u_megatextureTileDimensions; // number of tiles per megatexture, in two dimensions
+uniform vec2 u_megatextureVoxelSizeUv;
+uniform vec2 u_megatextureSliceSizeUv;
+uniform vec2 u_megatextureTileSizeUv;
+
+uniform ivec3 u_dimensions; // does not include padding
+#if defined(PADDING)
+uniform ivec3 u_paddingBefore;
+uniform ivec3 u_paddingAfter;
+#endif
+
+uniform vec4 u_minimumValues[METADATA_COUNT];
+uniform vec4 u_maximumValues[METADATA_COUNT];
+// uniform bool u_voxelQuantization[METADATA_COUNT];
+uniform int u_channelCount[METADATA_COUNT];
+
+uniform float u_stepSize;
+
+uniform sampler2D u_octreeInternalNodeTexture;
+uniform vec2 u_octreeInternalNodeTexelSizeUv;
+uniform int u_octreeInternalNodeTilesPerRow;
+uniform sampler2D u_octreeLeafNodeTexture;
+uniform vec2 u_octreeLeafNodeTexelSizeUv;
+uniform int u_octreeLeafNodeTilesPerRow;
+
+uniform mat4 u_transformPositionViewToUv;
+uniform mat4 u_transformPositionUvToView;
+uniform mat3 u_transformDirectionViewToLocal;
+uniform mat3 u_transformNormalLocalToWorld;
+uniform vec3 u_cameraPositionUv;
+
+#if defined(BOUNDS)
+uniform vec3 u_minBounds; // Bounds from the voxel primitive
+uniform vec3 u_maxBounds; // Bounds from the voxel primitive
+uniform vec3 u_minBoundsUv; // Similar to u_minBounds but relative to UV space [0,1]
+uniform vec3 u_maxBoundsUv; // Similar to u_maxBounds but relative to UV space [0,1]
+uniform vec3 u_inverseBounds; // Equal to 1.0 / (u_maxBounds - u_minBounds)
+uniform vec3 u_inverseBoundsUv; // Equal to 1.0 / (u_maxBoundsUv - u_minBoundsUv)
+#endif
+
+#if defined(CLIPPING_BOUNDS)
+uniform vec3 u_minClippingBounds;
+uniform vec3 u_maxClippingBounds;
+#endif
+
+#if defined(PICKING)
+uniform vec4 u_pickColor;
+#endif
+
+// --------------------------------------------------------
+// Misc math
+// --------------------------------------------------------
+
+#if defined(JITTER)
+#define HASHSCALE1 50.0
+float hash12(vec2 p)
+{
+ vec3 p3 = fract(vec3(p.xyx) * HASHSCALE1);
+ p3 += dot(p3, p3.yzx + 19.19);
+ return fract((p3.x + p3.y) * p3.z);
+}
+#endif
+
+int intMod(int a, int b) {
+ return a - (b * (a / b));
+}
+int intMin(int a, int b) {
+ return a <= b ? a : b;
+}
+int intMax(int a, int b) {
+ return a >= b ? a : b;
+}
+float safeMod(float a, float m) {
+ return mod(mod(a, m) + m, m);
+}
+bool inRange(float v, float minVal, float maxVal) {
+ return clamp(v, minVal, maxVal) == v;
+}
+bool inRange(vec3 v, vec3 minVal, vec3 maxVal) {
+ return clamp(v, minVal, maxVal) == v;
+}
+int normU8_toInt(float value) {
+ return int(value * 255.0);
+}
+int normU8x2_toInt(vec2 value) {
+ return int(value.x * 255.0) + 256 * int(value.y * 255.0);
+}
+float normU8x2_toFloat(vec2 value) {
+ return float(normU8x2_toInt(value)) / 65535.0;
+}
+
+// --------------------------------------------------------
+// Intersection tests, coordinate conversions, etc
+// --------------------------------------------------------
+
+struct Ray
+{
+ vec3 pos;
+ vec3 dir;
+};
+
+const float NoHit = -czm_infinity;
+const float InfHit = czm_infinity;
+
+#if (defined(SHAPE_CYLINDER) && defined(BOUNDS)) || (defined(SHAPE_ELLIPSOID) && defined(BOUNDS))
+vec2 resolveIntersections(vec2 intersections[SHAPE_INTERSECTION_COUNT])
+{
+ // TODO: completely skip shape if both of its Ts are below 0.0?
+ vec2 tEntryExit = vec2(NoHit, NoHit);
+
+ // Sort the intersections from min T to max T with bubble sort.
+ // Note: If this sorting function changes, some of the intersection test may
+ // need to be updated. Search for "bubble sort" to find those areas.
+
+ const int sortPasses = SHAPE_INTERSECTION_COUNT - 1;
+ for (int n = sortPasses; n > 0; --n)
+ {
+ for (int i = 0; i < sortPasses; ++i)
+ {
+ // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to break early instead
+ if (i >= n) { break; }
+
+ vec2 intersect0 = intersections[i];
+ vec2 intersect1 = intersections[i+1];
+
+ float idx0 = intersect0.x;
+ float idx1 = intersect1.x;
+ float t0 = intersect0.y;
+ float t1 = intersect1.y;
+
+ float tmin = min(t0, t1);
+ float tmax = max(t0, t1);
+ float idxmin = tmin == t0 ? idx0 : idx1;
+ float idxmax = tmin == t0 ? idx1 : idx0;
+
+ intersections[i] = vec2(idxmin, tmin);
+ intersections[i+1] = vec2(idxmax, tmax);
+ }
+ }
+
+ int surroundCount = 0;
+ bool surroundIsPositive = false;
+ for (int i = 0; i < SHAPE_INTERSECTION_COUNT; i++)
+ {
+ vec2 entry = intersections[i];
+ float idx = entry.x;
+ float t = entry.y;
+
+ bool currShapeIsPositive = idx <= 1.0;
+ bool enter = mod(idx, 2.0) == 0.0;
+
+ surroundCount += enter ? +1 : -1;
+ surroundIsPositive = currShapeIsPositive ? enter : surroundIsPositive;
+
+ // entering positive or exiting negative
+ if (surroundCount == 1 && surroundIsPositive && enter == currShapeIsPositive) {
+ tEntryExit.x = t;
+ }
+
+ // exiting positive or entering negative after being inside positive
+ // TODO: Can this be simplified?
+ if ((!enter && currShapeIsPositive && surroundCount == 0) || (enter && !currShapeIsPositive && surroundCount == 2 && surroundIsPositive)) {
+ tEntryExit.y = t;
+
+ // entry and exit have been found, so the loop can stop
+ break;
+ }
+ }
+ return tEntryExit;
+}
+#endif
+
+#if defined(SHAPE_BOX)
+// Unit cube from [-1, +1]
+vec2 intersectUnitCube(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ vec3 dInv = 1.0 / d;
+ vec3 od = -o * dInv;
+ vec3 t0 = od - dInv;
+ vec3 t1 = od + dInv;
+ vec3 m0 = min(t0, t1);
+ vec3 m1 = max(t0, t1);
+ float tMin = max(max(m0.x, m0.y), m0.z);
+ float tMax = min(min(m1.x, m1.y), m1.z);
+
+ if (tMin >= tMax) {
+ return vec2(NoHit, NoHit);
+ }
+
+ return vec2(tMin, tMax);
+}
+#endif
+
+#if defined(SHAPE_BOX)
+vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float t = -o.z / d.z;
+ vec2 planePos = o.xy + d.xy * t;
+ if (any(greaterThan(abs(planePos), vec2(1.0)))) {
+ return vec2(NoHit, NoHit);
+ }
+
+ return vec2(t, t);
+}
+#endif
+
+#if defined(SHAPE_BOX)
+vec2 intersectBoxShape(Ray ray)
+{
+ #if defined(BOUNDS)
+ vec3 pos = 0.5 * (u_minBounds + u_maxBounds);
+ vec3 scale = 0.5 * (u_maxBounds - u_minBounds);
+
+ if (any(equal(scale, vec3(0.0)))) {
+ // Transform the ray into unit space on Z plane
+ Ray flatRay;
+ if (scale.x == 0.0) {
+ flatRay = Ray(
+ (ray.pos.yzx - pos.yzx) / vec3(scale.yz, 1.0),
+ ray.dir.yzx / vec3(scale.yz, 1.0)
+ );
+ } else if (scale.y == 0.0) {
+ flatRay = Ray(
+ (ray.pos.xzy - pos.xzy) / vec3(scale.xz, 1.0),
+ ray.dir.xzy / vec3(scale.xz, 1.0)
+ );
+ } else if (scale.z == 0.0) {
+ flatRay = Ray(
+ (ray.pos.xyz - pos.xyz) / vec3(scale.xy, 1.0),
+ ray.dir.xyz / vec3(scale.xy, 1.0)
+ );
+ }
+ return intersectUnitSquare(flatRay);
+ } else {
+ // Transform the ray into "unit space"
+ Ray unitRay = Ray((ray.pos - pos) / scale, ray.dir / scale);
+ return intersectUnitCube(unitRay);
+ }
+ #else
+ return intersectUnitCube(ray);
+ #endif
+}
+#endif
+
+#if (defined(SHAPE_CYLINDER) && (defined(BOUNDS_2_MIN) || defined(BOUNDS_2_MAX))) || (defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)))
+vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
+{
+ vec2 o = ray.pos.xy;
+ vec2 d = ray.dir.xy;
+ vec2 n1 = vec2(sin(minAngle), -cos(minAngle));
+ vec2 n2 = vec2(-sin(maxAngle), cos(maxAngle));
+
+ float a1 = dot(o, n1);
+ float a2 = dot(o, n2);
+ float b1 = dot(d, n1);
+ float b2 = dot(d, n2);
+
+ float t1 = -a1 / b1;
+ float t2 = -a2 / b2;
+ float s1 = sign(a1);
+ float s2 = sign(a2);
+
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+ float smin = tmin == t1 ? s1 : s2;
+ float smax = tmin == t1 ? s2 : s1;
+
+ bool e = tmin >= 0.0;
+ bool f = tmax >= 0.0;
+ bool g = smin >= 0.0;
+ bool h = smax >= 0.0;
+
+ // if () return vec2(tmin, tmax);
+ // else if () return vec2(NoHitNeg, tmin);
+ // else if () return vec2(NoHitNeg, tmax);
+ // else if () return vec2(tmax, NoHitPos);
+ // else return vec2(NoHit, NoHit);
+
+ if (e != g && f == h) return vec2(tmin, tmax);
+ else if (e == g && f == h) return vec2(-InfHit, tmin);
+ else if (e != g && f != h) return vec2(tmax, +InfHit);
+ else return vec2(NoHit, NoHit);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+vec2 intersectUnitCylinder(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float a = dot(d.xy, d.xy);
+ float b = dot(o.xy, d.xy);
+ float c = dot(o.xy, o.xy) - 1.0;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NoHit, NoHit);
+ }
+
+ det = sqrt(det);
+ float ta = (-b - det) / a;
+ float tb = (-b + det) / a;
+ float t1 = min(ta, tb);
+ float t2 = max(ta, tb);
+
+ float z1 = o.z + t1 * d.z;
+ float z2 = o.z + t2 * d.z;
+
+ if (abs(z1) >= 1.0)
+ {
+ float tCap = (sign(z1) - o.z) / d.z;
+ t1 = abs(b + a * tCap) < det ? tCap : NoHit;
+ }
+
+ if (abs(z2) >= 1.0)
+ {
+ float tCap = (sign(z2) - o.z) / d.z;
+ t2 = abs(b + a * tCap) < det ? tCap : NoHit;
+ }
+
+ return vec2(t1, t2);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+vec2 intersectUnitCircle(Ray ray) {
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float t = -o.z / d.z;
+ vec2 zPlanePos = o.xy + d.xy * t;
+ float distSqr = dot(zPlanePos, zPlanePos);
+
+ if (distSqr > 1.0) {
+ return vec2(NoHit, NoHit);
+ }
+
+ return vec2(t, t);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER) && defined(BOUNDS_0_MIN)
+vec2 intersectInfiniteUnitCylinder(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float a = dot(d.xy, d.xy);
+ float b = dot(o.xy, d.xy);
+ float c = dot(o.xy, o.xy) - 1.0;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NoHit, NoHit);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+vec2 intersectCylinderShape(Ray ray)
+{
+ #if !defined(BOUNDS)
+ return intersectUnitCylinder(ray);
+ #else
+ float minRadius = u_minBounds.x; // [0,1]
+ float maxRadius = u_maxBounds.x; // [0,1]
+ float minHeight = u_minBounds.y; // [-1,+1]
+ float maxHeight = u_maxBounds.y; // [-1,+1]
+ float minAngle = u_minBounds.z; // [-pi,+pi]
+ float maxAngle = u_maxBounds.z; // [-pi,+pi]
+
+ float posZ = 0.5 * (minHeight + maxHeight);
+ vec3 pos = vec3(0.0, 0.0, posZ);
+ float scaleZ = 0.5 * (maxHeight - minHeight);
+
+ vec2 outerIntersect;
+
+ // TODO: use define instead of branch
+ if (scaleZ == 0.0) {
+ vec3 outerScale = vec3(maxRadius, maxRadius, 1.0);
+ Ray outerRay = Ray((ray.pos - pos) / outerScale, ray.dir / outerScale);
+ outerIntersect = intersectUnitCircle(outerRay);
+ } else {
+ vec3 outerScale = vec3(maxRadius, maxRadius, scaleZ);
+ Ray outerRay = Ray((ray.pos - pos) / outerScale, ray.dir / outerScale);
+ outerIntersect = intersectUnitCylinder(outerRay);
+ }
+
+ if (outerIntersect == vec2(NoHit, NoHit)) {
+ return vec2(NoHit, NoHit);
+ }
+
+ vec2 intersections[SHAPE_INTERSECTION_COUNT];
+ intersections[0] = vec2(float(0), outerIntersect.x);
+ intersections[1] = vec2(float(1), outerIntersect.y);
+
+ #if defined(BOUNDS_0_MIN)
+ vec3 innerScale = vec3(minRadius, minRadius, 1.0);
+ Ray innerRay = Ray((ray.pos - pos) / innerScale, ray.dir / innerScale);
+ vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
+
+ // TODO: use define instead of branch
+ if (minRadius != maxRadius) {
+ intersections[2] = vec2(float(2), innerIntersect.x);
+ intersections[3] = vec2(float(3), innerIntersect.y);
+ } else {
+ // When the cylinder is perfectly thin it's necessary to sandwich the
+ // inner cylinder intersection inside the outer cylinder intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the cylinder to be invisible because it will think the ray
+ // is still inside the inner (negative) cylinder after exiting the
+ // outer (positive) cylinder.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If resolveIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+
+ intersections[0] = vec2(float(0), outerIntersect.x);
+ intersections[1] = vec2(float(2), innerIntersect.x);
+ intersections[2] = vec2(float(3), innerIntersect.y);
+ intersections[3] = vec2(float(1), outerIntersect.y);
+ }
+ #endif
+
+ #if defined(BOUNDS_2_MIN) || defined(BOUNDS_2_MAX)
+ vec2 wedgeIntersect = intersectWedge(ray, minAngle, maxAngle);
+ intersections[BOUNDS_2_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_2_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
+ intersections[BOUNDS_2_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_2_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
+ #endif
+
+ return resolveIntersections(intersections);
+ #endif
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+vec2 intersectUnitSphere(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float b = dot(d, o);
+ float c = dot(o, o) - 1.0;
+ float det = b * b - c;
+
+ if (det < 0.0) {
+ return vec2(NoHit, NoHit);
+ }
+
+ det = sqrt(det);
+ float t1 = -b - det;
+ float t2 = -b + det;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && defined(BOUNDS_2_MIN)
+vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float a = dot(d, d);
+ float b = dot(d, o);
+ float c = dot(o, o) - 1.0;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NoHit, NoHit);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_1_MIN) || defined(BOUNDS_1_MAX))
+// TODO: can angle and direction be folded into the same parameter
+vec2 intersectUncappedCone(Ray ray, float angle, float direction)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ float s = direction;
+ float h = max(0.01, angle); // float fix
+
+ float hh = h * h;
+ float ds = d[2] * s;
+ float os = o[2] * s;
+ float od = dot(o, d);
+ float oo = dot(o, o);
+
+ float a = ds * ds - hh;
+ float b = ds * os - od * hh;
+ float c = os * os - oo * hh;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NoHit, NoHit);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ float h1 = (o[2] + tmin * d[2]) * s;
+ float h2 = (o[2] + tmax * d[2]) * s;
+
+ if (h1 < 0.0 && h2 < 0.0) {
+ return vec2(NoHit, NoHit);
+ }
+
+ else if (h1 < 0.0) return vec2(tmax, NoHitPos);
+ else if (h2 < 0.0) return vec2(NoHitNeg, tmin);
+ else return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && defined(BOUNDS)
+vec2 intersectClippedEllipsoid(Ray ray, vec3 minBounds, vec3 maxBounds)
+{
+ float lonMin = minBounds.x + 0.5 * czm_pi; // [-pi,+pi]
+ float lonMax = maxBounds.x + 0.5 * czm_pi; // [-pi,+pi]
+ float latMin = minBounds.y; // [-halfPi,+halfPi]
+ float latMax = maxBounds.y; // [-halfPi,+halfPi]
+ float heightMin = minBounds.z; // [-inf,+inf]
+ float heightMax = maxBounds.z; // [-inf,+inf]
+
+ vec2 outerIntersect = intersectUnitSphere(ray);
+ if (outerIntersect == vec2(NoHit, NoHit)) {
+ return vec2(NoHit, NoHit);
+ }
+
+ float intersections[SHAPE_INTERSECTION_COUNT];
+ intersections[BOUNDS_2_MAX_IDX * 2 + 0] = outerIntersect.x;
+ intersections[BOUNDS_2_MAX_IDX * 2 + 1] = outerIntersect.y;
+
+ #if defined(BOUNDS_2_MIN)
+ float innerScale = heightMin;
+ Ray innerRay = Ray(ray.pos / innerScale, ray.dir / innerScale);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ intersections[BOUNDS_2_MIN_IDX * 2 + 0] = innerIntersect.x;
+ intersections[BOUNDS_2_MIN_IDX * 2 + 1] = innerIntersect.y;
+ #endif
+
+ #if defined(BOUNDS_1_MIN)
+ vec2 botConeIntersect = intersectUncappedCone(ray, abs(latMin), sign(latMin));
+ intersections[BOUNDS_1_MIN_IDX * 2 + 0] = botConeIntersect.x;
+ intersections[BOUNDS_1_MIN_IDX * 2 + 1] = botConeIntersect.y;
+ #endif
+
+ #if defined(BOUNDS_1_MAX)
+ vec2 topConeIntersect = intersectUncappedCone(ray, abs(latMax), sign(latMax));
+ intersections[BOUNDS_1_MAX_IDX * 2 + 0] = topConeIntersect.x;
+ intersections[BOUNDS_1_MAX_IDX * 2 + 1] = topConeIntersect.y;
+ #endif
+
+ #if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
+ vec3 planeNormal1 = -vec3(cos(lonMin), sin(lonMin), 0.0);
+ vec3 planeNormal2 = vec3(cos(lonMax), sin(lonMax), 0.0);
+ vec2 wedgeIntersect = intersectWedge(ray, planeNormal1, planeNormal2);
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = wedgeIntersect.x;
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = wedgeIntersect.y;
+ #endif
+
+ return resolveIntersections(intersections);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+// robust iterative solution without trig functions
+// https://github.com/0xfaded/ellipse_demo/issues/1
+// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
+
+float ellipseDistanceIterative (vec2 p, in vec2 ab) {
+ float px = abs(p[0]);
+ float py = abs(p[1]);
+
+ float tx = 0.707;
+ float ty = 0.707;
+
+ float a = ab.x;
+ float b = ab.y;
+
+ for (int i = 0; i < 3; i++) {
+ float x = a * tx;
+ float y = b * ty;
+
+ float ex = (a*a - b*b) * pow(tx, 3.0) / a;
+ float ey = (b*b - a*a) * pow(ty, 3.0) / b;
+
+ float rx = x - ex;
+ float ry = y - ey;
+
+ float qx = px - ex;
+ float qy = py - ey;
+
+ float r = sqrt(ry * ry + rx * rx);
+ float q = sqrt(qy * qy + qx * qx);
+
+ tx = clamp((qx * r / q + ex) / a, 0.0, 1.0);
+ ty = clamp((qy * r / q + ey) / b, 0.0, 1.0);
+ float t = sqrt(ty * ty + tx * tx);
+ tx /= t;
+ ty /= t;
+ }
+
+ float cX = a * tx;
+ float cY = b * ty;
+ vec2 pos = vec2(cX * sign(p[0]), cY * sign(p[1]));
+ return length(pos - p) * sign(py - cY);
+}
+#endif
+
+#if defined(SHAPE_BOX)
+vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
+ return positionUv;
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
+ // 1) Convert positionUv [0,1] to unit space [-1, +1] in ellipsoid scale space.
+ // 2) Convert to non-ellipsoid space. Max ellipsoid axis has value 1, anything shorter is < 1.
+ // 3) Convert 3d position to 2D point relative to ellipse (since radii.x and radii.y are assumed to be equal for WGS84).
+ // 4) Find closest distance. if distance > 1, it's outside the outer shell, if distance < u_ellipsoidMinimumHeightUv, it's inside the inner shell.
+ // 5) Compute geodetic surface normal.
+ // 6) Compute longitude and latitude from geodetic surface normal.
+
+ vec3 posLocal = positionUv * 2.0 - 1.0; // 1
+ vec3 pos3D = posLocal * u_ellipsoidOuterRadiiLocal; // 2
+ vec2 pos2D = vec2(length(pos3D.xy), pos3D.z); // 3
+ float dist = ellipseDistanceIterative(pos2D, u_ellipsoidOuterRadiiLocal.xz); // 4
+ vec3 normal = normalize(pos3D * u_ellipsoidInverseRadiiSquaredLocal); // 5
+ float longitude = atan(normal.y, normal.x); // 6
+ float latitude = asin(normal.z); // 6
+
+ #if defined(BOUNDS)
+ float longitudeMin = u_minBounds.x;
+ float longitudeMax = u_maxBounds.x;
+ float latitudeMin = u_minBounds.x;
+ float latitudeMax = u_minBounds.y;
+ if (longitudeMin > longitudeMax) {
+ longitudeMin -= czm_twoPi;
+ if (longitude > longitudeMax) {
+ longitude -= czm_twoPi;
+ }
+ }
+ float shapeX = (longitude - longitudeMin) * u_boundsLengthInverse.x; // [0, 1]
+ float shapeY = (latitude - latitudeMin) * u_boundsLengthInverse.y; // [0, 1]
+ #else
+ float shapeX = (longitude / czm_pi) * 0.5 + 0.5;
+ float shapeY = (latitude / czm_piOverTwo) * 0.5 + 0.5;
+ #endif
+
+ float distMax = 0.0;
+ float distMin = -u_ellipsoidHeightDifferenceUv;
+ float shapeZ = (dist - distMin) / (distMax - distMin);
+ return vec3(shapeX, shapeY, shapeZ);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
+ vec3 positionLocal = positionUv * 2.0 - 1.0; // [-1,+1]
+ float radius = length(positionLocal.xy); // [0,1]
+ float height = positionUv.z; // [0,1]
+ float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
+ return vec3(radius, height, angle);
+}
+#endif
+
+vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
+ #if defined(SHAPE_BOX)
+ vec3 positionShape = transformFromUvToBoxSpace(positionUv);
+ #elif defined(SHAPE_ELLIPSOID)
+ vec3 positionShape = transformFromUvToEllipsoidSpace(positionUv);
+ #elif defined(SHAPE_CYLINDER)
+ vec3 positionShape = transformFromUvToCylinderSpace(positionUv);
+ #endif
+
+ #if defined(BOUNDS)
+ positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
+ // TODO: This breaks down when minBounds == maxBounds. To fix it, this
+ // function would have to know if ray is intersecting the front or back of a shape
+ // and set the shape space position to 1 (front) or 0 (back) accordingly.
+ #endif
+
+ return positionShape;
+}
+
+#if defined(SHAPE_ELLIPSOID)
+vec3 geodeticSurfaceNormalCartographic(float longitude, float latitude) {
+ float cosLatitude = cos(latitude);
+ float x = cosLatitude * cos(longitude);
+ float y = cosLatitude * sin(longitude);
+ float z = sin(latitude);
+ return normalize(vec3(x, y, z));
+}
+vec3 cartographicToCartesianUv(float longitude, float latitude, float height) {
+ vec3 normal = geodeticSurfaceNormalCartographic(longitude, latitude);
+ vec3 k = normal * u_ellipsoidOuterRadiiLocal * u_ellipsoidOuterRadiiLocal;
+ k /= sqrt(dot(normal, k));
+ vec3 final = normal * height + k;
+ return final * 0.5 + 0.5;
+}
+#endif
+
+vec3 transformFromShapeSpaceToUv(in vec3 positionUvShapeSpace) {
+ #if defined(SHAPE_CYLINDER)
+ float dist = positionUvShapeSpace.x;
+ float angle = czm_twoPi * safeMod(positionUvShapeSpace.y, 1.0);
+ float slice = positionUvShapeSpace.z;
+ float x = 0.5 + 0.5 * dist * cos(angle);
+ float y = 0.5 + 0.5 * dist * sin(angle);
+ float z = slice;
+ return vec3(x, y, z);
+ #elif defined(SHAPE_ELLIPSOID)
+ #if defined(BOUNDS)
+ float longitudeMin = u_minBounds.x;
+ float longitudeMax = u_maxBounds.x;
+ float latitudeMin = u_minBounds.y;
+ float latitudeMax = u_maxBounds.y;
+ float longitude = mix(longitudeMin, longitudeMax, positionUvShapeSpace.x);
+ float latitude = mix(latitudeMin, latitudeMax, positionUvShapeSpace.y);
+ float height = mix(-u_ellipsoidHeightDifferenceUv, 0.0, positionUvShapeSpace.z);
+ #else
+ float longitude = positionUvShapeSpace.x * czm_twoPi - czm_pi;
+ float latitude = positionUvShapeSpace.y * czm_pi - czm_piOverTwo;
+ float height = positionUvShapeSpace.z;
+ #endif
+ return cartographicToCartesianUv(longitude, latitude, height);
+ #else
+ return positionUvShapeSpace;
+ #endif
+}
+
+// --------------------------------------------------------
+// Megatexture
+// --------------------------------------------------------
+
+#if defined(MEGATEXTURE_IS_3D)
+// TODO: 3D textures have not been implemented yet
+
+void sampleFrom3DMegatextureAtUv(vec3 uv, out Attributes attributes)
+{
+ // Looping over the sampler array was causing strange rendering artifacts even though the shader compiled fine.
+ // Unroling the for loop fixed the problem.
+
+ // for (int i = 0; i < METADATA_COUNT; i++)
+ // {
+ // samples[i] = texture3D(u_megatextureTextures[i], uv);
+ // }
+
+ #if (METADATA_COUNT >= 1)
+ samples[0] = texture3D(u_megatextureTextures[0], uv);
+ #endif
+ #if (METADATA_COUNT >= 2)
+ samples[1] = texture3D(u_megatextureTextures[1], uv);
+ #endif
+ #if (METADATA_COUNT >= 3)
+ samples[2] = texture3D(u_megatextureTextures[2], uv);
+ #endif
+ #if (METADATA_COUNT >= 4)
+ samples[3] = texture3D(u_megatextureTextures[3], uv);
+ #endif
+
+ decodeTextureSamples(samples);
+}
+
+// TODO: this function has not been implemented
+vec3 indexToUv3D(int index, ivec3 dimensions, vec3 uvScale)
+{
+ return vec3(0.0);
+}
+
+// TODO: this function has not been tested
+void sampleFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex, out Attributes attributes)
+{
+ // Tile location
+ vec3 tileUvOffset = indexToUv3d(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
+
+ // Voxel location
+ vec3 voxelUvOffset = clamp(voxelCoord, vec3(0.5), vec3(voxelDims) - vec2(0.5)) * u_megatextureVoxelSizeUv;
+
+ // Final location in the megatexture
+ vec3 uv = tileUvOffset + voxelUvOffset;
+
+ for (int i = 0; i < METADATA_COUNT; i++) {
+ vec4 sample = texture3D(u_megatextureTextures[i], uv);
+ samples[i] = decodeTextureSample(sample);
+ }
+}
+
+#else // MEGATEXTURE_IS_2D
+/*
+ How 3D data is stored in a 2D megatexture
+
+ 2D megatexture with a single 2x2x2 voxel tile:
+ The tile is split into two "slices" by Z:
+
+ 0 1 2 3
+ +---+---+---+---+
+ | | | | | 3
+ +---+---+---+---+
+ | | | | | 2
+ +---+---+---+---+
+ |010|110|011|111| 1
+ +---+---+---+---+
+ |000|100|001|101| 0
+ +---+---+---+---+
+
+ (The megatexture likes to be power of two even if it means some empty space)
+
+ When the 3D coordinate's Z value is between two slices:
+
+ 2 +---+
+ |001|
+ 1 +-z-+
+ |000|
+ 0 +---+
+
+ The interpolation between the bottom and the top voxel is 0.5
+ More generally, the interpolation is: fract(coord.z - 0.5)
+*/
+
+vec2 indexToUv2D(int index, ivec2 dimensions, vec2 uvScale) {
+ int indexX = intMod(index, dimensions.x);
+ int indexY = index / dimensions.x;
+ return vec2(indexX, indexY) * uvScale;
+}
+Attributes sampleFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
+{
+ #if defined(NEAREST_SAMPLING)
+ // Round to the center of the nearest voxel
+ voxelCoord = floor(voxelCoord) + vec3(0.5);
+ #endif
+
+ // Tile location
+ vec2 tileUvOffset = indexToUv2D(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
+
+ // Slice locations
+ float slice = voxelCoord.z - 0.5;
+ float sliceLerp = fract(slice);
+ int sliceIndex = int(floor(slice));
+ int sliceIndex0 = intMax(sliceIndex, 0);
+ int sliceIndex1 = intMin(sliceIndex + 1, voxelDims.z - 1);
+ vec2 sliceUvOffset0 = indexToUv2D(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
+ vec2 sliceUvOffset1 = indexToUv2D(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
+
+ // Voxel location
+ vec2 voxelUvOffset = clamp(voxelCoord.xy, vec2(0.5), vec2(voxelDims.xy) - vec2(0.5)) * u_megatextureVoxelSizeUv;
+
+ // Final location in the megatexture
+ vec2 uv0 = tileUvOffset + sliceUvOffset0 + voxelUvOffset;
+ vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
+
+ #if defined(NEAREST_SAMPLING)
+ return sampleFrom2DMegatextureAtUv(uv0);
+ #else
+ Attributes attributes0 = sampleFrom2DMegatextureAtUv(uv0);
+ Attributes attributes1 = sampleFrom2DMegatextureAtUv(uv1);
+ return mixAttributes(attributes0, attributes1, sliceLerp);
+ #endif
+}
+#endif
+
+Attributes sampleFromMegatextureAtTileUv(vec3 tileUv, int tileIndex) {
+ vec3 voxelCoord = tileUv * vec3(u_dimensions);
+ ivec3 dimensions = u_dimensions;
+
+ #if defined(PADDING)
+ dimensions += u_paddingBefore + u_paddingAfter;
+ voxelCoord += vec3(u_paddingBefore);
+ #endif
+
+ #if defined(MEGATEXTURE_IS_3D)
+ return sampleFrom3DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
+ #else
+ return sampleFrom2DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
+ #endif
+}
+
+// --------------------------------------------------------
+// Octree traversal
+// --------------------------------------------------------
+
+void getOctreeLeafData(OctreeNodeData data, inout SampleData sampleDatas[SAMPLE_COUNT]) {
+ #if (SAMPLE_COUNT == 1)
+ sampleDatas[0].megatextureIndex = data.data;
+ sampleDatas[0].levelsAbove = data.flag == OCTREE_FLAG_PACKED_LEAF_FROM_PARENT ? 1 : 0;
+ #else
+ int leafIndex = data.data;
+ int leafNodeTexelCount = 2;
+ // Adding 0.5 moves to the center of the texel
+ float leafCoordXStart = float(intMod(leafIndex, u_octreeLeafNodeTilesPerRow) * leafNodeTexelCount) + 0.5;
+ float leafCoordY = float(leafIndex / u_octreeLeafNodeTilesPerRow) + 0.5;
+
+ vec2 leafUv0 = u_octreeLeafNodeTexelSizeUv * vec2(leafCoordXStart + 0.0, leafCoordY);
+ vec2 leafUv1 = u_octreeLeafNodeTexelSizeUv * vec2(leafCoordXStart + 1.0, leafCoordY);
+ vec4 leafData0 = texture2D(u_octreeLeafNodeTexture, leafUv0);
+ vec4 leafData1 = texture2D(u_octreeLeafNodeTexture, leafUv1);
+
+ float lerp = normU8x2_toFloat(leafData0.xy);
+
+ sampleDatas[0].megatextureIndex = normU8x2_toInt(leafData1.xy);
+ sampleDatas[1].megatextureIndex = normU8x2_toInt(leafData1.zw);
+ sampleDatas[0].levelsAbove = normU8_toInt(leafData0.z);
+ sampleDatas[1].levelsAbove = normU8_toInt(leafData0.w);
+ sampleDatas[0].weight = 1.0 - lerp;
+ sampleDatas[1].weight = lerp;
+ #endif
+}
+
+OctreeNodeData getOctreeRootData() {
+ vec4 rootData = texture2D(u_octreeInternalNodeTexture, vec2(0.0));
+
+ OctreeNodeData data;
+ data.data = normU8x2_toInt(rootData.xy);
+ data.flag = normU8x2_toInt(rootData.zw);
+ return data;
+}
+
+OctreeNodeData getOctreeChildData(int parentOctreeIndex, ivec3 childCoord) {
+ int childIndex = childCoord.z * 4 + childCoord.y * 2 + childCoord.x;
+ int octreeCoordX = intMod(parentOctreeIndex, u_octreeInternalNodeTilesPerRow) * 9 + 1 + childIndex;
+ int octreeCoordY = parentOctreeIndex / u_octreeInternalNodeTilesPerRow;
+ vec2 octreeUv = u_octreeInternalNodeTexelSizeUv * vec2(float(octreeCoordX) + 0.5, float(octreeCoordY) + 0.5);
+ vec4 childData = texture2D(u_octreeInternalNodeTexture, octreeUv);
+
+ OctreeNodeData data;
+ data.data = normU8x2_toInt(childData.xy);
+ data.flag = normU8x2_toInt(childData.zw);
+ return data;
+}
+
+int getOctreeParentIndex(int octreeIndex) {
+ int octreeCoordX = intMod(octreeIndex, u_octreeInternalNodeTilesPerRow) * 9;
+ int octreeCoordY = octreeIndex / u_octreeInternalNodeTilesPerRow;
+ vec2 octreeUv = u_octreeInternalNodeTexelSizeUv * vec2(float(octreeCoordX) + 0.5, float(octreeCoordY) + 0.5);
+ vec4 parentData = texture2D(u_octreeInternalNodeTexture, octreeUv);
+ int parentOctreeIndex = normU8x2_toInt(parentData.xy);
+ return parentOctreeIndex;
+}
+
+void traverseOctreeDownwards(in vec3 positionUv, inout ivec4 octreeCoords, inout int parentOctreeIndex, out SampleData sampleDatas[SAMPLE_COUNT]) {
+ float sizeAtLevel = 1.0 / pow(2.0, float(octreeCoords.w));
+ vec3 start = vec3(octreeCoords.xyz) * sizeAtLevel;
+ vec3 end = start + vec3(sizeAtLevel);
+
+ for (int i = 0; i < OCTREE_MAX_LEVELS; i++) {
+ // Find out which octree child contains the position
+ // 0 if before center, 1 if after
+ vec3 center = 0.5 * (start + end);
+ vec3 childCoord = step(center, positionUv);
+
+ // Get octree coords for the next level down
+ octreeCoords.xyz = octreeCoords.xyz * 2 + ivec3(childCoord);
+ octreeCoords.w += 1;
+
+ OctreeNodeData childData = getOctreeChildData(parentOctreeIndex, ivec3(childCoord));
+
+ if (childData.flag == OCTREE_FLAG_INTERNAL) {
+ // keep going deeper
+ start = mix(start, center, childCoord);
+ end = mix(center, end, childCoord);
+ parentOctreeIndex = childData.data;
+ } else {
+ getOctreeLeafData(childData, sampleDatas);
+ return;
+ }
+ }
+}
+
+void traverseOctree(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, out float levelStepMult, out ivec4 octreeCoords, out int parentOctreeIndex, out SampleData sampleDatas[SAMPLE_COUNT]) {
+ levelStepMult = 1.0;
+ octreeCoords = ivec4(0);
+ parentOctreeIndex = 0;
+
+ // TODO: is it possible for this to be out of bounds, and does it matter?
+ positionUvShapeSpace = transformFromUvToShapeSpace(positionUv);
+ positionUvLocal = positionUvShapeSpace;
+
+ OctreeNodeData rootData = getOctreeRootData();
+ if (rootData.flag == OCTREE_FLAG_LEAF) {
+ // No child data, only the root tile has data
+ getOctreeLeafData(rootData, sampleDatas);
+ }
+ else
+ {
+ traverseOctreeDownwards(positionUvShapeSpace, octreeCoords, parentOctreeIndex, sampleDatas);
+ levelStepMult = 1.0 / pow(2.0, float(octreeCoords.w));
+ vec3 boxStart = vec3(octreeCoords.xyz) * levelStepMult;
+ positionUvLocal = (positionUvShapeSpace - boxStart) / levelStepMult;
+ }
+}
+
+void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, inout float levelStepMult, inout ivec4 octreeCoords, inout int parentOctreeIndex, inout SampleData sampleDatas[SAMPLE_COUNT]) {
+ float dimAtLevel = pow(2.0, float(octreeCoords.w));
+ positionUvShapeSpace = transformFromUvToShapeSpace(positionUv);
+ positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
+
+ // Note: This code assumes the position is always inside the root tile.
+ bool insideTile = octreeCoords.w == 0 || inRange(positionUvLocal, vec3(0.0), vec3(1.0));
+
+ if (!insideTile)
+ {
+ // Go up tree
+ for (int i = 0; i < OCTREE_MAX_LEVELS; i++)
+ {
+ octreeCoords.xyz /= ivec3(2);
+ octreeCoords.w -= 1;
+ dimAtLevel /= 2.0;
+
+ positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
+ insideTile = octreeCoords.w == 0 || inRange(positionUvLocal, vec3(0.0), vec3(1.0));
+
+ if (!insideTile) {
+ parentOctreeIndex = getOctreeParentIndex(parentOctreeIndex);
+ } else {
+ break;
+ }
+ }
+
+ // Go down tree
+ traverseOctreeDownwards(positionUvShapeSpace, octreeCoords, parentOctreeIndex, sampleDatas);
+ levelStepMult = 1.0 / pow(2.0, float(octreeCoords.w));
+ positionUvLocal = positionUvShapeSpace / levelStepMult - vec3(octreeCoords.xyz);
+ }
+}
+
+// Convert an array of mixed-resolution sample datas to a final weighted sample.
+Attributes getSamplesAtLocalPosition(in vec3 positionUvLocal, in ivec4 octreeCoords, in SampleData sampleDatas[SAMPLE_COUNT]) {
+ // In some cases positionUvLocal goes outside the 0 to 1 bounds, such as when sampling neighbor voxels on the edge of a tile.
+ // This needs to be handled carefully, especially for mixed resolution, or else the wrong part of the tile is read.
+ // https://www.wolframalpha.com/input/?i=sign%28x%29+*+max%280%2C+%28abs%28x-0.5%29-0.5%29%29
+ vec3 overflow = sign(positionUvLocal) * max(abs(positionUvLocal - vec3(0.5)) - vec3(0.5), vec3(0.0));
+ positionUvLocal = clamp(positionUvLocal, vec3(0.0), vec3(1.0 - czm_epsilon6)); // epsilon to avoid fract(1) = 0 situation
+
+ Attributes attributes;
+
+ // When more than one sample is taken the accumulator needs to start at 0
+ #if (SAMPLE_COUNT > 1)
+ attributes = clearAttributes();
+ #endif
+
+ for (int i = 0; i < SAMPLE_COUNT; i++) {
+ SampleData sampleData = sampleDatas[i];
+ vec3 actualUvLocal = positionUvLocal;
+ int levelsAbove = sampleData.levelsAbove;
+ if (levelsAbove > 0) {
+ // Calcuate a new local uv relative to the ancestor tile.
+ float levelsAboveFactor = 1.0 / pow(2.0, float(levelsAbove));
+ actualUvLocal = fract((vec3(octreeCoords.xyz) + positionUvLocal) * levelsAboveFactor) + overflow * levelsAboveFactor;
+ }
+
+ Attributes tempAttributes = sampleFromMegatextureAtTileUv(actualUvLocal, sampleData.megatextureIndex);
+
+ #if (SAMPLE_COUNT == 1)
+ attributes = tempAttributes;
+ #else
+ attributes = sumAttributes(attributes, tempAttributes)
+ #endif
+ }
+ return attributes;
+}
+
+Attributes getSamplesAtPosition(vec3 positionUv, vec4 outOfBoundsValue) {
+ vec3 positionUvShapeSpace;
+ vec3 positionUvLocal;
+ float levelStepMult;
+ ivec4 octreeCoords;
+ int parentOctreeIndex;
+ SampleData sampleDatas[SAMPLE_COUNT];
+ traverseOctree(positionUv, positionUvShapeSpace, positionUvLocal, levelStepMult, octreeCoords, parentOctreeIndex, sampleDatas);
+ return getSamplesAtLocalPosition(positionUvLocal, octreeCoords, sampleDatas);
+}
+Attributes getSamplesAtPosition(vec3 positionUv) {
+ return getSamplesAtPosition(positionUv, vec4(0.0));
+}
+
+// void getNormalAtPosition(ivec4 octreeCoords, vec3 positionUvShapeSpace, vec3 positionUvLocal, SampleData sampleDatas[SAMPLE_COUNT], out vec3 normalLocalSpace[METADATA_COUNT], out vec3 normalWorldSpace[METADATA_COUNT], out vec3 normalViewSpace[METADATA_COUNT], out bool valid[METADATA_COUNT]) {
+// for (int i = 0; i < METADATA_COUNT; i++){
+// valid[i] = true;
+// }
+
+// Attributes attributes;
+// #define USE_SIMPLE_NORMALS
+// #if defined(NEIGHBORS_INCLUDED_ON_TILE_EDGES) || defined(USE_SIMPLE_NORMALS)
+// // There might be small seam artifacts when the edge count is 1 or less.
+// float sampleL[METADATA_COUNT];
+// float sampleR[METADATA_COUNT];
+// float sampleD[METADATA_COUNT];
+// float sampleU[METADATA_COUNT];
+// float sampleB[METADATA_COUNT];
+// float sampleF[METADATA_COUNT];
+// getSamplesAtLocalPosition(positionUvLocal + vec3(-1.0, 0.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
+// for(int i = 0; i < METADATA_COUNT; i++) {
+// sampleL[i] = attributes[i].a;
+// }
+// getSamplesAtLocalPosition(positionUvLocal + vec3(+1.0, 0.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
+// for(int i = 0; i < METADATA_COUNT; i++) {
+// sampleR[i] = attributes[i].a;
+// }
+// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, -1.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
+// for(int i = 0; i < METADATA_COUNT; i++) {
+// sampleD[i] = attributes[i].a;
+// }
+// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, +1.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
+// for(int i = 0; i < METADATA_COUNT; i++) {
+// sampleU[i] = attributes[i].a;
+// }
+// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, 0.0, -1.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
+// for(int i = 0; i < METADATA_COUNT; i++) {
+// sampleB[i] = attributes[i].a;
+// }
+// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, 0.0, +1.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
+// for(int i = 0; i < METADATA_COUNT; i++) {
+// sampleF[i] = attributes[i].a;
+// }
+// #else
+// float dimAtLevel = pow(2.0, float(octreeCoords.w));
+// vec3 voxelSizeShapeSpace = 1.0 / (dimAtLevel * vec3(u_dimensions));
+
+// // There might be small seam artifacts when the edge count is 0
+// float sampleL[METADATA_COUNT];
+// float sampleR[METADATA_COUNT];
+// float sampleD[METADATA_COUNT];
+// float sampleU[METADATA_COUNT];
+// float sampleB[METADATA_COUNT];
+// float sampleF[METADATA_COUNT];
+// getSamplesAtPosition(transformFromShapeSpaceToUv(positionUvShapeSpace - vec3(voxelSizeShapeSpace.x, 0.0, 0.0), attributes));
+// for(int i; i= nonZeroMax) {
+ colorAccum.a = colorAccumTemp.a;
+ colorAccum.rgb += colorAccumTemp.rgb;
+ colorAccumTemp = vec4(0.0);
+ nonZeroCount = 0;
+ }
+ }
+ #else
+ colorAccum += (1.0 - colorAccum.a) * vec4(finalSample.rgb * finalSample.a, finalSample.a);
+ #endif
+
+ // Stop traversing if the alpha has been fully saturated
+ if (colorAccum.a > alphaAccumMax) {
+ colorAccum.a = alphaAccumMax;
+ break;
+ }
+
+ // Keep raymarching
+ currT += stepT;
+ positionUv += stepT * viewDirUv;
+
+ // Check if the ray is occluded by the depth
+ #if defined(DEPTH_TEST)
+ if (currT >= depthT) {
+ break;
+ }
+ #endif
+
+ // Check if the ray has entered empty space. If so, do another intersection test
+ // to see if there is more of the shape to intersect. If there isn't, the raymarch is over.
+ if (currT > endT) {
+ vec2 tEntryExit = intersectShape(positionUv, viewDirUv);
+ if (tEntryExit == vec2(NoHit, NoHit)) {
+ break;
+ }
+ currT += tEntryExit.x;
+ endT += tEntryExit.y;
+ positionUv += tEntryExit.x * viewDirUv;
+ }
+
+ // Traverse the octree from the current ray position.
+ // This is an optimized alternative to traverseOctree that expects the
+ // ray to stay in the same tile on average. Otherwise it will traverse
+ // upwards and back downwards.
+ traverseOctreeFromExisting(positionUv, positionUvShapeSpace, positionUvLocal, levelStepMult, octreeCoords, parentOctreeIndex, sampleDatas);
+ stepT = u_stepSize * levelStepMult;
+ }
+
+ // Convert the alpha from [0,alphaAccumMax] to [0,1]
+ colorAccum.a /= alphaAccumMax;
+
+ #if defined(PICKING)
+ // If alpha is 0.0, there is nothing to pick
+ if (colorAccum.a == 0.0) {
+ discard;
+ }
+ gl_FragColor = u_pickColor;
+ #else
+ gl_FragColor = colorAccum;
+ #endif
+}
\ No newline at end of file
diff --git a/Source/Shaders/VoxelVS.glsl b/Source/Shaders/VoxelVS.glsl
new file mode 100644
index 00000000000..6c29c959b1c
--- /dev/null
+++ b/Source/Shaders/VoxelVS.glsl
@@ -0,0 +1,11 @@
+attribute vec2 position;
+
+uniform vec4 u_ndcSpaceAxisAlignedBoundingBox;
+
+void main() {
+ vec2 aabbMin = u_ndcSpaceAxisAlignedBoundingBox.xy;
+ vec2 aabbMax = u_ndcSpaceAxisAlignedBoundingBox.zw;
+ vec2 translation = 0.5 * (aabbMax + aabbMin);
+ vec2 scale = 0.5 * (aabbMax - aabbMin);
+ gl_Position = vec4(position * scale + translation, 0.0, 1.0);
+}
diff --git a/Source/Widgets/CesiumInspector/CesiumInspector.css b/Source/Widgets/CesiumInspector/CesiumInspector.css
index ca721ca1929..cea597ff95d 100644
--- a/Source/Widgets/CesiumInspector/CesiumInspector.css
+++ b/Source/Widgets/CesiumInspector/CesiumInspector.css
@@ -54,7 +54,7 @@
margin: 5px 0;
font-family: sans-serif;
font-size: 10pt;
- width: 185px;
+ width: 100%;
}
.cesium-cesiumInspector-frustumStatistics {
diff --git a/Source/Widgets/Viewer/Viewer.css b/Source/Widgets/Viewer/Viewer.css
index e5a5644fb3f..bbae808bbdf 100644
--- a/Source/Widgets/Viewer/Viewer.css
+++ b/Source/Widgets/Viewer/Viewer.css
@@ -105,3 +105,14 @@
overflow-y: auto;
overflow-x: hidden;
}
+
+.cesium-viewer-voxelInspectorContainer {
+ display: block;
+ position: absolute;
+ top: 50px;
+ right: 10px;
+ max-height: calc(100% - 120px);
+ box-sizing: border-box;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index a24bdfc313e..5319e2eb7df 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -24,6 +24,7 @@ import computeFlyToLocationForRectangle from "../../Scene/computeFlyToLocationFo
import ImageryLayer from "../../Scene/ImageryLayer.js";
import SceneMode from "../../Scene/SceneMode.js";
import TimeDynamicPointCloud from "../../Scene/TimeDynamicPointCloud.js";
+import VoxelPrimitive from "../../Scene/VoxelPrimitive.js";
import knockout from "../../ThirdParty/knockout.js";
import Animation from "../Animation/Animation.js";
import AnimationViewModel from "../Animation/AnimationViewModel.js";
@@ -2103,14 +2104,11 @@ function zoomToOrFly(that, zoomTarget, options, isFlight) {
return;
}
- //If the zoom target is a Cesium3DTileset
- if (zoomTarget instanceof Cesium3DTileset) {
- that._zoomTarget = zoomTarget;
- return;
- }
-
- //If the zoom target is a TimeDynamicPointCloud
- if (zoomTarget instanceof TimeDynamicPointCloud) {
+ if (
+ zoomTarget instanceof Cesium3DTileset ||
+ zoomTarget instanceof TimeDynamicPointCloud ||
+ zoomTarget instanceof VoxelPrimitive
+ ) {
that._zoomTarget = zoomTarget;
return;
}
@@ -2189,47 +2187,11 @@ function updateZoomTarget(viewer) {
const zoomOptions = defaultValue(viewer._zoomOptions, {});
let options;
- // If zoomTarget was Cesium3DTileset
- if (target instanceof Cesium3DTileset) {
- return target.readyPromise.then(function () {
- const boundingSphere = target.boundingSphere;
- // If offset was originally undefined then give it base value instead of empty object
- if (!defined(zoomOptions.offset)) {
- zoomOptions.offset = new HeadingPitchRange(
- 0.0,
- -0.5,
- boundingSphere.radius
- );
- }
-
- options = {
- offset: zoomOptions.offset,
- duration: zoomOptions.duration,
- maximumHeight: zoomOptions.maximumHeight,
- complete: function () {
- zoomPromise.resolve(true);
- },
- cancel: function () {
- zoomPromise.resolve(false);
- },
- };
-
- if (viewer._zoomIsFlight) {
- camera.flyToBoundingSphere(target.boundingSphere, options);
- } else {
- camera.viewBoundingSphere(boundingSphere, zoomOptions.offset);
- camera.lookAtTransform(Matrix4.IDENTITY);
-
- // Finish the promise
- zoomPromise.resolve(true);
- }
-
- clearZoom(viewer);
- });
- }
-
- // If zoomTarget was TimeDynamicPointCloud
- if (target instanceof TimeDynamicPointCloud) {
+ if (
+ target instanceof Cesium3DTileset ||
+ target instanceof TimeDynamicPointCloud ||
+ target instanceof VoxelPrimitive
+ ) {
return target.readyPromise.then(function () {
const boundingSphere = target.boundingSphere;
// If offset was originally undefined then give it base value instead of empty object
diff --git a/Source/Widgets/Viewer/viewerVoxelInspectorMixin.js b/Source/Widgets/Viewer/viewerVoxelInspectorMixin.js
new file mode 100644
index 00000000000..1c4b963adbe
--- /dev/null
+++ b/Source/Widgets/Viewer/viewerVoxelInspectorMixin.js
@@ -0,0 +1,34 @@
+import Check from "../../Core/Check.js";
+import VoxelInspector from "../VoxelInspector/VoxelInspector.js";
+
+/**
+ * A mixin which adds the {@link VoxelInspector} widget to the {@link Viewer} widget.
+ * Rather than being called directly, this function is normally passed as
+ * a parameter to {@link Viewer#extend}, as shown in the example below.
+ * @function
+ *
+ * @param {Viewer} viewer The viewer instance.
+ *
+ * @example
+ * var viewer = new Cesium.Viewer('cesiumContainer');
+ * viewer.extend(Cesium.viewerVoxelInspectorMixin);
+ */
+function viewerVoxelInspectorMixin(viewer) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("viewer", viewer);
+ //>>includeEnd('debug');
+
+ const container = document.createElement("div");
+ container.className = "cesium-viewer-voxelInspectorContainer";
+ viewer.container.appendChild(container);
+ const voxelInspector = new VoxelInspector(container, viewer.scene);
+
+ Object.defineProperties(viewer, {
+ voxelInspector: {
+ get: function () {
+ return voxelInspector;
+ },
+ },
+ });
+}
+export default viewerVoxelInspectorMixin;
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.css b/Source/Widgets/VoxelInspector/VoxelInspector.css
new file mode 100644
index 00000000000..60cae4fd30d
--- /dev/null
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.css
@@ -0,0 +1,121 @@
+ul.cesium-cesiumInspector-statistics {
+ margin: 0;
+ padding-top: 3px;
+ padding-bottom: 3px;
+}
+
+ul.cesium-cesiumInspector-statistics + ul.cesium-cesiumInspector-statistics {
+ border-top: 1px solid #aaa;
+}
+
+.cesium-cesiumInspector-slider {
+ margin-top: 5px;
+}
+
+.cesium-cesiumInspector-visible {
+ width: 400px;
+ height: auto;
+}
+
+.cesium-cesiumInspector-slider input[type="number"] {
+ text-align: left;
+ background-color: #222;
+ outline: none;
+ border: 1px solid #444;
+ color: #edffff;
+ width: 100px;
+ border-radius: 3px;
+ padding: 1px;
+ margin-left: 10px;
+ cursor: auto;
+}
+
+.cesium-cesiumInspector-slider input[type="number"]::-webkit-outer-spin-button,
+.cesium-cesiumInspector-slider input[type="number"]::-webkit-inner-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+.cesium-cesiumInspector-slider input[type="range"] {
+ margin-left: 5px;
+ vertical-align: middle;
+}
+
+.cesium-cesiumInspector-hide .cesium-cesiumInspector-styleEditor {
+ display: none;
+}
+
+.cesium-cesiumInspector-styleEditor {
+ padding: 10px;
+ border-radius: 5px;
+ background: rgba(48, 51, 54, 0.8);
+ border: 1px solid #444;
+}
+
+.cesium-cesiumInspector-styleEditor textarea {
+ width: 100%;
+ height: 300px;
+ background: transparent;
+ color: #edffff;
+ border: none;
+ padding: 1px;
+ white-space: pre;
+ overflow-wrap: normal;
+ overflow-x: auto;
+}
+
+.cesium-3DTilesInspector {
+ width: 300px;
+ pointer-events: all;
+}
+
+.cesium-3DTilesInspector-statistics {
+ font-size: 11px;
+}
+
+.cesium-3DTilesInspector div,
+.cesium-3DTilesInspector input[type="range"] {
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.cesium-cesiumInspector-error {
+ color: #ff9e9e;
+ overflow: auto;
+}
+
+.cesium-3DTilesInspector .cesium-cesiumInspector-section {
+ margin-top: 3px;
+}
+
+.cesium-3DTilesInspector
+ .cesium-cesiumInspector-sectionHeader
+ + .cesium-cesiumInspector-show {
+ border-top: 1px solid white;
+}
+
+input.cesium-cesiumInspector-url {
+ overflow: hidden;
+ white-space: nowrap;
+ overflow-x: scroll;
+ background-color: transparent;
+ color: white;
+ outline: none;
+ border: none;
+ height: 1em;
+ width: 100%;
+}
+
+.cesium-cesiumInspector .field-group {
+ display: table;
+}
+
+.cesium-cesiumInspector .field-group > label {
+ display: table-cell;
+ font-weight: bold;
+}
+
+.cesium-cesiumInspector .field-group > .field {
+ display: table-cell;
+ width: 100%;
+}
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
new file mode 100644
index 00000000000..1f360e67ef2
--- /dev/null
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -0,0 +1,422 @@
+import CesiumMath from "../../Core/Math.js";
+import Check from "../../Core/Check.js";
+import defaultValue from "../../Core/defaultValue.js";
+import defined from "../../Core/defined.js";
+import destroyObject from "../../Core/destroyObject.js";
+import knockout from "../../ThirdParty/knockout.js";
+import getElement from "../getElement.js";
+import InspectorShared from "../InspectorShared.js";
+import VoxelInspectorViewModel from "./VoxelInspectorViewModel.js";
+import VoxelBoxShape from "../../Scene/VoxelBoxShape.js";
+import VoxelCylinderShape from "../../Scene/VoxelCylinderShape.js";
+import VoxelEllipsoidShape from "../../Scene/VoxelEllipsoidShape.js";
+
+/**
+ * Inspector widget to aid in debugging voxels
+ *
+ * @alias VoxelInspector
+ * @constructor
+ *
+ * @param {Element|String} container The DOM element or ID that will contain the widget.
+ * @param {Scene} scene the Scene instance to use.
+ */
+function VoxelInspector(container, scene) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.defined("container", container);
+ Check.typeOf.object("scene", scene);
+ //>>includeEnd('debug');
+
+ container = getElement(container);
+ const element = document.createElement("div");
+ const viewModel = new VoxelInspectorViewModel(scene);
+
+ const text = document.createElement("div");
+ text.textContent = "Voxel Inspector";
+ text.className = "cesium-cesiumInspector-button";
+ text.setAttribute("data-bind", "click: inspectorVisibleToggle");
+ element.appendChild(text);
+ element.className = "cesium-cesiumInspector cesium-VoxelInspector";
+ element.setAttribute(
+ "data-bind",
+ 'css: { "cesium-cesiumInspector-visible" : inspectorVisible, "cesium-cesiumInspector-hidden" : !inspectorVisible}'
+ );
+ container.appendChild(element);
+
+ const panel = document.createElement("div");
+ panel.className = "cesium-cesiumInspector-dropDown";
+ element.appendChild(panel);
+
+ const createSection = InspectorShared.createSection;
+ const createCheckbox = InspectorShared.createCheckbox;
+
+ // Display
+ const displayPanelContents = createSection(
+ panel,
+ "Display",
+ "displayVisible",
+ "displayVisibleToggle"
+ );
+
+ displayPanelContents.appendChild(createCheckbox("Depth Test", "depthTest"));
+ displayPanelContents.appendChild(createCheckbox("Show", "show"));
+ displayPanelContents.appendChild(
+ createCheckbox("Disable Update", "disableUpdate")
+ );
+ displayPanelContents.appendChild(createCheckbox("Debug Draw", "debugDraw"));
+ displayPanelContents.appendChild(createCheckbox("Jitter", "jitter"));
+ displayPanelContents.appendChild(
+ createCheckbox("Nearest Sampling", "nearestSampling")
+ );
+ displayPanelContents.appendChild(createCheckbox("Despeckle", "despeckle"));
+
+ const screenSpaceErrorContainer = document.createElement("div");
+ screenSpaceErrorContainer.appendChild(
+ makeRangeInput("Screen Space Error", "screenSpaceError", 0, 128)
+ );
+ displayPanelContents.appendChild(screenSpaceErrorContainer);
+
+ const stepSizeContainer = document.createElement("div");
+ stepSizeContainer.appendChild(
+ makeRangeInput("Step Size", "stepSize", 0.0, 2.0)
+ );
+ displayPanelContents.appendChild(stepSizeContainer);
+
+ // Transform
+ const transformPanelContents = createSection(
+ panel,
+ "Transform",
+ "transformVisible",
+ "transformVisibleToggle"
+ );
+
+ const maxTrans = 20000000.0;
+ const maxScale = 20000000.0;
+ const maxAngle = CesiumMath.PI;
+
+ transformPanelContents.appendChild(
+ makeRangeInput("Translation X", "translationX", -maxTrans, +maxTrans)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Translation Y", "translationY", -maxTrans, +maxTrans)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Translation Z", "translationZ", -maxTrans, +maxTrans)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Scale X", "scaleX", -maxScale, +maxScale)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Scale Y", "scaleY", -maxScale, +maxScale)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Scale Z", "scaleZ", -maxScale, +maxScale)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Heading", "angleX", -maxAngle, +maxAngle)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Pitch", "angleY", -maxAngle, +maxAngle)
+ );
+ transformPanelContents.appendChild(
+ makeRangeInput("Roll", "angleZ", -maxAngle, +maxAngle)
+ );
+
+ // Bounds
+ const boundsPanelContents = createSection(
+ panel,
+ "Bounds",
+ "boundsVisible",
+ "boundsVisibleToggle"
+ );
+
+ makeCoordinateRange(
+ "Max X",
+ "Min X",
+ "Max Y",
+ "Min Y",
+ "Max Z",
+ "Min Z",
+ "boundsBoxMaxX",
+ "boundsBoxMinX",
+ "boundsBoxMaxY",
+ "boundsBoxMinY",
+ "boundsBoxMaxZ",
+ "boundsBoxMinZ",
+ VoxelBoxShape.DefaultMinBounds,
+ VoxelBoxShape.DefaultMaxBounds,
+ "shapeIsBox",
+ boundsPanelContents
+ );
+
+ makeCoordinateRange(
+ "Max Longitude",
+ "Min Longitude",
+ "Max Latitude",
+ "Min Latitude",
+ "Max Height",
+ "Min Height",
+ "boundsEllipsoidMaxLongitude",
+ "boundsEllipsoidMinLongitude",
+ "boundsEllipsoidMaxLatitude",
+ "boundsEllipsoidMinLatitude",
+ "boundsEllipsoidMaxHeight",
+ "boundsEllipsoidMinHeight",
+ VoxelEllipsoidShape.DefaultMinBounds,
+ VoxelEllipsoidShape.DefaultMaxBounds,
+ "shapeIsEllipsoid",
+ boundsPanelContents
+ );
+
+ makeCoordinateRange(
+ "Max Radius",
+ "Min Radius",
+ "Max Height",
+ "Min Height",
+ "Max Angle",
+ "Min Angle",
+ "boundsCylinderMaxRadius",
+ "boundsCylinderMinRadius",
+ "boundsCylinderMaxHeight",
+ "boundsCylinderMinHeight",
+ "boundsCylinderMaxAngle",
+ "boundsCylinderMinAngle",
+ VoxelCylinderShape.DefaultMinBounds,
+ VoxelCylinderShape.DefaultMaxBounds,
+ "shapeIsCylinder",
+ boundsPanelContents
+ );
+
+ // Clipping
+ const clippingPanelContents = createSection(
+ panel,
+ "Clipping",
+ "clippingVisible",
+ "clippingVisibleToggle"
+ );
+
+ makeCoordinateRange(
+ "Max X",
+ "Min X",
+ "Max Y",
+ "Min Y",
+ "Max Z",
+ "Min Z",
+ "clippingBoxMaxX",
+ "clippingBoxMinX",
+ "clippingBoxMaxY",
+ "clippingBoxMinY",
+ "clippingBoxMaxZ",
+ "clippingBoxMinZ",
+ VoxelBoxShape.DefaultMinBounds,
+ VoxelBoxShape.DefaultMaxBounds,
+ "shapeIsBox",
+ clippingPanelContents
+ );
+
+ makeCoordinateRange(
+ "Max Longitude",
+ "Min Longitude",
+ "Max Latitude",
+ "Min Latitude",
+ "Max Height",
+ "Min Height",
+ "clippingEllipsoidMaxLongitude",
+ "clippingEllipsoidMinLongitude",
+ "clippingEllipsoidMaxLatitude",
+ "clippingEllipsoidMinLatitude",
+ "clippingEllipsoidMaxHeight",
+ "clippingEllipsoidMinHeight",
+ VoxelEllipsoidShape.DefaultMinBounds,
+ VoxelEllipsoidShape.DefaultMaxBounds,
+ "shapeIsEllipsoid",
+ clippingPanelContents
+ );
+
+ makeCoordinateRange(
+ "Max Radius",
+ "Min Radius",
+ "Max Height",
+ "Min Height",
+ "Max Angle",
+ "Min Angle",
+ "clippingCylinderMaxRadius",
+ "clippingCylinderMinRadius",
+ "clippingCylinderMaxHeight",
+ "clippingCylinderMinHeight",
+ "clippingCylinderMaxAngle",
+ "clippingCylinderMinAngle",
+ VoxelCylinderShape.DefaultMinBounds,
+ VoxelCylinderShape.DefaultMaxBounds,
+ "shapeIsCylinder",
+ clippingPanelContents
+ );
+
+ // Style
+ const stylePanelContents = createSection(
+ panel,
+ "Style",
+ "styleVisible",
+ "styleVisibleToggle"
+ );
+ const stylePanelEditor = document.createElement("div");
+ stylePanelContents.appendChild(stylePanelEditor);
+
+ const styleEditor = document.createElement("textarea");
+ styleEditor.setAttribute(
+ "data-bind",
+ "textInput: styleString, event: { keydown: styleEditorKeyPress }"
+ );
+ stylePanelEditor.className = "cesium-cesiumInspector-styleEditor";
+ stylePanelEditor.appendChild(styleEditor);
+ const compileStyleButton = makeButton("compileStyle", "Compile (Ctrl+Enter)");
+ stylePanelEditor.appendChild(compileStyleButton);
+
+ const compilationText = document.createElement("label");
+ compilationText.style.display = "block";
+ compilationText.setAttribute(
+ "data-bind",
+ "text: styleCompilationMessage, style: {color: styleCompilationSuccess ? 'green' : 'red'}"
+ );
+ stylePanelEditor.appendChild(compilationText);
+
+ knockout.applyBindings(viewModel, element);
+
+ this._viewModel = viewModel;
+ this._container = container;
+ this._element = element;
+ this._panel = panel;
+}
+
+Object.defineProperties(VoxelInspector.prototype, {
+ /**
+ * Gets the view model.
+ * @memberof VoxelInspector.prototype
+ *
+ * @type {VoxelInspectorViewModel}
+ */
+ viewModel: {
+ get: function () {
+ return this._viewModel;
+ },
+ },
+});
+
+/**
+ * @returns {Boolean} true if the object has been destroyed, false otherwise.
+ */
+VoxelInspector.prototype.isDestroyed = function () {
+ return false;
+};
+
+/**
+ * Destroys the widget. Should be called if permanently
+ * removing the widget from layout.
+ */
+VoxelInspector.prototype.destroy = function () {
+ knockout.cleanNode(this._element);
+ this._container.removeChild(this._element);
+ this.viewModel.destroy();
+
+ return destroyObject(this);
+};
+
+function makeRangeInput(text, property, min, max, step, displayProperty) {
+ step = defaultValue(step, 0.01);
+ displayProperty = defaultValue(displayProperty, property);
+ const input = document.createElement("input");
+ input.setAttribute("data-bind", `value: ${displayProperty}`);
+ input.type = "number";
+
+ const slider = document.createElement("input");
+ slider.type = "range";
+ slider.min = min;
+ slider.max = max;
+ slider.step = step;
+ slider.setAttribute("data-bind", `valueUpdate: "input", value: ${property}`);
+
+ const wrapper = document.createElement("div");
+ wrapper.appendChild(slider);
+
+ const container = document.createElement("div");
+ container.className = "cesium-cesiumInspector-slider";
+ container.appendChild(document.createTextNode(text));
+ container.appendChild(input);
+ container.appendChild(wrapper);
+
+ return container;
+}
+
+// function makeTestSlider(text, property, min, max, step, displayProperty) {
+// displayProperty = defaultValue(displayProperty, property);
+// const input = document.createElement('input');
+// // input.setAttribute('data-bind', 'value: ' + displayProperty);
+// input.setAttribute('data-bind', 'attr: {min: 1, max: MaxPage}, value: ' + displayProperty);
+
+// input.type = 'number';
+
+// const slider = document.createElement('input');
+// slider.type = 'range';
+// slider.min = min;
+// slider.max = max;
+// slider.step = step;
+// slider.setAttribute('data-bind', 'valueUpdate: "input", value: ' + property);
+
+// const wrapper = document.createElement('div');
+// wrapper.appendChild(slider);
+
+// const container = document.createElement('div');
+// container.className = 'cesium-cesiumInspector-slider';
+// container.appendChild(document.createTextNode(text));
+// container.appendChild(input);
+// container.appendChild(wrapper);
+
+// return container;
+// }
+
+function makeButton(action, text, active) {
+ const button = document.createElement("button");
+ button.type = "button";
+ button.textContent = text;
+ button.className = "cesium-cesiumInspector-pickButton";
+ let binding = `click: ${action}`;
+ if (defined(active)) {
+ binding += `, css: {"cesium-cesiumInspector-pickButtonHighlight" : ${active}}`;
+ }
+ button.setAttribute("data-bind", binding);
+
+ return button;
+}
+
+function makeCoordinateRange(
+ maxXTitle,
+ minXTitle,
+ maxYTitle,
+ minYTitle,
+ maxZTitle,
+ minZTitle,
+ maxXVar,
+ minXVar,
+ maxYVar,
+ minYVar,
+ maxZVar,
+ minZVar,
+ defaultMinBounds,
+ defaultMaxBounds,
+ allowedShape,
+ parentContainer
+) {
+ const min = defaultMinBounds;
+ const max = defaultMaxBounds;
+ const boundsElement = parentContainer.appendChild(
+ document.createElement("div")
+ );
+ boundsElement.setAttribute("data-bind", `if: ${allowedShape}`);
+ boundsElement.appendChild(makeRangeInput(maxXTitle, maxXVar, min.x, max.x));
+ boundsElement.appendChild(makeRangeInput(minXTitle, minXVar, min.x, max.x));
+ boundsElement.appendChild(makeRangeInput(maxYTitle, maxYVar, min.y, max.y));
+ boundsElement.appendChild(makeRangeInput(minYTitle, minYVar, min.y, max.y));
+ boundsElement.appendChild(makeRangeInput(maxZTitle, maxZVar, min.z, max.z));
+ boundsElement.appendChild(makeRangeInput(minZTitle, minZVar, min.z, max.z));
+}
+
+export default VoxelInspector;
diff --git a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
new file mode 100644
index 00000000000..8414b50a1d7
--- /dev/null
+++ b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
@@ -0,0 +1,1130 @@
+import Cartesian3 from "../../Core/Cartesian3.js";
+import Check from "../../Core/Check.js";
+import defaultValue from "../../Core/defaultValue.js";
+import defined from "../../Core/defined.js";
+import destroyObject from "../../Core/destroyObject.js";
+import HeadingPitchRoll from "../../Core/HeadingPitchRoll.js";
+import Matrix3 from "../../Core/Matrix3.js";
+import Matrix4 from "../../Core/Matrix4.js";
+import CustomShader from "../../Scene/ModelExperimental/CustomShader.js";
+import VoxelShapeType from "../../Scene/VoxelShapeType.js";
+import knockout from "../../ThirdParty/knockout.js";
+
+function formatShaderString(str) {
+ // This function:
+ // A) removes whitespace lines at the beginning of the string
+ // B) removes unnecessary spaces from the beginning of each line
+
+ const lines = str.split("\n");
+ let firstLineIdx;
+ for (firstLineIdx = 0; firstLineIdx < lines.length; firstLineIdx++) {
+ if (lines[firstLineIdx].match(/\S/)) {
+ // Found the first line that's not entirely whitespace
+ break;
+ }
+ }
+ if (firstLineIdx === lines.length) {
+ // All lines are empty
+ return "";
+ }
+
+ let finalStr = "";
+ const pattern = /^\s*/;
+ const firstLine = lines[firstLineIdx];
+ const spacesInFrontOfFirstLine = firstLine.match(pattern)[0].length;
+ for (let i = firstLineIdx; i < lines.length; i++) {
+ let line = lines[i];
+ const spacesInFront = line.match(pattern)[0].length;
+ if (spacesInFront >= spacesInFrontOfFirstLine) {
+ line = line.slice(spacesInFrontOfFirstLine);
+ }
+ finalStr += `${line}\n`;
+ }
+ return finalStr;
+}
+
+/**
+ * The view model for {@link VoxelInspector}.
+ * @alias VoxelInspectorViewModel
+ * @constructor
+ *
+ * @param {Scene} scene The scene instance to use.
+ */
+function VoxelInspectorViewModel(scene) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("scene", scene);
+ //>>includeEnd('debug');
+
+ this._scene = scene;
+ this._voxelPrimitive = undefined;
+ this._customShaderCompilationRemoveCallback = undefined;
+
+ this._definedProperties = [];
+ this._getPrimitiveFunctions = [];
+
+ const that = this;
+ function addProperty(options) {
+ const name = options.name;
+ const initialValue = options.initialValue;
+ const toggle = defaultValue(options.toggle, false);
+ let setPrimitiveFunction = options.setPrimitiveFunction;
+ let getPrimitiveFunction = options.getPrimitiveFunction;
+
+ that[name] = initialValue;
+ that._definedProperties.push(name);
+
+ if (setPrimitiveFunction === true) {
+ setPrimitiveFunction = function (value) {
+ that._voxelPrimitive[name] = value;
+ };
+ }
+ if (getPrimitiveFunction === true) {
+ getPrimitiveFunction = function () {
+ that[name] = that._voxelPrimitive[name];
+ };
+ }
+ if (defined(getPrimitiveFunction)) {
+ that._getPrimitiveFunctions.push(getPrimitiveFunction);
+ }
+
+ if (toggle) {
+ that.constructor.prototype[`${name}Toggle`] = function () {
+ that[name] = !that[name];
+ };
+ }
+
+ const knock = knockout.observable();
+ knockout.defineProperty(that, name, {
+ get: function () {
+ return knock();
+ },
+ set: function (value) {
+ // Convert input values to the correct type
+ if (typeof initialValue === "number" && typeof value === "string") {
+ value = Number(value);
+ if (isNaN(value)) {
+ value = initialValue;
+ }
+ }
+ if (typeof initialValue === "boolean" && typeof value === "number") {
+ value = value === 1 ? true : false;
+ }
+ knock(value);
+ if (defined(setPrimitiveFunction) && defined(that._voxelPrimitive)) {
+ setPrimitiveFunction(value);
+ scene.requestRender();
+ }
+ },
+ });
+ return knock;
+ }
+
+ addProperty({
+ name: "inspectorVisible",
+ initialValue: false,
+ toggle: true,
+ });
+ addProperty({
+ name: "displayVisible",
+ initialValue: false,
+ toggle: true,
+ });
+ addProperty({
+ name: "styleVisible",
+ initialValue: false,
+ toggle: true,
+ });
+ addProperty({
+ name: "styleString",
+ initialValue: "",
+ getPrimitiveFunction: function () {
+ const shaderString = that._voxelPrimitive.customShader.fragmentShaderText;
+ that.styleString = formatShaderString(shaderString);
+ },
+ });
+ addProperty({
+ name: "styleCompilationMessage",
+ initialValue: "",
+ });
+ addProperty({
+ name: "styleCompilationSuccess",
+ initialValue: true,
+ });
+ addProperty({
+ name: "transformVisible",
+ initialValue: false,
+ toggle: true,
+ });
+ addProperty({
+ name: "boundsVisible",
+ initialValue: false,
+ toggle: true,
+ });
+ addProperty({
+ name: "clippingVisible",
+ initialValue: false,
+ toggle: true,
+ });
+ addProperty({
+ name: "depthTest",
+ initialValue: false,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "show",
+ initialValue: true,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "disableUpdate",
+ initialValue: false,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "debugDraw",
+ initialValue: false,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "jitter",
+ initialValue: true,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "nearestSampling",
+ initialValue: true,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "despeckle",
+ initialValue: false,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "screenSpaceError",
+ initialValue: 4.0,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "stepSize",
+ initialValue: 1.0,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
+ addProperty({
+ name: "shapeIsBox",
+ getPrimitiveFunction: function () {
+ const shapeType = that._voxelPrimitive.shape;
+ that.shapeIsBox = shapeType === VoxelShapeType.BOX;
+ },
+ });
+ addProperty({
+ name: "shapeIsEllipsoid",
+ getPrimitiveFunction: function () {
+ const shapeType = that._voxelPrimitive.shape;
+ that.shapeIsEllipsoid = shapeType === VoxelShapeType.ELLIPSOID;
+ },
+ });
+ addProperty({
+ name: "shapeIsCylinder",
+ getPrimitiveFunction: function () {
+ const shapeType = that._voxelPrimitive.shape;
+ that.shapeIsCylinder = shapeType === VoxelShapeType.CYLINDER;
+ },
+ });
+ addProperty({
+ name: "boundsBoxMaxX",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ value,
+ maxBounds.y,
+ maxBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsBoxMaxX = maxBounds.x;
+ },
+ });
+ addProperty({
+ name: "boundsBoxMinX",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ value,
+ minBounds.y,
+ minBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsBoxMinX = minBounds.x;
+ },
+ });
+ addProperty({
+ name: "boundsBoxMaxY",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ maxBounds.x,
+ value,
+ maxBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsBoxMaxY = maxBounds.y;
+ },
+ });
+ addProperty({
+ name: "boundsBoxMinY",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ minBounds.x,
+ value,
+ minBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsBoxMinY = minBounds.y;
+ },
+ });
+ addProperty({
+ name: "boundsBoxMaxZ",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ maxBounds.x,
+ maxBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsBoxMaxZ = maxBounds.z;
+ },
+ });
+ addProperty({
+ name: "boundsBoxMinZ",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ minBounds.x,
+ minBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsBoxMinZ = minBounds.z;
+ },
+ });
+ addProperty({
+ name: "boundsEllipsoidMaxLongitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ value,
+ maxBounds.y,
+ maxBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsEllipsoidMaxLongitude = maxBounds.x;
+ },
+ });
+ addProperty({
+ name: "boundsEllipsoidMinLongitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ value,
+ minBounds.y,
+ minBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsEllipsoidMinLongitude = minBounds.x;
+ },
+ });
+ addProperty({
+ name: "boundsEllipsoidMaxLatitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ maxBounds.x,
+ value,
+ maxBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsEllipsoidMaxLatitude = maxBounds.y;
+ },
+ });
+ addProperty({
+ name: "boundsEllipsoidMinLatitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ minBounds.x,
+ value,
+ minBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsEllipsoidMinLatitude = minBounds.y;
+ },
+ });
+ addProperty({
+ name: "boundsEllipsoidMaxHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ maxBounds.x,
+ maxBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsEllipsoidMaxHeight = maxBounds.z;
+ },
+ });
+ addProperty({
+ name: "boundsEllipsoidMinHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ minBounds.x,
+ minBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsEllipsoidMinHeight = minBounds.z;
+ },
+ });
+ addProperty({
+ name: "boundsCylinderMaxRadius",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ value,
+ maxBounds.y,
+ maxBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsCylinderMaxRadius = maxBounds.x;
+ },
+ });
+ addProperty({
+ name: "boundsCylinderMinRadius",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ value,
+ minBounds.y,
+ minBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsCylinderMinRadius = minBounds.x;
+ },
+ });
+ addProperty({
+ name: "boundsCylinderMaxHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ maxBounds.x,
+ value,
+ maxBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsCylinderMaxHeight = maxBounds.y;
+ },
+ });
+ addProperty({
+ name: "boundsCylinderMinHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ minBounds.x,
+ value,
+ minBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsCylinderMinHeight = minBounds.y;
+ },
+ });
+ addProperty({
+ name: "boundsCylinderMaxAngle",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that._voxelPrimitive.maxBounds = new Cartesian3(
+ maxBounds.x,
+ maxBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ const maxBounds = that._voxelPrimitive.maxBounds;
+ that.boundsCylinderMaxAngle = maxBounds.z;
+ },
+ });
+ addProperty({
+ name: "boundsCylinderMinAngle",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that._voxelPrimitive.minBounds = new Cartesian3(
+ minBounds.x,
+ minBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ const minBounds = that._voxelPrimitive.minBounds;
+ that.boundsCylinderMinAngle = minBounds.z;
+ },
+ });
+ addProperty({
+ name: "clippingBoxMaxX",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ value,
+ maxClippingBounds.y,
+ maxClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingBoxMaxX = that._voxelPrimitive.maxClippingBounds.x;
+ },
+ });
+ addProperty({
+ name: "clippingBoxMinX",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ value,
+ minClippingBounds.y,
+ minClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingBoxMinX = that._voxelPrimitive.minClippingBounds.x;
+ },
+ });
+ addProperty({
+ name: "clippingBoxMaxY",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ maxClippingBounds.x,
+ value,
+ maxClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingBoxMaxY = that._voxelPrimitive.maxClippingBounds.y;
+ },
+ });
+ addProperty({
+ name: "clippingBoxMinY",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ minClippingBounds.x,
+ value,
+ minClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingBoxMinY = that._voxelPrimitive.minClippingBounds.y;
+ },
+ });
+ addProperty({
+ name: "clippingBoxMaxZ",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ maxClippingBounds.x,
+ maxClippingBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingBoxMaxZ = that._voxelPrimitive.maxClippingBounds.z;
+ },
+ });
+ addProperty({
+ name: "clippingBoxMinZ",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ minClippingBounds.x,
+ minClippingBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingBoxMinZ = that._voxelPrimitive.minClippingBounds.z;
+ },
+ });
+ addProperty({
+ name: "clippingEllipsoidMaxLongitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ value,
+ maxClippingBounds.y,
+ maxClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingEllipsoidMaxLongitude =
+ that._voxelPrimitive.maxClippingBounds.x;
+ },
+ });
+ addProperty({
+ name: "clippingEllipsoidMinLongitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ value,
+ minClippingBounds.y,
+ minClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingEllipsoidMinLongitude =
+ that._voxelPrimitive.minClippingBounds.x;
+ },
+ });
+ addProperty({
+ name: "clippingEllipsoidMaxLatitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ maxClippingBounds.x,
+ value,
+ maxClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingEllipsoidMaxLatitude =
+ that._voxelPrimitive.maxClippingBounds.y;
+ },
+ });
+ addProperty({
+ name: "clippingEllipsoidMinLatitude",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ minClippingBounds.x,
+ value,
+ minClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingBoxMinY = that._voxelPrimitive.minClippingBounds.y;
+ },
+ });
+ addProperty({
+ name: "clippingEllipsoidMaxHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ maxClippingBounds.x,
+ maxClippingBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingEllipsoidMaxHeight =
+ that._voxelPrimitive.maxClippingBounds.z;
+ },
+ });
+ addProperty({
+ name: "clippingEllipsoidMinHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ minClippingBounds.x,
+ minClippingBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingEllipsoidMinHeight =
+ that._voxelPrimitive.minClippingBounds.z;
+ },
+ });
+ addProperty({
+ name: "clippingCylinderMaxRadius",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ value,
+ maxClippingBounds.y,
+ maxClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingCylinderMaxRadius = that._voxelPrimitive.maxClippingBounds.x;
+ },
+ });
+ addProperty({
+ name: "clippingCylinderMinRadius",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ value,
+ minClippingBounds.y,
+ minClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingCylinderMinRadius = that._voxelPrimitive.minClippingBounds.x;
+ },
+ });
+ addProperty({
+ name: "clippingCylinderMaxHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ maxClippingBounds.x,
+ value,
+ maxClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingCylinderMaxHeight = that._voxelPrimitive.maxClippingBounds.y;
+ },
+ });
+ addProperty({
+ name: "clippingCylinderMinHeight",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ minClippingBounds.x,
+ value,
+ minClippingBounds.z
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingCylinderMinHeight = that._voxelPrimitive.minClippingBounds.y;
+ },
+ });
+ addProperty({
+ name: "clippingCylinderMaxAngle",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const maxClippingBounds = that._voxelPrimitive.maxClippingBounds;
+ that._voxelPrimitive.maxClippingBounds = new Cartesian3(
+ maxClippingBounds.x,
+ maxClippingBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingCylinderMaxAngle = that._voxelPrimitive.maxClippingBounds.z;
+ },
+ });
+ addProperty({
+ name: "clippingCylinderMinAngle",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (value) {
+ const minClippingBounds = that._voxelPrimitive.minClippingBounds;
+ that._voxelPrimitive.minClippingBounds = new Cartesian3(
+ minClippingBounds.x,
+ minClippingBounds.y,
+ value
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.clippingCylinderMinAngle = that._voxelPrimitive.minClippingBounds.z;
+ },
+ });
+
+ addProperty({
+ name: "translationX",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (translationX) {
+ const originalTranslation = Matrix4.getTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ );
+ that._voxelPrimitive.modelMatrix = Matrix4.setTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3(
+ translationX,
+ originalTranslation.y,
+ originalTranslation.z
+ ),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.translationX = Matrix4.getTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ ).x;
+ },
+ });
+ addProperty({
+ name: "translationY",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (translationY) {
+ const originalTranslation = Matrix4.getTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ );
+
+ that._voxelPrimitive.modelMatrix = Matrix4.setTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3(
+ originalTranslation.x,
+ translationY,
+ originalTranslation.z
+ ),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.translationY = Matrix4.getTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ ).y;
+ },
+ });
+ addProperty({
+ name: "translationZ",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (translationZ) {
+ const originalTranslation = Matrix4.getTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ );
+
+ that._voxelPrimitive.modelMatrix = Matrix4.setTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3(
+ originalTranslation.x,
+ originalTranslation.y,
+ translationZ
+ ),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.translationZ = Matrix4.getTranslation(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ ).z;
+ },
+ });
+
+ addProperty({
+ name: "scaleX",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (scaleX) {
+ const originalScale = Matrix4.getScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ );
+ that._voxelPrimitive.modelMatrix = Matrix4.setScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3(scaleX, originalScale.y, originalScale.z),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.scaleX = Matrix4.getScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ ).x;
+ },
+ });
+ addProperty({
+ name: "scaleY",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (scaleY) {
+ const originalScale = Matrix4.getScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ );
+ that._voxelPrimitive.modelMatrix = Matrix4.setScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3(originalScale.x, scaleY, originalScale.z),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.scaleY = Matrix4.getScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ ).y;
+ },
+ });
+ addProperty({
+ name: "scaleZ",
+ initialValue: 0.0,
+ setPrimitiveFunction: function (scaleZ) {
+ const originalScale = Matrix4.getScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ );
+ that._voxelPrimitive.modelMatrix = Matrix4.setScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3(originalScale.x, originalScale.y, scaleZ),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ getPrimitiveFunction: function () {
+ that.scaleZ = Matrix4.getScale(
+ that._voxelPrimitive.modelMatrix,
+ new Cartesian3()
+ ).z;
+ },
+ });
+
+ addProperty({
+ name: "angleX",
+ initialValue: 0.0,
+ setPrimitiveFunction: function () {
+ that._voxelPrimitive.modelMatrix = Matrix4.setRotation(
+ that._voxelPrimitive.modelMatrix,
+ Matrix3.fromHeadingPitchRoll(
+ new HeadingPitchRoll(that.angleX, that.angleY, that.angleZ)
+ ),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ });
+
+ addProperty({
+ name: "angleY",
+ initialValue: 0.0,
+ setPrimitiveFunction: function () {
+ that._voxelPrimitive.modelMatrix = Matrix4.setRotation(
+ that._voxelPrimitive.modelMatrix,
+ Matrix3.fromHeadingPitchRoll(
+ new HeadingPitchRoll(that.angleX, that.angleY, that.angleZ)
+ ),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ });
+
+ addProperty({
+ name: "angleZ",
+ initialValue: 0.0,
+ setPrimitiveFunction: function () {
+ that._voxelPrimitive.modelMatrix = Matrix4.setRotation(
+ that._voxelPrimitive.modelMatrix,
+ Matrix3.fromHeadingPitchRoll(
+ new HeadingPitchRoll(that.angleX, that.angleY, that.angleZ)
+ ),
+ that._voxelPrimitive.modelMatrix
+ );
+ },
+ });
+}
+
+Object.defineProperties(VoxelInspectorViewModel.prototype, {
+ /**
+ * Gets the scene
+ * @memberof VoxelInspectorViewModel.prototype
+ * @type {Scene}
+ * @readonly
+ */
+ scene: {
+ get: function () {
+ return this._scene;
+ },
+ },
+
+ /**
+ * Gets or sets the primitive of the view model.
+ * @memberof VoxelInspectorViewModel.prototype
+ * @type {VoxelPrimitive}
+ */
+ voxelPrimitive: {
+ get: function () {
+ return this._voxelPrimitive;
+ },
+ set: function (voxelPrimitive) {
+ if (defined(this._customShaderCompilationRemoveCallback)) {
+ this._customShaderCompilationRemoveCallback();
+ }
+
+ // Update properties from the new primitive
+ if (defined(voxelPrimitive)) {
+ this._voxelPrimitive = voxelPrimitive;
+
+ const that = this;
+ that._voxelPrimitive.readyPromise.then(function () {
+ that._customShaderCompilationRemoveCallback = that._voxelPrimitive.customShaderCompilationEvent.addEventListener(
+ function (error) {
+ const shaderString =
+ that._voxelPrimitive.customShader.fragmentShaderText;
+ that.styleString = formatShaderString(shaderString);
+
+ if (!defined(error)) {
+ that.styleCompilationMessage = "Shader compiled successfully!";
+ that.styleCompilationSuccess = true;
+ } else {
+ that.styleCompilationMessage = error.message;
+ that.styleCompilationSuccess = false;
+ }
+ }
+ );
+ for (let i = 0; i < that._getPrimitiveFunctions.length; i++) {
+ that._getPrimitiveFunctions[i]();
+ }
+ });
+ }
+ },
+ },
+});
+
+/**
+ * Compiles the style in the style editor.
+ * @private
+ */
+VoxelInspectorViewModel.prototype.compileStyle = function () {
+ if (defined(this._voxelPrimitive)) {
+ this._voxelPrimitive.customShader = new CustomShader({
+ fragmentShaderText: this.styleString,
+ });
+ }
+};
+
+/**
+ * Handles key press events on the style editor.
+ * @private
+ */
+VoxelInspectorViewModel.prototype.styleEditorKeyPress = function (
+ sender,
+ event
+) {
+ if (event.keyCode === 9) {
+ //tab
+ event.preventDefault();
+ const textArea = event.target;
+ const start = textArea.selectionStart;
+ const end = textArea.selectionEnd;
+ let newEnd = end;
+ const selected = textArea.value.slice(start, end);
+ const lines = selected.split("\n");
+ const length = lines.length;
+ let i;
+ if (!event.shiftKey) {
+ for (i = 0; i < length; ++i) {
+ lines[i] = ` ${lines[i]}`;
+ newEnd += 2;
+ }
+ } else {
+ for (i = 0; i < length; ++i) {
+ if (lines[i][0] === " ") {
+ if (lines[i][1] === " ") {
+ lines[i] = lines[i].substr(2);
+ newEnd -= 2;
+ } else {
+ lines[i] = lines[i].substr(1);
+ newEnd -= 1;
+ }
+ }
+ }
+ }
+ const newText = lines.join("\n");
+ textArea.value =
+ textArea.value.slice(0, start) + newText + textArea.value.slice(end);
+ textArea.selectionStart = start !== end ? start : newEnd;
+ textArea.selectionEnd = newEnd;
+ } else if (event.ctrlKey && (event.keyCode === 10 || event.keyCode === 13)) {
+ //ctrl + enter
+ this.compileStyle();
+ }
+ return true;
+};
+
+/**
+ * @returns {Boolean} true if the object has been destroyed, false otherwise.
+ */
+VoxelInspectorViewModel.prototype.isDestroyed = function () {
+ return false;
+};
+
+/**
+ * Destroys the widget. Should be called if permanently
+ * removing the widget from layout.
+ */
+VoxelInspectorViewModel.prototype.destroy = function () {
+ const that = this;
+ this._definedProperties.forEach(function (property) {
+ knockout.getObservable(that, property).dispose();
+ });
+
+ return destroyObject(this);
+};
+
+export default VoxelInspectorViewModel;
diff --git a/Source/Widgets/widgets.css b/Source/Widgets/widgets.css
index b6584d2e5cf..885a32f81e1 100644
--- a/Source/Widgets/widgets.css
+++ b/Source/Widgets/widgets.css
@@ -4,6 +4,7 @@
@import url(./CesiumWidget/CesiumWidget.css);
@import url(./CesiumInspector/CesiumInspector.css);
@import url(./Cesium3DTilesInspector/Cesium3DTilesInspector.css);
+@import url(./VoxelInspector/VoxelInspector.css);
@import url(./FullscreenButton/FullscreenButton.css);
@import url(./VRButton/VRButton.css);
@import url(./Geocoder/Geocoder.css);
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/a.bin b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/subtree.bin
new file mode 100644
index 0000000000000000000000000000000000000000..2518924ca56aa3fb60ed2c89e2e765ead97fb28a
GIT binary patch
literal 312
zcmXqZ31MJlU|&E7I!ej;c_pcNCFJPL$jnIzE=?*aN+oEq0obDAl0=XzK-MCh
zQLJPYT@B>}<$zL^C8_>tX~n4^r4X)9YF>It2GIQmwXrb0P<=qfa9Ok3*jfb$fCdjV
HbQl-_(;r_a
literal 0
HcmV?d00001
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
new file mode 100644
index 00000000000..227fd904003
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483648,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxel":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxel","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxel","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
new file mode 100644
index 00000000000..74b24b34572
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
@@ -0,0 +1 @@
+{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
new file mode 100644
index 00000000000..39e46626920
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
@@ -0,0 +1 @@
+{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/subtree.bin b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/subtree.bin
new file mode 100644
index 0000000000000000000000000000000000000000..2518924ca56aa3fb60ed2c89e2e765ead97fb28a
GIT binary patch
literal 312
zcmXqZ31MJlU|&E7I!ej;c_pcNCFJPL$jnIzE=?*aN+oEq0obDAl0=XzK-MCh
zQLJPYT@B>}<$zL^C8_>tX~n4^r4X)9YF>It2GIQmwXrb0P<=qfa9Ok3*jfb$fCdjV
HbQl-_(;r_a
literal 0
HcmV?d00001
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
new file mode 100644
index 00000000000..bf65877f714
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483650,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
new file mode 100644
index 00000000000..74b24b34572
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
@@ -0,0 +1 @@
+{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
new file mode 100644
index 00000000000..39e46626920
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
@@ -0,0 +1 @@
+{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/subtree.bin
new file mode 100644
index 0000000000000000000000000000000000000000..2518924ca56aa3fb60ed2c89e2e765ead97fb28a
GIT binary patch
literal 312
zcmXqZ31MJlU|&E7I!ej;c_pcNCFJPL$jnIzE=?*aN+oEq0obDAl0=XzK-MCh
zQLJPYT@B>}<$zL^C8_>tX~n4^r4X)9YF>It2GIQmwXrb0P<=qfa9Ok3*jfb$fCdjV
HbQl-_(;r_a
literal 0
HcmV?d00001
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
new file mode 100644
index 00000000000..90e35e2f214
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
@@ -0,0 +1 @@
+{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483649,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2],"bounds":{"min":[0.0,0.0,-1.0],"max":[1.0,1.0,0.0]}}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
new file mode 100644
index 00000000000..74b24b34572
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
@@ -0,0 +1 @@
+{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
new file mode 100644
index 00000000000..f68ae62e2ab
--- /dev/null
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
@@ -0,0 +1 @@
+{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"region":[0.0,0.0,1.0,1.0,-1.0,0.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[6378137.0,0.0,0.0,0.0,0.0,6378137.0,0.0,0.0,0.0,0.0,6356752.314245179,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
diff --git a/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js b/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
new file mode 100644
index 00000000000..5eaaf1f26ba
--- /dev/null
+++ b/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
@@ -0,0 +1,123 @@
+import {
+ Cartesian3,
+ Cesium3DTilesVoxelProvider,
+ Ellipsoid,
+ Math as CesiumMath,
+ Matrix4,
+ MetadataComponentType,
+ MetadataType,
+ ResourceCache,
+ VoxelProvider,
+ VoxelShapeType,
+} from "../../Source/Cesium.js";
+import createScene from "../createScene.js";
+import pollToPromise from "../pollToPromise.js";
+
+describe(
+ "Scene/Cesium3DTilesVoxelProvider",
+ function () {
+ let scene;
+
+ beforeAll(function () {
+ scene = createScene();
+ });
+
+ afterAll(function () {
+ scene.destroyForSpecs();
+ });
+
+ afterEach(function () {
+ scene.primitives.removeAll();
+ ResourceCache.clearForSpecs();
+ });
+
+ it("conforms to VoxelProvider interface", function () {
+ expect(Cesium3DTilesVoxelProvider).toConformToInterface(VoxelProvider);
+ });
+
+ it("constructor works", function () {
+ const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const provider = new Cesium3DTilesVoxelProvider({
+ url: url,
+ });
+
+ return pollToPromise(function () {
+ provider.update(scene.frameState);
+ return provider.ready;
+ }).then(function () {
+ expect(provider).toBeDefined();
+ expect(provider.ready).toBeTrue();
+ expect(provider.modelMatrix).toEqualEpsilon(
+ Matrix4.fromScale(Ellipsoid.WGS84.radii),
+ CesiumMath.EPSILON10
+ );
+ expect(provider.shape).toEqual(VoxelShapeType.ELLIPSOID);
+ expect(provider.minBounds).toEqual(new Cartesian3(0.0, 0.0, -1.0));
+ expect(provider.maxBounds).toEqual(new Cartesian3(1.0, 1.0, 0.0));
+ expect(provider.dimensions).toEqual(new Cartesian3(2, 2, 2));
+ expect(provider.paddingBefore).toBeUndefined();
+ expect(provider.paddingAfter).toBeUndefined();
+ expect(provider.names).toEqual(["a"]);
+ expect(provider.types).toEqual([MetadataType.SCALAR]);
+ expect(provider.componentTypes).toEqual([
+ MetadataComponentType.FLOAT32,
+ ]);
+ expect(provider.minimumValues).toEqual([[0]]);
+ expect(provider.maximumValues).toEqual([[1]]);
+ expect(provider.maximumTileCount).toEqual(1);
+ });
+ });
+
+ it("constructor throws when url option is missing", function () {
+ expect(function () {
+ return new Cesium3DTilesVoxelProvider({
+ url: undefined,
+ });
+ }).toThrowDeveloperError();
+ });
+
+ it("requestData works for root tile", function () {
+ const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const provider = new Cesium3DTilesVoxelProvider({
+ url: url,
+ });
+
+ return pollToPromise(function () {
+ provider.update(scene.frameState);
+ return provider.ready;
+ }).then(function () {
+ const requestTilePromise = provider.requestData();
+ // need to call update until the promise is ready
+ return pollToPromise(function () {
+ provider.update(scene.frameState);
+ return provider._doneLoading();
+ })
+ .then(function () {
+ return requestTilePromise;
+ })
+ .then(function (data) {
+ expect(data.length).toEqual(1);
+
+ const dimensions = provider.dimensions;
+ const voxelCount = dimensions.x * dimensions.y * dimensions.z;
+ const componentCount = MetadataType.getComponentCount(
+ provider.types[0]
+ );
+ const expectedLength = voxelCount * componentCount;
+ expect(data[0].length).toEqual(expectedLength);
+ });
+ });
+ });
+
+ it("requestData throws if the provider is not ready", function () {
+ const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const provider = new Cesium3DTilesVoxelProvider({
+ url: url,
+ });
+ expect(function () {
+ return provider.requestData();
+ }).toThrowDeveloperError();
+ });
+ },
+ "WebGL"
+);
diff --git a/Specs/Scene/GltfVoxelProviderSpec.js b/Specs/Scene/GltfVoxelProviderSpec.js
new file mode 100644
index 00000000000..ef9f736648f
--- /dev/null
+++ b/Specs/Scene/GltfVoxelProviderSpec.js
@@ -0,0 +1,137 @@
+import {
+ Cartesian3,
+ GltfVoxelProvider,
+ Matrix4,
+ MetadataComponentType,
+ MetadataType,
+ ResourceCache,
+ VoxelProvider,
+ VoxelShapeType,
+} from "../../Source/Cesium.js";
+import createScene from "../createScene.js";
+import pollToPromise from "../pollToPromise.js";
+
+describe(
+ "Scene/GltfVoxelProvider",
+ function () {
+ let scene;
+
+ beforeAll(function () {
+ scene = createScene();
+ });
+
+ afterAll(function () {
+ scene.destroyForSpecs();
+ });
+
+ afterEach(function () {
+ scene.primitives.removeAll();
+ ResourceCache.clearForSpecs();
+ });
+
+ it("conforms to VoxelProvider interface", function () {
+ expect(GltfVoxelProvider).toConformToInterface(VoxelProvider);
+ });
+
+ it("constructor works", function () {
+ const url =
+ "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/0/0/0/0/tile.gltf";
+ const provider = new GltfVoxelProvider({
+ gltf: url,
+ });
+
+ return pollToPromise(function () {
+ provider.update(scene.frameState);
+ return provider.ready;
+ }).then(function () {
+ expect(provider).toBeDefined();
+ expect(provider.ready).toBeTrue();
+ expect(provider.modelMatrix).toEqual(Matrix4.IDENTITY);
+ expect(provider.shape).toEqual(VoxelShapeType.ELLIPSOID);
+ expect(provider.minBounds).toEqual(new Cartesian3(0.0, 0.0, -1.0));
+ expect(provider.maxBounds).toEqual(new Cartesian3(1.0, 1.0, 0.0));
+ expect(provider.dimensions).toEqual(new Cartesian3(2, 2, 2));
+ expect(provider.paddingBefore).toBeUndefined();
+ expect(provider.paddingAfter).toBeUndefined();
+ expect(provider.names).toEqual(["a"]);
+ expect(provider.types).toEqual([MetadataType.SCALAR]);
+ expect(provider.componentTypes).toEqual([
+ MetadataComponentType.FLOAT32,
+ ]);
+ expect(provider.minimumValues).toBeUndefined();
+ expect(provider.maximumValues).toBeUndefined();
+ expect(provider.maximumTileCount).toEqual(1);
+ });
+ });
+
+ it("constructor throws when gltf option is missing", function () {
+ expect(function () {
+ return new GltfVoxelProvider({
+ gltf: undefined,
+ });
+ }).toThrowDeveloperError();
+ });
+
+ it("requestData works", function () {
+ const url =
+ "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/0/0/0/0/tile.gltf";
+ const provider = new GltfVoxelProvider({
+ gltf: url,
+ });
+
+ return pollToPromise(function () {
+ provider.update(scene.frameState);
+ return provider.ready;
+ }).then(function () {
+ const requestTilePromise = provider.requestData();
+
+ // need to call update until the promise is ready
+ return pollToPromise(function () {
+ provider.update(scene.frameState);
+ return provider._doneLoading();
+ })
+ .then(function () {
+ return requestTilePromise;
+ })
+ .then(function (data) {
+ expect(data.length).toEqual(1);
+
+ const dimensions = provider.dimensions;
+ const voxelCount = dimensions.x * dimensions.y * dimensions.z;
+ const componentCount = MetadataType.getComponentCount(
+ provider.types[0]
+ );
+ const expectedLength = voxelCount * componentCount;
+ expect(data[0].length).toEqual(expectedLength);
+ });
+ });
+ });
+
+ it("requestData throws for non-root tiles", function () {
+ const url =
+ "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/0/0/0/0/tile.gltf";
+ const provider = new GltfVoxelProvider({
+ gltf: url,
+ });
+ expect(function () {
+ return provider.requestData({
+ x: 0,
+ y: 0,
+ z: 0,
+ level: 1,
+ });
+ }).toThrowDeveloperError();
+ });
+
+ it("requestData throws if the provider is not ready", function () {
+ const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const provider = new GltfVoxelProvider({
+ gltf: url,
+ });
+ expect(function () {
+ return provider.requestData();
+ }).toThrowDeveloperError();
+ });
+ },
+ "WebGL"
+);
diff --git a/Specs/Scene/VoxelBoxShapeSpec.js b/Specs/Scene/VoxelBoxShapeSpec.js
new file mode 100644
index 00000000000..c162dd4b896
--- /dev/null
+++ b/Specs/Scene/VoxelBoxShapeSpec.js
@@ -0,0 +1,696 @@
+import {
+ BoundingSphere,
+ Cartesian3,
+ Math as CesiumMath,
+ Matrix3,
+ Matrix4,
+ OrientedBoundingBox,
+ Quaternion,
+ VoxelBoxShape,
+} from "../../Source/Cesium.js";
+
+describe("Scene/VoxelBoxShape", function () {
+ it("constructs", function () {
+ const shape = new VoxelBoxShape();
+ expect(shape.isVisible).toEqual(false);
+ });
+
+ it("update works with model matrix", function () {
+ const shape = new VoxelBoxShape();
+
+ const translation = new Cartesian3(1.0, 2.0, 3.0);
+ const scale = new Cartesian3(2.0, 3.0, 4.0);
+ const angle = CesiumMath.PI_OVER_FOUR;
+ const rotation = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, angle);
+
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+
+ const expectedOrientedBoundingBox = new OrientedBoundingBox(
+ translation,
+ Matrix3.fromColumnMajorArray([
+ scale.x * Math.cos(angle),
+ scale.x * Math.sin(angle),
+ 0.0,
+ scale.y * Math.cos(angle + CesiumMath.PI_OVER_TWO),
+ scale.y * Math.sin(angle + CesiumMath.PI_OVER_TWO),
+ 0.0,
+ 0.0,
+ 0.0,
+ scale.z,
+ ])
+ );
+
+ const expectedBoundingSphere = new BoundingSphere(
+ translation,
+ Cartesian3.magnitude(scale)
+ );
+
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ expect(shape.orientedBoundingBox.center).toEqual(
+ expectedOrientedBoundingBox.center
+ );
+ expect(shape.orientedBoundingBox.halfAxes).toEqualEpsilon(
+ expectedOrientedBoundingBox.halfAxes,
+ CesiumMath.EPSILON12
+ );
+ expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
+ expect(shape.boundTransform).toEqual(modelMatrix);
+ expect(shape.shapeTransform).toEqual(modelMatrix);
+ expect(shape.isVisible).toBeTrue();
+ });
+
+ it("update works with non-default minimum and maximum bounds", function () {
+ const shape = new VoxelBoxShape();
+ const translation = new Cartesian3(1.0, 2.0, 3.0);
+ const scale = new Cartesian3(2.0, 3.0, 4.0);
+ const rotation = Quaternion.IDENTITY;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale,
+ new Matrix4()
+ );
+ const minBounds = new Cartesian3(-0.75, -0.75, -0.75);
+ const maxBounds = new Cartesian3(-0.25, -0.25, -0.25);
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const expectedTranslation = new Cartesian3(0.75, 1.75, 2.75);
+ const expectedScale = new Cartesian3(0.5, 0.75, 1.0);
+ const expectedRotation = rotation;
+ const expectedModelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ expectedTranslation,
+ expectedRotation,
+ expectedScale,
+ new Matrix4()
+ );
+ const expectedOrientedBoundingBox = new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale)
+ );
+ const expectedBoundingSphere = new BoundingSphere(
+ expectedTranslation,
+ Cartesian3.magnitude(expectedScale)
+ );
+
+ expect(shape.orientedBoundingBox).toEqual(expectedOrientedBoundingBox);
+ expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
+ expect(shape.boundTransform).toEqual(expectedModelMatrix);
+ expect(shape.shapeTransform).toEqual(expectedModelMatrix);
+ expect(shape.isVisible).toBeTrue();
+ });
+
+ it("update is visible with zero scale for one component", function () {
+ const shape = new VoxelBoxShape();
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ const translation = new Cartesian3(1.0, 2.0, 3.0);
+ const rotation = Quaternion.IDENTITY;
+
+ let scale;
+ let modelMatrix;
+
+ // 0 scale for X
+ scale = new Cartesian3(0.0, 2.0, 2.0);
+ modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeTrue();
+
+ // 0 scale for Y
+ scale = Cartesian3.fromElements(2.0, 0.0, 2.0, scale);
+ modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeTrue();
+
+ // 0 scale for Z
+ scale = Cartesian3.fromElements(2.0, 2.0, 0.0, scale);
+ modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeTrue();
+ });
+
+ it("update is invisible with zero scale for two or more components", function () {
+ const shape = new VoxelBoxShape();
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ const translation = new Cartesian3(1.0, 2.0, 3.0);
+ const rotation = Quaternion.IDENTITY;
+
+ let scale;
+ let modelMatrix;
+
+ // 0 scale for X and Y
+ scale = new Cartesian3(0.0, 0.0, 2.0);
+ modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // 0 scale for X and Z
+ scale = Cartesian3.fromElements(0.0, 2.0, 0.0, scale);
+ modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // 0 scale for Y and Z
+ scale = Cartesian3.fromElements(2.0, 0.0, 0.0, scale);
+ modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // 0 scale for X, Y, and Z
+ scale = Cartesian3.fromElements(0.0, 0.0, 0.0, scale);
+ modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+ });
+
+ it("update is visible with zero bounds for one component", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+
+ let minBounds;
+ let maxBounds;
+ let expectedScale;
+ let actualScale;
+ let actualTranslation;
+
+ // 0 in X bound
+ minBounds = new Cartesian3(0.0, -1.0, -1.0);
+ maxBounds = new Cartesian3(0.0, +1.0, +1.0);
+ expectedScale = new Cartesian3(0.0, 1.0, 1.0);
+
+ shape.update(modelMatrix, minBounds, maxBounds);
+ actualScale = Matrix4.getScale(shape.shapeTransform, new Cartesian3());
+ actualTranslation = Matrix4.getTranslation(
+ shape.shapeTransform,
+ new Cartesian3()
+ );
+ expect(shape.isVisible).toBeTrue();
+ expect(actualScale).toEqual(expectedScale);
+ expect(actualTranslation).toEqual(translation);
+
+ // 0 in Y bound
+ minBounds = new Cartesian3(-1.0, 0.0, -1.0);
+ maxBounds = new Cartesian3(+1.0, 0.0, +1.0);
+ expectedScale = new Cartesian3(1.0, 0.0, 1.0);
+
+ shape.update(modelMatrix, minBounds, maxBounds);
+ actualScale = Matrix4.getScale(shape.shapeTransform, new Cartesian3());
+ actualTranslation = Matrix4.getTranslation(
+ shape.shapeTransform,
+ new Cartesian3()
+ );
+ expect(shape.isVisible).toBeTrue();
+ expect(actualScale).toEqual(expectedScale);
+ expect(actualTranslation).toEqual(translation);
+
+ // 0 in Z bound
+ minBounds = new Cartesian3(-1.0, -1.0, 0.0);
+ maxBounds = new Cartesian3(+1.0, +1.0, 0.0);
+ expectedScale = new Cartesian3(1.0, 1.0, 0.0);
+
+ shape.update(modelMatrix, minBounds, maxBounds);
+ actualScale = Matrix4.getScale(shape.shapeTransform, new Cartesian3());
+ actualTranslation = Matrix4.getTranslation(
+ shape.shapeTransform,
+ new Cartesian3()
+ );
+ expect(shape.isVisible).toBeTrue();
+ expect(actualScale).toEqual(expectedScale);
+ expect(actualTranslation).toEqual(translation);
+ });
+
+ it("update is invisible with zero bounds for two or more components", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+
+ let minBounds;
+ let maxBounds;
+
+ // 0 in X and Y bounds
+ minBounds = new Cartesian3(0.0, 0.0, -1.0);
+ maxBounds = new Cartesian3(0.0, 0.0, +1.0);
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // 0 in X and Z bounds
+ minBounds = new Cartesian3(0.0, -1.0, 0.0);
+ maxBounds = new Cartesian3(0.0, +1.0, 0.0);
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // 0 in Y and Z bounds
+ minBounds = new Cartesian3(-1.0, 0.0, 0.0);
+ maxBounds = new Cartesian3(+1.0, 0.0, 0.0);
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // 0 in X, Y, and Z bounds
+ minBounds = new Cartesian3(0.0, 0.0, 0.0);
+ maxBounds = new Cartesian3(0.0, 0.0, 0.0);
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+ });
+
+ it("update is invisible when minimum bounds exceed maximum bounds", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+
+ let minBounds;
+ let maxBounds;
+
+ // Exceeds X
+ minBounds = new Cartesian3(+2.0, -1.0, -1.0);
+ maxBounds = new Cartesian3(+1.0, +1.0, +1.0);
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // Exceeds Y
+ minBounds = new Cartesian3(-1.0, +2.0, -1.0);
+ maxBounds = new Cartesian3(+1.0, +1.0, +1.0);
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+
+ // Exceeds Z
+ minBounds = new Cartesian3(-1.0, -1.0, +2.0);
+ maxBounds = new Cartesian3(+1.0, +1.0, +1.0);
+ shape.update(modelMatrix, minBounds, maxBounds);
+ expect(shape.isVisible).toBeFalse();
+ });
+
+ it("update throws with no model matrix parameter", function () {
+ const shape = new VoxelBoxShape();
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+
+ expect(function () {
+ return shape.update(undefined, minBounds, maxBounds);
+ }).toThrowDeveloperError();
+ });
+
+ it("update throws with no minimum bounds parameter", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+
+ expect(function () {
+ return shape.update(modelMatrix, undefined, maxBounds);
+ }).toThrowDeveloperError();
+ });
+
+ it("update throws with no maximum bounds parameter", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+
+ expect(function () {
+ return shape.update(modelMatrix, minBounds, undefined);
+ }).toThrowDeveloperError();
+ });
+
+ it("computeOrientedBoundingBoxForTile works for root tile", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const tileLevel = 0;
+ const tileX = 0;
+ const tileY = 0;
+ const tileZ = 0;
+ const orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+
+ const expectedOrientedBoundingBox = shape.orientedBoundingBox;
+ expect(orientedBoundingBox).toEqual(expectedOrientedBoundingBox);
+ });
+
+ it("computeOrientedBoundingBoxForTile works for children of root tile", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const expectedScale = new Cartesian3(0.5, 0.5, 0.5);
+ let expectedTranslation;
+
+ const tileLevel = 1;
+ let tileX;
+ let tileY;
+ let tileZ;
+ let orientedBoundingBox;
+
+ tileX = 0;
+ tileY = 0;
+ tileZ = 0;
+
+ // Child (0, 0, 0)
+ orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ expectedTranslation = new Cartesian3(-0.5, -0.5, -0.5);
+ expect(orientedBoundingBox).toEqual(
+ new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale, new Matrix3())
+ )
+ );
+
+ // Child (1, 0, 0)
+ tileX = 1;
+ tileY = 0;
+ tileZ = 0;
+ orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ expectedTranslation = new Cartesian3(+0.5, -0.5, -0.5);
+ expect(orientedBoundingBox).toEqual(
+ new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale, new Matrix3())
+ )
+ );
+
+ // Child (0, 1, 0)
+ tileX = 0;
+ tileY = 1;
+ tileZ = 0;
+ orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ expectedTranslation = new Cartesian3(-0.5, +0.5, -0.5);
+ expect(orientedBoundingBox).toEqual(
+ new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale, new Matrix3())
+ )
+ );
+
+ // Child (0, 0, 1)
+ tileX = 0;
+ tileY = 0;
+ tileZ = 1;
+ orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ expectedTranslation = new Cartesian3(-0.5, -0.5, +0.5);
+ expect(orientedBoundingBox).toEqual(
+ new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale, new Matrix3())
+ )
+ );
+
+ // Child (1, 1, 0)
+ tileX = 1;
+ tileY = 1;
+ tileZ = 0;
+ orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ expectedTranslation = new Cartesian3(+0.5, +0.5, -0.5);
+ expect(orientedBoundingBox).toEqual(
+ new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale, new Matrix3())
+ )
+ );
+
+ // Child (1, 0, 1)
+ tileX = 1;
+ tileY = 0;
+ tileZ = 1;
+ orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ expectedTranslation = new Cartesian3(+0.5, -0.5, +0.5);
+ expect(orientedBoundingBox).toEqual(
+ new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale, new Matrix3())
+ )
+ );
+
+ // Child (1, 1, 1)
+ tileX = 1;
+ tileY = 1;
+ tileZ = 1;
+ orientedBoundingBox = shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ expectedTranslation = new Cartesian3(+0.5, +0.5, +0.5);
+ expect(orientedBoundingBox).toEqual(
+ new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedScale, new Matrix3())
+ )
+ );
+ });
+
+ it("computeOrientedBoundingBoxForTile throws with no tile coordinates parameter", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const tileLevel = 0;
+ const tileX = 0;
+ const tileY = 0;
+ const tileZ = 0;
+
+ expect(function () {
+ return shape.computeOrientedBoundingBoxForTile(
+ undefined,
+ tileX,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ }).toThrowDeveloperError();
+
+ expect(function () {
+ return shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ undefined,
+ tileY,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ }).toThrowDeveloperError();
+
+ expect(function () {
+ return shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ undefined,
+ tileZ,
+ new OrientedBoundingBox()
+ );
+ }).toThrowDeveloperError();
+
+ expect(function () {
+ return shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ undefined,
+ new OrientedBoundingBox()
+ );
+ }).toThrowDeveloperError();
+ });
+
+ it("computeOrientedBoundingBoxForTile throws with no result parameter", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const tileLevel = 0;
+ const tileX = 0;
+ const tileY = 0;
+ const tileZ = 0;
+
+ expect(function () {
+ return shape.computeOrientedBoundingBoxForTile(
+ tileLevel,
+ tileX,
+ tileY,
+ tileZ,
+ undefined
+ );
+ }).toThrowDeveloperError();
+ });
+
+ it("computeApproximateStepSize works", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const dimensions = new Cartesian3(32, 32, 16);
+ const stepSize = shape.computeApproximateStepSize(dimensions);
+ expect(stepSize).toBeGreaterThan(0.0);
+ expect(stepSize).toBeLessThan(1.0);
+ });
+
+ it("computeApproximateStepSize throws with no dimensions parameter", function () {
+ const shape = new VoxelBoxShape();
+ const translation = Cartesian3.ZERO;
+ const rotation = Quaternion.IDENTITY;
+ const scale = Cartesian3.ONE;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ expect(function () {
+ return shape.computeApproximateStepSize(undefined);
+ }).toThrowDeveloperError();
+ });
+});
diff --git a/Specs/Scene/VoxelCylinderShapeSpec.js b/Specs/Scene/VoxelCylinderShapeSpec.js
new file mode 100644
index 00000000000..481f3a18c6c
--- /dev/null
+++ b/Specs/Scene/VoxelCylinderShapeSpec.js
@@ -0,0 +1,214 @@
+import {
+ BoundingSphere,
+ Cartesian3,
+ Math as CesiumMath,
+ Matrix3,
+ Matrix4,
+ OrientedBoundingBox,
+ Quaternion,
+ VoxelCylinderShape,
+} from "../../Source/Cesium.js";
+
+describe(
+ "Scene/VoxelCylinderShape",
+ function () {
+ it("constructs", function () {
+ const shape = new VoxelCylinderShape();
+ expect(shape.isVisible).toEqual(false);
+ });
+
+ it("update works with model matrix", function () {
+ const shape = new VoxelCylinderShape();
+
+ const translation = new Cartesian3(1.0, 2.0, 3.0);
+ const scale = new Cartesian3(2.0, 3.0, 4.0);
+ const halfScale = Cartesian3.multiplyByScalar(
+ scale,
+ 0.5,
+ new Cartesian3()
+ );
+ const angle = CesiumMath.PI_OVER_FOUR;
+ const rotation = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, angle);
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+ const minBounds = VoxelCylinderShape.DefaultMinBounds;
+ const maxBounds = VoxelCylinderShape.DefaultMaxBounds;
+
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const expectedOrientedBoundingBox = new OrientedBoundingBox(
+ translation,
+ Matrix3.fromColumnMajorArray([
+ halfScale.x * Math.cos(angle),
+ halfScale.x * Math.sin(angle),
+ 0.0,
+ halfScale.y * Math.cos(angle + CesiumMath.PI_OVER_TWO),
+ halfScale.y * Math.sin(angle + CesiumMath.PI_OVER_TWO),
+ 0.0,
+ 0.0,
+ 0.0,
+ halfScale.z,
+ ])
+ );
+ const expectedBoundingSphere = new BoundingSphere(
+ translation,
+ Cartesian3.magnitude(halfScale)
+ );
+
+ expect(shape.orientedBoundingBox.center).toEqual(
+ expectedOrientedBoundingBox.center
+ );
+ expect(shape.orientedBoundingBox.halfAxes).toEqualEpsilon(
+ expectedOrientedBoundingBox.halfAxes,
+ CesiumMath.EPSILON12
+ );
+ expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
+ expect(shape.boundTransform).toEqual(modelMatrix);
+ expect(shape.shapeTransform).toEqual(modelMatrix);
+ expect(shape.isVisible).toBeTrue();
+ });
+
+ it("update works with non-default minimum and maximum bounds", function () {
+ const shape = new VoxelCylinderShape();
+ const translation = new Cartesian3(1.0, 2.0, 3.0);
+ const scale = new Cartesian3(2.0, 3.0, 4.0);
+ const rotation = Quaternion.IDENTITY;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale,
+ new Matrix4()
+ );
+
+ // Half revolution
+ const minRadius = 0.25;
+ const maxRadius = 0.75;
+ const minHeight = -0.5;
+ const maxHeight = +0.5;
+ const minAngle = -CesiumMath.PI;
+ const maxAngle = 0.0;
+ const minBounds = new Cartesian3(minRadius, minHeight, minAngle);
+ const maxBounds = new Cartesian3(maxRadius, maxHeight, maxAngle);
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const expectedMinX = translation.x - maxRadius * scale.x;
+ const expectedMaxX = translation.x + maxRadius * scale.x;
+ const expectedMinY = translation.y + minHeight * scale.y;
+ const expectedMaxY = translation.y + maxHeight * scale.y;
+ const expectedMinZ = translation.z - maxRadius * scale.z;
+ const expectedMaxZ = translation.z;
+
+ const expectedScale = new Cartesian3(
+ expectedMaxX - expectedMinX,
+ expectedMaxY - expectedMinY,
+ expectedMaxZ - expectedMinZ
+ );
+ const expectedTranslation = new Cartesian3(
+ 0.5 * (expectedMaxX + expectedMinX),
+ 0.5 * (expectedMaxY + expectedMinY),
+ 0.5 * (expectedMaxZ + expectedMinZ)
+ );
+
+ const expectedHalfScale = Cartesian3.multiplyByScalar(
+ expectedScale,
+ 0.5,
+ new Cartesian3()
+ );
+ const expectedOrientedBoundingBox = new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedHalfScale)
+ );
+ const expectedBoundingSphere = new BoundingSphere(
+ expectedTranslation,
+ Cartesian3.magnitude(expectedHalfScale)
+ );
+ const expectedBoundTransform = Matrix4.setTranslation(
+ Matrix4.fromScale(expectedScale, new Matrix4()),
+ expectedTranslation,
+ new Matrix4()
+ );
+
+ expect(shape.orientedBoundingBox.center).toEqualEpsilon(
+ expectedOrientedBoundingBox.center,
+ CesiumMath.EPSILON12
+ );
+ expect(shape.orientedBoundingBox.halfAxes).toEqualEpsilon(
+ expectedOrientedBoundingBox.halfAxes,
+ CesiumMath.EPSILON12
+ );
+ expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
+ expect(shape.boundTransform).toEqual(expectedBoundTransform);
+ expect(shape.shapeTransform).toEqual(modelMatrix);
+ expect(shape.isVisible).toBeTrue();
+ });
+
+ it("update works with minimum and maximum bounds that cross the 180th meridian", function () {
+ const shape = new VoxelCylinderShape();
+ const translation = Cartesian3.ZERO;
+ const scale = Cartesian3.ONE;
+ const rotation = Quaternion.IDENTITY;
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale,
+ new Matrix4()
+ );
+
+ // Half revolution around 180th meridian
+ const minAngle = +CesiumMath.PI_OVER_TWO;
+ const maxAngle = -CesiumMath.PI_OVER_TWO;
+ const defaultMinBounds = VoxelCylinderShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds;
+ const minBounds = new Cartesian3(
+ defaultMinBounds.x,
+ defaultMinBounds.y,
+ minAngle
+ );
+ const maxBounds = new Cartesian3(
+ defaultMaxBounds.x,
+ defaultMaxBounds.y,
+ maxAngle
+ );
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ const expectedScale = new Cartesian3(0.5, 1.0, 1.0);
+ const expectedTranslation = new Cartesian3(-0.5, 0.0, 0.0);
+
+ const expectedHalfScale = Cartesian3.multiplyByScalar(
+ expectedScale,
+ 0.5,
+ new Cartesian3()
+ );
+ const expectedOrientedBoundingBox = new OrientedBoundingBox(
+ expectedTranslation,
+ Matrix3.fromScale(expectedHalfScale)
+ );
+ const expectedBoundingSphere = new BoundingSphere(
+ expectedTranslation,
+ Cartesian3.magnitude(expectedHalfScale)
+ );
+ const expectedBoundTransform = Matrix4.setTranslation(
+ Matrix4.fromScale(expectedScale, new Matrix4()),
+ expectedTranslation,
+ new Matrix4()
+ );
+
+ expect(shape.orientedBoundingBox.center).toEqualEpsilon(
+ expectedOrientedBoundingBox.center,
+ CesiumMath.EPSILON12
+ );
+ expect(shape.orientedBoundingBox.halfAxes).toEqualEpsilon(
+ expectedOrientedBoundingBox.halfAxes,
+ CesiumMath.EPSILON12
+ );
+ expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
+ expect(shape.boundTransform).toEqual(expectedBoundTransform);
+ expect(shape.shapeTransform).toEqual(modelMatrix);
+ expect(shape.isVisible).toBeTrue();
+ });
+ },
+ "WebGL"
+);
diff --git a/Specs/Scene/VoxelEllipsoidShapeSpec.js b/Specs/Scene/VoxelEllipsoidShapeSpec.js
new file mode 100644
index 00000000000..e80c0ecd79b
--- /dev/null
+++ b/Specs/Scene/VoxelEllipsoidShapeSpec.js
@@ -0,0 +1,179 @@
+import {
+ Cartesian3,
+ Math as CesiumMath,
+ Ellipsoid,
+ OrientedBoundingBox,
+ Matrix4,
+ Ray,
+ Rectangle,
+ VoxelEllipsoidShape,
+ VoxelShapeType,
+} from "../../Source/Cesium.js";
+
+describe(
+ "Scene/VoxelEllipsoidShape",
+ function () {
+ const PI_OVER_TWO = CesiumMath.PI_OVER_TWO;
+ const west = -PI_OVER_TWO;
+ const east = PI_OVER_TWO;
+ const south = -PI_OVER_TWO;
+ const north = PI_OVER_TWO;
+ const rectangle = new Rectangle(west, south, east, north);
+ const minimumHeight = 0.0;
+ const maximumHeight = 1000000.0;
+ let ellipsoid;
+ const scratchCartesian3 = new Cartesian3();
+ beforeEach(function () {
+ ellipsoid = new VoxelEllipsoidShape({
+ rectangle: rectangle,
+ minimumHeight: minimumHeight,
+ maximumHeight: maximumHeight,
+ });
+ ellipsoid.update(); // compute transforms
+ });
+
+ it("constructs with arguments", function () {
+ expect(ellipsoid.ellipsoid.equals(Ellipsoid.WGS84)).toBe(true);
+ expect(ellipsoid.rectangle.equals(rectangle)).toBe(true);
+ expect(ellipsoid.minimumHeight).toBe(minimumHeight);
+ expect(ellipsoid.maximumHeight).toBe(maximumHeight);
+ expect(ellipsoid._type).toBe(VoxelShapeType.ELLIPSOID);
+ });
+
+ it("updates bounding shapes upon changes to ellipsoid", function () {
+ const oldObb = ellipsoid.orientedBoundingBox.clone();
+ const oldSphere = ellipsoid.boundingSphere.clone();
+ ellipsoid.ellipsoid = Ellipsoid.MOON;
+ expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
+ expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
+ });
+
+ it("updates bounding shapes upon changes to rectangle", function () {
+ const oldObb = ellipsoid.orientedBoundingBox.clone();
+ const oldSphere = ellipsoid.boundingSphere.clone();
+ ellipsoid.rectangle = new Rectangle(
+ west + CesiumMath.EPSILON7,
+ south,
+ east,
+ north
+ );
+ expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
+ expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
+ });
+
+ it("updates bounding shapes upon changes to minimum height", function () {
+ const oldObb = ellipsoid.orientedBoundingBox.clone();
+ const oldSphere = ellipsoid.boundingSphere.clone();
+ ellipsoid.minimumHeight += 1;
+ expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(true);
+ expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(true);
+ });
+
+ it("updates bounding shapes upon changes to maximum height", function () {
+ const oldObb = ellipsoid.orientedBoundingBox.clone();
+ const oldSphere = ellipsoid.boundingSphere.clone();
+ ellipsoid.maximumHeight += 1;
+ expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
+ expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
+ });
+
+ it("computes shape transform", function () {
+ const radii = Ellipsoid.WGS84._radii;
+ const scaleX = 2.0 * (radii.x + maximumHeight);
+ const scaleY = 2.0 * (radii.y + maximumHeight);
+ const scaleZ = 2.0 * (radii.z + maximumHeight);
+ const scale = Cartesian3.fromElements(scaleX, scaleY, scaleZ);
+ const shapeTransform = Matrix4.fromScale(scale, new Matrix4());
+ expect(shapeTransform.equals(ellipsoid._shapeTransform)).toBe(true);
+ });
+
+ it("can clone itself", function () {
+ const ellipsoidClone = ellipsoid.clone();
+ expect(ellipsoidClone).not.toBe(ellipsoid);
+ expect(ellipsoid.ellipsoid.equals(ellipsoidClone.ellipsoid)).toBe(true);
+ expect(ellipsoid.rectangle.equals(ellipsoidClone.rectangle)).toBe(true);
+ expect(ellipsoid.minimumHeight).toBe(ellipsoidClone.minimumHeight);
+ expect(ellipsoid.maximumHeight).toBe(ellipsoidClone.maximumHeight);
+ });
+
+ it("computes bounding volume for root tile", function () {
+ const result = new OrientedBoundingBox();
+ ellipsoid.computeOrientedBoundingBoxForTile(0, 0, 0, 0, result);
+ expect(result.equals(ellipsoid.orientedBoundingBox)).toBe(true);
+ });
+
+ it("indicates when a point in local space is outside the shape", function () {
+ const clippingMinimum = Cartesian3.ZERO;
+ const clippingMaximum = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ expect(
+ ellipsoid.localPointInsideShape(
+ Cartesian3.ZERO,
+ clippingMinimum,
+ clippingMaximum
+ )
+ ).toBe(false);
+ expect(
+ ellipsoid.localPointInsideShape(
+ Cartesian3.fromElements(0.49, 0.0, 0.0),
+ clippingMinimum,
+ clippingMaximum
+ )
+ ).toBe(true);
+ });
+
+ it("transforms from local to shape space", function () {
+ const point = Cartesian3.fromElements(0.5, 0.0, 0.0);
+ expect(
+ ellipsoid
+ .transformFromLocalToShapeSpace(point, scratchCartesian3)
+ .equals(Cartesian3.fromElements(0.5, 0.5, 1.0))
+ ).toBe(true);
+ });
+
+ it("intersects ray with outer shell", function () {
+ const origin = Cartesian3.fromElements(2.0, 0.0, 0.0);
+ const direction = Cartesian3.fromElements(-1.0, 0.0, 0.0);
+ const ray = new Ray(origin, direction);
+ const minClipping = Cartesian3.ZERO;
+ const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
+ });
+
+ it("intersects ray with inner shell", function () {
+ const origin = Cartesian3.ZERO;
+ const direction = Cartesian3.fromElements(1.0, 0.0, 0.0);
+ const ray = new Ray(origin, direction);
+ const minClipping = Cartesian3.ZERO;
+ const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ expect(t).toEqualEpsilon(
+ 1.0 - ellipsoid._ellipsoidHeightDifferenceUv,
+ CesiumMath.EPSILON4
+ );
+ });
+
+ it("intersects ray with longitude face", function () {
+ ellipsoid.rectangle = new Rectangle(west, south, 0.0, north);
+ const origin = Cartesian3.fromElements(0.99, 1.0, 0.0);
+ const direction = Cartesian3.fromElements(0.0, -1.0, 0.0);
+ const ray = new Ray(origin, direction);
+ const minClipping = Cartesian3.fromElements(-1.0, -1.0, -1.0);
+ const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
+ });
+
+ it("intersects ray with latitude face", function () {
+ ellipsoid.rectangle = new Rectangle(west, south, east, 0.0);
+ const origin = Cartesian3.fromElements(0.99, 0.0, 1.0);
+ const direction = Cartesian3.fromElements(0.0, 0.0, -1.0);
+ const ray = new Ray(origin, direction);
+ const minClipping = Cartesian3.fromElements(-1.0, -1.0, -1.0);
+ const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
+ });
+ },
+ "WebGL"
+);
diff --git a/Specs/Scene/VoxelPrimitiveSpec.js b/Specs/Scene/VoxelPrimitiveSpec.js
new file mode 100644
index 00000000000..e45fb36ecf4
--- /dev/null
+++ b/Specs/Scene/VoxelPrimitiveSpec.js
@@ -0,0 +1,224 @@
+import {
+ AttributeType,
+ Cartesian3,
+ ComponentDatatype,
+ defined,
+ Matrix3,
+ Matrix4,
+ Pass,
+ VoxelBoxShape,
+ VoxelPrimitive,
+} from "../../Source/Cesium.js";
+import createScene from "../createScene.js";
+
+const metadataName = "dummyMetadataName";
+function DummyVoxelProvider() {
+ this.shape = new VoxelBoxShape();
+ this.voxelDimensions = new Cartesian3(2, 2, 2);
+ this.voxelsPerTile = 2 * 2 * 2;
+ this.ready = true;
+ this.readyPromise = Promise.resolve(this);
+ this._tileCount = 4096;
+ this.neighborEdgeCount = 0;
+ this.metadataNames = [metadataName];
+ this.numberOfLevels = 16;
+
+ this.properties = {};
+
+ this.properties[metadataName] = {
+ type: AttributeType.VEC4,
+ componentType: ComponentDatatype.FLOAT,
+ componentCount: 4,
+ min: 0,
+ max: 1,
+ count: this.voxelsPerTile * this._tileCount,
+ };
+}
+DummyVoxelProvider.prototype.requestData = function (options) {
+ const maxIndex = Math.pow(2, options.level) - 1;
+ const requestOutsideOfShape =
+ options.x < 0 ||
+ options.x > maxIndex ||
+ options.y < 0 ||
+ options.y > maxIndex ||
+ options.z < 0 ||
+ options.z > maxIndex;
+ if (options.level >= this.numberOfLevels || requestOutsideOfShape) {
+ return Promise.resolve(undefined);
+ }
+ const returnArray = new Uint32Array(this.voxelsPerTile);
+ return Promise.resolve(returnArray);
+};
+
+describe(
+ "Scene/VoxelPrimitive",
+ function () {
+ const scene = createScene();
+ const provider = new DummyVoxelProvider();
+ let primitive;
+ beforeEach(function () {
+ scene.primitives.removeAll();
+ primitive = new VoxelPrimitive({
+ provider: provider,
+ });
+ scene._primitives.add(primitive);
+ scene.renderForSpecs();
+ });
+
+ it("constructs a primitive", function () {
+ const command = scene.frameState.commandList[0];
+ expect(command).toBeDefined();
+ expect(command.pass).toBe(Pass.VOXELS);
+ });
+
+ it("constructs with options", function () {
+ expect(primitive.provider).toBe(provider);
+ return primitive.readyPromise.then(function () {
+ const property = provider.properties[metadataName];
+ expect(primitive.shape._type).toBe(provider.shape._type);
+ expect(
+ primitive._voxelDimensions.equals(provider.voxelDimensions)
+ ).toBe(true);
+ expect(primitive._voxelNeighborEdgeCount).toBe(
+ provider.neighborEdgeCount
+ );
+ // Object.values workaround is Object.keys.map
+ const minimumValues = primitive.minimumValues[0];
+ expect(
+ Object.keys(minimumValues)
+ .map(function (key) {
+ return minimumValues[key];
+ })
+ .every(function (value) {
+ return value === property.min;
+ })
+ ).toBe(true);
+ const maximumValues = primitive.maximumValues[0];
+ expect(
+ Object.keys(maximumValues)
+ .map(function (key) {
+ return maximumValues[key];
+ })
+ .every(function (value) {
+ return value === property.max;
+ })
+ ).toBe(true);
+ expect(primitive._tileCount).toBe(provider._tileCount);
+ expect(defined(primitive._traversal)).toBe(true);
+ // TODO should we test writing glsl functions? i.e. sample functions, setting style input values for each metadata
+ });
+ });
+
+ it("sets clipping range extrema when given valid range between 0 and 1", function () {
+ const setValue = Cartesian3.fromElements(0.1, 0.5, 0.3);
+ expect(primitive.minClippingBounds.equals(setValue)).toBe(false);
+ primitive.minClippingBounds = setValue;
+ expect(primitive.minClippingBounds.equals(setValue)).toBe(true);
+ expect(primitive.maxClippingBounds.equals(setValue)).toBe(false);
+ primitive.maxClippingBounds = setValue;
+ expect(primitive.maxClippingBounds.equals(setValue)).toBe(true);
+ });
+
+ it("clamps clipping range extrema when given values outside [0, 1]", function () {
+ const setValue = Cartesian3.fromElements(-1.0, 0.5, 2.0);
+ const clampedValue = Cartesian3.fromElements(0.0, 0.5, 1.0);
+ primitive.minClippingBounds = setValue;
+ expect(primitive.minClippingBounds.equals(clampedValue)).toBe(true);
+ primitive.maxClippingBounds = setValue;
+ expect(primitive.maxClippingBounds.equals(clampedValue)).toBe(true);
+ });
+
+ it("uses default style", function () {
+ primitive.style = undefined;
+ expect(primitive.style).toBe(VoxelPrimitive.DefaultStyle);
+ });
+
+ it("creates style", function () {
+ const options = {
+ type: "VoxelPrimitiveStyle",
+ source:
+ "vec4 style(StyleInput styleInput) {\n" +
+ " return vec4(1.0);\n" +
+ "}",
+ uniforms: { dummyUniform: 0 },
+ };
+ const material = VoxelPrimitive.CreateStyle(options);
+ expect(material._template.type).toBe(options.type);
+ expect(material._template.source).toBe(options.source);
+ expect(material._template.uniforms.dummyUniform).toBe(
+ options.uniforms.dummyUniform
+ );
+ });
+
+ it("updates transform matrices", function () {
+ const shape = primitive._shape;
+ shape.translation = new Cartesian3(2.382, -3.643, 1.084);
+ return primitive.readyPromise.then(function () {
+ primitive.update(scene.frameState);
+ const worldToBoundTransform = Matrix4.inverse(
+ shape._boundTransform,
+ new Matrix4()
+ );
+ expect(
+ primitive._worldToBoundTransform.equals(worldToBoundTransform)
+ ).toBe(true);
+
+ const shapeTransform = shape._shapeTransform;
+ const worldToShapeTransform = Matrix4.inverse(
+ shape._shapeTransform,
+ new Matrix4()
+ );
+ expect(
+ primitive._worldToShapeTransform.equals(worldToShapeTransform)
+ ).toBe(true);
+
+ const shapeScale = Matrix4.getScale(shapeTransform, new Matrix4());
+ const shapeRotation = Matrix4.getRotation(
+ shapeTransform,
+ new Matrix4()
+ );
+ const shapeScaleMaximum = Cartesian3.maximumComponent(shapeScale);
+ const shapeNormalizedScale = Cartesian3.divideByScalar(
+ shapeScale,
+ shapeScaleMaximum,
+ new Matrix4()
+ );
+ const scaleAndRotation = Matrix3.multiplyByScale(
+ shapeRotation,
+ shapeNormalizedScale,
+ new Matrix4()
+ );
+ const worldToUvTransformDirection = Matrix3.inverse(
+ scaleAndRotation,
+ new Matrix3()
+ );
+ expect(
+ primitive._worldToUvTransformDirection.equals(
+ worldToUvTransformDirection
+ )
+ ).toBe(true);
+
+ const shapeToWorldNormal = Matrix4.getRotation(
+ shapeTransform,
+ new Matrix4()
+ );
+ Matrix3.multiplyByScale(
+ shapeToWorldNormal,
+ shapeNormalizedScale,
+ shapeToWorldNormal
+ );
+ Matrix3.transpose(shapeToWorldNormal, shapeToWorldNormal);
+ Matrix3.inverse(shapeToWorldNormal, shapeToWorldNormal);
+ expect(primitive._shapeToWorldNormal.equals(shapeToWorldNormal)).toBe(
+ true
+ );
+
+ const stepSizeUv = shape.computeApproximateStepSize(
+ primitive._voxelDimensions
+ );
+ expect(primitive._stepSizeUv).toBe(stepSizeUv);
+ });
+ });
+ },
+ "WebGL"
+);
diff --git a/Specs/Scene/VoxelShapeTypeSpec.js b/Specs/Scene/VoxelShapeTypeSpec.js
new file mode 100644
index 00000000000..240f2815997
--- /dev/null
+++ b/Specs/Scene/VoxelShapeTypeSpec.js
@@ -0,0 +1,43 @@
+import {
+ PrimitiveType,
+ VoxelBoxShape,
+ VoxelCylinderShape,
+ VoxelEllipsoidShape,
+ VoxelShapeType,
+} from "../../Source/Cesium.js";
+
+describe("Scene/VoxelShapeType", function () {
+ it("fromPrimitiveType works", function () {
+ expect(VoxelShapeType.fromPrimitiveType(PrimitiveType.VOXEL_BOX)).toBe(
+ VoxelShapeType.BOX
+ );
+ expect(
+ VoxelShapeType.fromPrimitiveType(PrimitiveType.VOXEL_ELLIPSOID)
+ ).toBe(VoxelShapeType.ELLIPSOID);
+ expect(VoxelShapeType.fromPrimitiveType(PrimitiveType.VOXEL_CYLINDER)).toBe(
+ VoxelShapeType.CYLINDER
+ );
+ });
+ it("fromPrimitiveType throws for invalid type", function () {
+ expect(function () {
+ return VoxelShapeType.fromPrimitiveType("NOT_A_PRIMITIVE_TYPE");
+ }).toThrowDeveloperError();
+ });
+
+ it("toShapeConstructor works", function () {
+ expect(VoxelShapeType.toShapeConstructor(VoxelShapeType.BOX)).toBe(
+ VoxelBoxShape
+ );
+ expect(VoxelShapeType.toShapeConstructor(VoxelShapeType.ELLIPSOID)).toBe(
+ VoxelEllipsoidShape
+ );
+ expect(VoxelShapeType.toShapeConstructor(VoxelShapeType.CYLINDER)).toBe(
+ VoxelCylinderShape
+ );
+ });
+ it("toShapeConstructor throws for invalid type", function () {
+ expect(function () {
+ return VoxelShapeType.toShapeConstructor("NOT_A_SHAPE_TYPE");
+ }).toThrowDeveloperError();
+ });
+});
diff --git a/Specs/Scene/VoxelTraversalSpec.js b/Specs/Scene/VoxelTraversalSpec.js
new file mode 100644
index 00000000000..de7f2b2e48a
--- /dev/null
+++ b/Specs/Scene/VoxelTraversalSpec.js
@@ -0,0 +1,396 @@
+import {
+ AttributeType,
+ ComponentDatatype,
+ defer,
+ VoxelTraversal,
+ VoxelPrimitive,
+ VoxelBoxShape,
+ Cartesian3,
+ OrientedBoundingBox,
+ Math as CesiumMath,
+ CullingVolume,
+} from "../../Source/Cesium.js";
+import MetadataType from "../../Source/Scene/MetadataType.js";
+import createScene from "../createScene.js";
+
+const randomNumber = CesiumMath.nextRandomNumber;
+const testQueryCoords = Cartesian3.fromElements(
+ randomNumber() - 0.5, // get in the same interval as the shape [-0.5, 0.5]
+ randomNumber() - 0.5,
+ randomNumber() - 0.5
+);
+const numberOfLevels = 15;
+const numberOfBins = Math.pow(2, numberOfLevels - 1);
+const maxIndex = numberOfBins - 1;
+const testQueryTileCoords = Cartesian3.fromElements(
+ Math.floor((testQueryCoords.x + 0.5) * numberOfBins),
+ Math.floor((testQueryCoords.y + 0.5) * numberOfBins),
+ Math.floor((testQueryCoords.z + 0.5) * numberOfBins)
+);
+if (testQueryCoords.x > maxIndex) {
+ testQueryCoords.x = maxIndex;
+}
+if (testQueryCoords.y > maxIndex) {
+ testQueryCoords.y = maxIndex;
+}
+if (testQueryCoords.z > maxIndex) {
+ testQueryCoords.z = maxIndex;
+}
+const metadataName = "dummyMetadataName";
+function DummyVoxelProvider() {
+ this.shape = new VoxelBoxShape();
+ const voxelDimension = 64;
+ this.voxelDimensions = new Cartesian3(
+ voxelDimension,
+ voxelDimension,
+ voxelDimension
+ );
+ const channelCount = 4;
+ this.voxelsPerTile = voxelDimension * voxelDimension * voxelDimension;
+ this.floatsPerTile = channelCount * this.voxelsPerTile;
+ this.ready = true;
+ this.readyPromise = Promise.resolve(this);
+ this._tileCount = 4096;
+ this.neighborEdgeCount = 0;
+ this.numberOfLevels = numberOfLevels;
+
+ this.properties = {};
+ this.properties[metadataName] = {
+ type: AttributeType.VEC4,
+ componentType: ComponentDatatype.FLOAT,
+ componentCount: 4,
+ min: 0,
+ max: 1,
+ count: this.voxelsPerTile * this._tileCount,
+ };
+}
+DummyVoxelProvider.prototype.requestData = function (options) {
+ const maxIndex = Math.pow(2, options.level) - 1;
+ const requestOutsideOfShape =
+ options.x < 0 ||
+ options.x > maxIndex ||
+ options.y < 0 ||
+ options.y > maxIndex ||
+ options.z < 0 ||
+ options.z > maxIndex;
+ if (options.level >= this.numberOfLevels || requestOutsideOfShape) {
+ return Promise.resolve(undefined);
+ }
+ const returnArray = new Uint32Array(this.floatsPerTile);
+ if (
+ (options.x === testQueryTileCoords.x &&
+ options.y === testQueryTileCoords.y &&
+ options.z === testQueryTileCoords.z &&
+ options.level === this.numberOfLevels - 1) ||
+ this.numberOfLevels === 1 // voxel index test
+ ) {
+ for (let i = 0; i < this.floatsPerTile; i++) {
+ returnArray[i] = i;
+ }
+ }
+ return Promise.resolve(returnArray);
+};
+
+const towardPrimitive = Cartesian3.fromElements(1.0, 1.0, 1.0);
+
+function turnCameraAround(scene) {
+ scene.camera.direction = Cartesian3.negate(towardPrimitive, new Cartesian3());
+ scene.renderForSpecs();
+}
+
+describe(
+ "Scene/VoxelTraversal",
+ function () {
+ const provider = new DummyVoxelProvider();
+ const scene = createScene();
+ const frameState = scene.frameState;
+ const camera = frameState.camera;
+ const context = scene.context;
+ const keyframeCount = 1;
+ const voxelDimensions = provider.voxelDimensions;
+ const neighborEdgeCount = provider.neighborEdgeCount;
+ const channelCount = provider.channelCount;
+ const minimumValues = provider.minimumValues;
+ const maximumValues = provider.maximumValues;
+ const datatypes = provider.datatypes;
+ const textureMemory = 500;
+
+ let traversalPromise = defer();
+ let primitive;
+ beforeEach(function () {
+ camera.position = Cartesian3.fromElements(-10, -10, -10);
+ camera.direction = Cartesian3.fromElements(1, 1, 1);
+ camera.frustum.fov = CesiumMath.PI_OVER_TWO;
+ scene.primitives.removeAll();
+ primitive = new VoxelPrimitive({
+ voxelProvider: provider,
+ });
+ scene.primitives.add(primitive);
+ scene.renderForSpecs();
+ traversalPromise = primitive.readyPromise.then(function () {
+ return new VoxelTraversal(
+ primitive,
+ context,
+ keyframeCount,
+ voxelDimensions,
+ neighborEdgeCount,
+ channelCount,
+ minimumValues,
+ maximumValues,
+ datatypes,
+ textureMemory
+ );
+ });
+ });
+
+ it("constructs with arguments", function () {
+ return traversalPromise.then(function (traversal) {
+ expect(traversal.primitive).toBe(primitive);
+ const megatextureKeys = Object.keys(traversal.megatextures);
+ expect(megatextureKeys.length).toBe(1);
+ expect(megatextureKeys).toEqual(
+ jasmine.arrayContaining([metadataName])
+ );
+ const megatexture = traversal.megatexture;
+ expect(megatexture.channelCount).toBe(
+ provider.properties[metadataName].componentCount
+ );
+ expect(megatexture.datatype).toBe(MetadataType.FLOAT);
+ const twiceNeighborEdgeCount = 2 * neighborEdgeCount;
+ expect(
+ megatexture.voxelCountPerTile.equals(
+ Cartesian3.add(
+ voxelDimensions,
+ Cartesian3.fromElements(
+ twiceNeighborEdgeCount,
+ twiceNeighborEdgeCount,
+ twiceNeighborEdgeCount
+ ),
+ new Cartesian3()
+ )
+ )
+ ).toBe(true);
+ expect(megatexture.metadataName).toBe(metadataName);
+ });
+ });
+
+ it("recomputes bounding volume when shape moves", function () {
+ return traversalPromise.then(function (traversal) {
+ const rootNode = traversal.rootNode;
+ const oldOrientedBoundingBox = rootNode.orientedBoundingBox.clone();
+ const shape = traversal.primitive._shape;
+ const translation = Cartesian3.fromElements(1, 1, 1);
+ shape.translation = translation;
+ shape.update();
+ const keyFrameLocation = 0;
+ const recomputeBoundingVolumes = true;
+ const pauseUpdate = false;
+ traversal.update(
+ frameState,
+ keyFrameLocation,
+ recomputeBoundingVolumes,
+ pauseUpdate
+ );
+ const newOrientedBoundingBox = rootNode.orientedBoundingBox.clone();
+ expect(
+ OrientedBoundingBox.equals(
+ oldOrientedBoundingBox,
+ newOrientedBoundingBox
+ )
+ ).toBe(false);
+ expect(newOrientedBoundingBox.center.equals(translation)).toBe(true);
+ });
+ });
+
+ it("computes screen space error for root tile", function () {
+ return traversalPromise.then(function (traversal) {
+ const rootNode = traversal.rootNode;
+ const cameraPosition = frameState.camera.positionWC;
+ const screenSpaceErrorDenominator =
+ frameState.camera.frustum.sseDenominator;
+ const screenHeight =
+ frameState.context.drawingBufferHeight / frameState.pixelRatio;
+ const screenSpaceErrorMultiplier =
+ screenHeight / screenSpaceErrorDenominator;
+ rootNode.computeScreenSpaceError(
+ cameraPosition,
+ screenSpaceErrorMultiplier
+ );
+
+ let distanceToCamera = Math.sqrt(
+ rootNode.orientedBoundingBox.distanceSquaredTo(cameraPosition)
+ );
+ distanceToCamera = Math.max(distanceToCamera, CesiumMath.EPSILON7);
+ const error =
+ screenSpaceErrorMultiplier *
+ (rootNode.approximateVoxelSize / distanceToCamera);
+ expect(rootNode.screenSpaceError).toBe(error);
+ });
+ });
+
+ it("computes visibility for root tile", function () {
+ return traversalPromise.then(function (traversal) {
+ const rootNode = traversal.rootNode;
+ const visibilityPlaneMask = CullingVolume.MASK_INDETERMINATE;
+
+ const visibilityWhenLookingAtRoot = rootNode.visibility(
+ frameState,
+ visibilityPlaneMask
+ );
+ expect(visibilityWhenLookingAtRoot).toBe(CullingVolume.MASK_INSIDE);
+
+ turnCameraAround(scene);
+ const visibilityWhenLookingAway = rootNode.visibility(
+ frameState,
+ visibilityPlaneMask
+ );
+ expect(visibilityWhenLookingAway).toBe(CullingVolume.MASK_OUTSIDE);
+ });
+ });
+
+ it("loads tiles into megatexture", function () {
+ return traversalPromise.then(function (traversal) {
+ const keyFrameLocation = 0;
+ const recomputeBoundingVolumes = true;
+ const pauseUpdate = false;
+ traversal.update(
+ frameState,
+ keyFrameLocation,
+ recomputeBoundingVolumes,
+ pauseUpdate
+ );
+
+ const megatexture = traversal.megatextures[metadataName];
+ let tilesInMegatextureCount = megatexture.occupiedCount;
+ const tileInQueueWhenLookingAtRoot = tilesInMegatextureCount === 1;
+ expect(tileInQueueWhenLookingAtRoot).toBe(true);
+
+ traversal.megatexture.remove(0);
+ turnCameraAround(scene);
+ traversal.update(
+ frameState,
+ keyFrameLocation,
+ recomputeBoundingVolumes,
+ pauseUpdate
+ );
+ tilesInMegatextureCount = traversal.megatexture.occupiedCount;
+ const tileNotInQueueWhenLookingAway = tilesInMegatextureCount === 0;
+ expect(tileNotInQueueWhenLookingAway).toBe(true);
+ });
+ });
+
+ it("unloads tiles in megatexture", function () {
+ return traversalPromise.then(function (traversal) {
+ const keyFrameLocation = 0;
+ const recomputeBoundingVolumes = true;
+ const pauseUpdate = false;
+ function updateTraversalTenTimes() {
+ // to fully fetch data and copy to texture
+ function updateTraversal() {
+ traversal.update(
+ frameState,
+ keyFrameLocation,
+ recomputeBoundingVolumes,
+ pauseUpdate
+ );
+ }
+ for (let i = 0; i < 10; i++) {
+ updateTraversal();
+ }
+ }
+
+ const eps = CesiumMath.EPSILON7;
+ const bottomLeftNearCorner = Cartesian3.fromElements(
+ -0.5 - eps,
+ -0.5 - eps,
+ -0.5 - eps
+ );
+ const topRightFarCorner = Cartesian3.fromElements(
+ 0.5 + eps,
+ 0.5 + eps,
+ 0.5 + eps
+ );
+ scene.camera.position = bottomLeftNearCorner;
+ updateTraversalTenTimes();
+ const numberOfNodesOnGPU = traversal.keyframeNodesInMegatexture.length;
+ const deepestNode =
+ traversal.keyframeNodesInMegatexture[numberOfNodesOnGPU - 1];
+ const deepestSpatialNode = deepestNode.spatialNode;
+ const nodeIsInMegatexture =
+ deepestNode.state === VoxelTraversal.LoadState.LOADED;
+ expect(nodeIsInMegatexture).toBe(true);
+
+ scene.camera.position = topRightFarCorner;
+ turnCameraAround(scene);
+ updateTraversalTenTimes();
+ const nodeNoLongerInMegatexture =
+ traversal.keyframeNodesInMegatexture.filter(function (keyFrameNode) {
+ const spatialNode = keyFrameNode.spatialNode;
+ return (
+ spatialNode.level === deepestSpatialNode.level &&
+ spatialNode.x === deepestSpatialNode.x &&
+ spatialNode.y === deepestSpatialNode.y &&
+ spatialNode.x === deepestSpatialNode.z
+ );
+ }).length === 0;
+ expect(nodeNoLongerInMegatexture).toBe(true);
+ });
+ });
+
+ it("gets tile metadata at a world cartesian coordiate", function () {
+ return traversalPromise
+ .then(function (traversal) {
+ return traversal.getMetadataAtWorldCartesian(
+ testQueryCoords,
+ metadataName
+ );
+ })
+ .then(function (queryValue) {
+ expect(queryValue[1]).not.toBe(0);
+ });
+ });
+
+ it("gives undefined when querying metadata outside of bounds", function () {
+ return traversalPromise
+ .then(function (traversal) {
+ return traversal.getMetadataAtWorldCartesian(
+ Cartesian3.fromElements(1, 1, 1),
+ metadataName
+ );
+ })
+ .then(function (queryValue) {
+ expect(queryValue).toBe(undefined);
+ });
+ });
+
+ it("returns right voxel within tile when querying", function () {
+ return traversalPromise.then(function (traversal) {
+ traversal.primitive.provider.numberOfLevels = 1;
+ const voxelsPerTile = provider.voxelsPerTile;
+ const queryPromises = new Array(voxelsPerTile);
+ const queryPointCoords = [
+ [-0.5, -0.5, -0.5],
+ [0, -0.5, -0.5],
+ [-0.5, 0, -0.5],
+ [0, 0, -0.5],
+ [-0.5, -0.5, 0],
+ [0, -0.5, 0],
+ [-0.5, 0, 0],
+ [0, 0, 0],
+ ];
+ queryPointCoords.forEach(function (coord, index) {
+ queryPromises[index] = traversal.getMetadataAtWorldCartesian(
+ Cartesian3.fromArray(coord),
+ metadataName
+ );
+ });
+ return Promise.all(queryPromises).then(function (queryValues) {
+ queryValues.forEach(function (value, index) {
+ expect(value[0]).toBe(index);
+ });
+ });
+ });
+ });
+ },
+ "WebGL"
+);
diff --git a/Specs/Widgets/Viewer/ViewerSpec.js b/Specs/Widgets/Viewer/ViewerSpec.js
index e0f816ae470..140a3e88636 100644
--- a/Specs/Widgets/Viewer/ViewerSpec.js
+++ b/Specs/Widgets/Viewer/ViewerSpec.js
@@ -25,6 +25,7 @@ import { ImageryLayerCollection } from "../../../Source/Cesium.js";
import { SceneMode } from "../../../Source/Cesium.js";
import { ShadowMode } from "../../../Source/Cesium.js";
import { TimeDynamicPointCloud } from "../../../Source/Cesium.js";
+import { VoxelPrimitive } from "../../../Source/Cesium.js";
import createViewer from "../../createViewer.js";
import DomEventSimulator from "../../DomEventSimulator.js";
import MockDataSource from "../../MockDataSource.js";
@@ -36,6 +37,7 @@ import { CesiumWidget } from "../../../Source/Cesium.js";
import { ClockViewModel } from "../../../Source/Cesium.js";
import { FullscreenButton } from "../../../Source/Cesium.js";
import { Geocoder } from "../../../Source/Cesium.js";
+import { GltfVoxelProvider } from "../../../Source/Cesium.js";
import { HomeButton } from "../../../Source/Cesium.js";
import { NavigationHelpButton } from "../../../Source/Cesium.js";
import { SceneModePicker } from "../../../Source/Cesium.js";
@@ -1375,6 +1377,77 @@ describe(
});
});
+ function loadVoxelPrimitive(viewer) {
+ const voxelPrimitive = new VoxelPrimitive({
+ provider: new GltfVoxelProvider({
+ url:
+ "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/0/0/0/0/tile.gltf",
+ }),
+ });
+ viewer.scene.primitives.add(voxelPrimitive);
+ return voxelPrimitive.readyPromise;
+ }
+
+ it("zoomTo zooms to VoxelPrimitive with default offset when offset not defined", function () {
+ viewer = createViewer(container);
+
+ return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
+ const expectedBoundingSphere = voxelPrimitive.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.0,
+ -0.5,
+ expectedBoundingSphere.radius
+ );
+
+ const promise = viewer.zoomTo(voxelPrimitive);
+ let wasCompleted = false;
+ spyOn(viewer.camera, "viewBoundingSphere").and.callFake(function (
+ boundingSphere,
+ offset
+ ) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ });
+
+ viewer._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("zoomTo zooms to VoxelPrimitive with offset", function () {
+ viewer = createViewer(container);
+
+ return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
+ const expectedBoundingSphere = voxelPrimitive.boundingSphere;
+ const expectedOffset = new HeadingPitchRange(
+ 0.4,
+ 1.2,
+ 4.0 * expectedBoundingSphere.radius
+ );
+
+ const promise = viewer.zoomTo(voxelPrimitive, expectedOffset);
+ let wasCompleted = false;
+ spyOn(viewer.camera, "viewBoundingSphere").and.callFake(function (
+ boundingSphere,
+ offset
+ ) {
+ expect(boundingSphere).toEqual(expectedBoundingSphere);
+ expect(offset).toEqual(expectedOffset);
+ wasCompleted = true;
+ });
+
+ viewer._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
it("zoomTo zooms to entity with undefined offset when offset not defined", function () {
viewer = createViewer(container);
viewer.entities.add({
@@ -1649,6 +1722,92 @@ describe(
});
});
+ it("flyTo flies to VoxelPrimitive with default offset when options not defined", function () {
+ viewer = createViewer(container);
+
+ return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
+ const promise = viewer.flyTo(voxelPrimitive);
+ let wasCompleted = false;
+
+ spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function (
+ target,
+ options
+ ) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ });
+
+ viewer._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flies to VoxelPrimitive with default offset when offset not defined", function () {
+ viewer = createViewer(container);
+ const options = {};
+
+ return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
+ const promise = viewer.flyTo(voxelPrimitive, options);
+ let wasCompleted = false;
+
+ spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function (
+ target,
+ options
+ ) {
+ expect(options.offset).toBeDefined();
+ expect(options.duration).toBeUndefined();
+ expect(options.maximumHeight).toBeUndefined();
+ wasCompleted = true;
+ options.complete();
+ });
+
+ viewer._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
+ it("flyTo flies to VoxelPrimitive when options are defined", function () {
+ viewer = createViewer(container);
+
+ // load tileset to test
+ return loadVoxelPrimitive(viewer).then(function (voxelPrimitive) {
+ const offsetVal = new HeadingPitchRange(3.0, 0.2, 2.3);
+ const options = {
+ offset: offsetVal,
+ duration: 3.0,
+ maximumHeight: 5.0,
+ };
+
+ const promise = viewer.flyTo(voxelPrimitive, options);
+ let wasCompleted = false;
+
+ spyOn(viewer.camera, "flyToBoundingSphere").and.callFake(function (
+ target,
+ options
+ ) {
+ expect(options.duration).toBeDefined();
+ expect(options.maximumHeight).toBeDefined();
+ wasCompleted = true;
+ options.complete();
+ });
+
+ viewer._postRender();
+
+ return promise.then(function () {
+ expect(wasCompleted).toEqual(true);
+ });
+ });
+ });
+
it("flyTo flies to entity with default offset when options not defined", function () {
viewer = createViewer(container);
From 2a6e026ff9b847fb06a133f5aa8695d749667db8 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 31 Mar 2022 15:24:03 -0400
Subject: [PATCH 002/679] cleaned up buildDrawCommands
---
Source/Scene/VoxelPrimitive.js | 623 ++++++++++++---------------------
1 file changed, 221 insertions(+), 402 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 5742baf92bf..838ccb3b3e7 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -19,7 +19,6 @@ import ShaderDestination from "../Renderer/ShaderDestination.js";
import BlendingState from "./BlendingState.js";
import CullFace from "./CullFace.js";
import CustomShader from "./ModelExperimental/CustomShader.js";
-import CustomShaderPipelineStage from "./ModelExperimental/CustomShaderPipelineStage.js";
import Material from "./Material.js";
import PolylineCollection from "./PolylineCollection.js";
import VoxelShapeType from "./VoxelShapeType.js";
@@ -27,8 +26,7 @@ import VoxelTraversal from "./VoxelTraversal.js";
import DrawCommand from "../Renderer/DrawCommand.js";
import Pass from "../Renderer/Pass.js";
import ShaderBuilder from "../Renderer/ShaderBuilder.js";
-// import VoxelFS from "../Shaders/VoxelFS.js";
-import VoxelFS_String from "./VoxelFS_String.js";
+import VoxelFS from "../Shaders/VoxelFS.js";
import VoxelVS from "../Shaders/VoxelVS.js";
import MetadataType from "./MetadataType.js";
/**
@@ -1339,12 +1337,12 @@ VoxelPrimitive.prototype.update = function (frameState) {
uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
uniforms.octreeLeafNodeTexelSizeUv = traversal.leafNodeTexelSizeUv;
- uniforms.megatextureTextures = [];
const megatextures = traversal.megatextures;
const megatexture = megatextures[0];
const megatextureLength = megatextures.length;
+ uniforms.megatextureTextures = new Array(megatextureLength);
for (let i = 0; i < megatextureLength; i++) {
- uniforms.megatextureTextures.push(megatextures[i].texture);
+ uniforms.megatextureTextures[i] = megatextures[i].texture;
}
uniforms.megatextureSliceDimensions = Cartesian2.clone(
@@ -1585,21 +1583,54 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
};
+/**
+ * @param {MetadataType} type
+ */
+function getGlslType(type) {
+ if (type === MetadataType.SCALAR) {
+ return "float";
+ } else if (type === MetadataType.VEC2) {
+ return "vec2";
+ } else if (type === MetadataType.VEC3) {
+ return "vec3";
+ } else if (type === MetadataType.VEC4) {
+ return "vec4";
+ }
+ return "vec4";
+}
+/**
+ * @param {MetadataType} type
+ */
+function getGlslTextureSwizzle(type) {
+ if (type === MetadataType.SCALAR) {
+ return ".r";
+ } else if (type === MetadataType.VEC2) {
+ return ".ra";
+ } else if (type === MetadataType.VEC3) {
+ return ".rgb";
+ } else if (type === MetadataType.VEC4) {
+ return "";
+ }
+ return "";
+}
+
+/**
+ * @param {MetadataType} type
+ * @param {Number} index
+ */
+function getGlslField(type, index) {
+ if (type === MetadataType.SCALAR) {
+ return "";
+ }
+ return `.[${index}]`;
+}
+
/**
* @param {VoxelPrimitive} that
* @param {Context} context
* @private
*/
function buildDrawCommands(that, context) {
- // const renderResources
- // CustomShaderPipelineStage.process(renderResources, primitive);
-
- // TODO: questions about custom shaders:
- // - where should attribute min/max go?
- // - should shader builder call `fromCache` or `replaceCache`
-
- // return;
-
const provider = that._provider;
const shapeType = provider.shape;
const names = provider.names;
@@ -1622,22 +1653,25 @@ function buildDrawCommands(that, context) {
const customShader = that._customShader;
const attributeLength = types.length;
- // Vertex shader
-
const shaderBuilder = new ShaderBuilder();
- shaderBuilder.addVertexLines([VoxelVS]);
- // Fragment shader
+ // Vertex shader
+ shaderBuilder.addVertexLines(["#line 0", VoxelVS]);
+ shaderBuilder.setPositionAttribute("vec4", "a_position");
- const fragmentStructId = CustomShaderPipelineStage.STRUCT_ID_FRAGMENT_INPUT;
- const fragmentStructName =
- CustomShaderPipelineStage.STRUCT_NAME_FRAGMENT_INPUT;
+ // Fragment shader
+ shaderBuilder.addFragmentLines([
+ customShader.fragmentShaderText,
+ "#line 0",
+ VoxelFS,
+ ]);
- const attributeStructId = CustomShaderPipelineStage.STRUCT_ID_ATTRIBUTES_FS;
- const attributeStructName = CustomShaderPipelineStage.STRUCT_NAME_ATTRIBUTES;
+ const fragmentStructId = "FragmentInput";
+ const fragmentStructName = "FragmentInput";
+ const attributeStructId = "Attributes";
+ const attributeStructName = "Attributes";
const attributeFieldName = "attributes";
-
- const voxelStructId = "VoxelFS";
+ const voxelStructId = "Voxel";
const voxelStructName = "Voxel";
const voxelFieldName = "voxel";
@@ -1648,49 +1682,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
- /**
- *
- * @param {MetadataType} type
- */
- function getGlslType(type) {
- if (type === MetadataType.SCALAR) {
- return "float";
- } else if (type === MetadataType.VEC2) {
- return "vec2";
- } else if (type === MetadataType.VEC3) {
- return "vec3";
- } else if (type === MetadataType.VEC4) {
- return "vec4";
- }
- return "vec4";
- }
- /**
- * @param {MetadataType} type
- */
- function getGlslTextureSwizzle(type) {
- if (type === MetadataType.SCALAR) {
- return ".r";
- } else if (type === MetadataType.VEC2) {
- return ".ra";
- } else if (type === MetadataType.VEC3) {
- return ".rgb";
- } else if (type === MetadataType.VEC4) {
- return "";
- }
- return "";
- }
-
- /**
- * @param {MetadataType} type
- * @param {Number} index
- */
- function getGlslField(type, index) {
- if (type === MetadataType.SCALAR) {
- return "";
- }
- return `.[${index}]`;
- }
-
for (let i = 0; i < attributeLength; i++) {
const name = names[i];
const type = types[i];
@@ -1731,34 +1722,177 @@ function buildDrawCommands(that, context) {
fragmentStructName,
ShaderDestination.FRAGMENT
);
-
shaderBuilder.addStructField(
fragmentStructId,
attributeStructName,
attributeFieldName
);
-
shaderBuilder.addStructField(
fragmentStructId,
voxelStructName,
voxelFieldName
);
- // Custom shader
- shaderBuilder.addFragmentLines([customShader.fragmentShaderText]);
+ shaderBuilder.addUniform(
+ "sampler2D",
+ "u_megatextureTextures[METADATA_COUNT]",
+ ShaderDestination.FRAGMENT
+ );
+
+ // clearAttributes function
+ {
+ const clearAttributesFunctionId = "clearAttributes";
+ const clearAttributesFunctionName = "clearAttributes";
+
+ shaderBuilder.addFunction(
+ clearAttributesFunctionId,
+ `${attributeStructName} ${clearAttributesFunctionName}()`,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
+ `${attributeStructName} attributes;`,
+ ]);
+
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const glslType = getGlslType(type, componentType);
+ shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
+ `attributes.${name} = ${glslType}(0.0);`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
+ `return attributes;`,
+ ]);
+ }
+
+ // sumAttributes function
+ {
+ const sumAttributesFunctionId = "sumAttributes";
+ const sumAttributesFunctionName = "sumAttributes";
+ const sumAttributesFunctionDeclaration = `${attributeStructName} ${sumAttributesFunctionName}(${attributeStructName} attributesA, ${attributeStructName} attributesB)`;
+
+ shaderBuilder.addFunction(
+ sumAttributesFunctionId,
+ sumAttributesFunctionDeclaration,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
+ `${attributeStructName} attributes;`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
+ `${attributeFieldName}.${name} = attributesA.${name} + attributesB.${name};`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
+ `return attributes;`,
+ ]);
+ }
+
+ // mixAttributes
+ {
+ const mixAttributesFunctionId = "mixAttributes";
+ const mixAttributesFunctionName = "mixAttributes";
+ const mixAttributesFieldAttributesA = "attributesA";
+ const mixAttributesFieldAttributesB = "attributesB";
+ const mixAttributesFieldMixAmount = "mixFactor";
+ shaderBuilder.addFunction(
+ mixAttributesFunctionId,
+ `${attributeStructName} ${mixAttributesFunctionName}(${attributeStructName} ${mixAttributesFieldAttributesA}, ${attributeStructName} ${mixAttributesFieldAttributesB}, float ${mixAttributesFieldMixAmount})`,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
+ `${attributeStructName} attributes;`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
+ `attributes.${name} = mix(${mixAttributesFieldAttributesA}.${name}, ${mixAttributesFieldAttributesB}.${name}, ${mixAttributesFieldMixAmount});`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
+ `return attributes;`,
+ ]);
+ }
+
+ // setMinMaxAttributes function
+ if (defined(minimumValues) && defined(maximumValues)) {
+ shaderBuilder.addDefine(
+ "HAS_MIN_MAX",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ const minMaxAttributesFunctionId = "setMinMaxAttributes";
+ const minMaxAttributesFunctionName = "setMinMaxAttributes";
+ shaderBuilder.addFunction(
+ minMaxAttributesFunctionId,
+ `void ${minMaxAttributesFunctionName}(inout ${voxelStructName} ${voxelFieldName})`,
+ ShaderDestination.FRAGMENT
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentCount = MetadataType.getComponentCount(type);
+ for (let j = 0; j < componentCount; j++) {
+ const minimumValue = minimumValues[i][j];
+ const maximumValue = maximumValues[i][j];
+
+ // glsl needs to have `.0` at the end of whole numbers floats.
+ let minimumValueString = minimumValue.toString();
+ if (minimumValueString.indexOf(".") === -1) {
+ minimumValueString = `${minimumValue}.0`;
+ }
+ let maximumValueString = maximumValue.toString();
+ if (maximumValueString.indexOf(".") === -1) {
+ maximumValueString = `${maximumValue}.0`;
+ }
+
+ const glslField = getGlslField(type, j);
+ const minLine = `${voxelFieldName}.${name}Minimum${glslField} = ${minimumValueString};`;
+ const maxLine = `${voxelFieldName}.${name}Maximum${glslField} = ${maximumValueString};`;
+ shaderBuilder.addFunctionLines(minMaxAttributesFunctionId, [
+ minLine,
+ maxLine,
+ ]);
+ }
+ }
+ }
+
+ const sampleFrom2DMegatextureId = "sampleFrom2DMegatextureAtUv";
+ const sampleFrom2DMegatextureName = "sampleFrom2DMegatextureAtUv";
+ shaderBuilder.addFunction(
+ sampleFrom2DMegatextureId,
+ `${attributeStructName} ${sampleFrom2DMegatextureName}(vec2 uv)`,
+ ShaderDestination.FRAGMENT
+ );
+
+ shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
+ `${attributeStructName} attributes;`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const glslTextureSwizzle = getGlslTextureSwizzle(type, componentType);
+ shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
+ `attributes.${name} = texture2D(u_megatextureTextures[${i}], uv)${glslTextureSwizzle};`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
+ `return attributes;`,
+ ]);
- // Voxel shader
- shaderBuilder.addFragmentLines(["#line 0", VoxelFS_String]);
shaderBuilder.addDefine(
"METADATA_COUNT",
attributeLength,
ShaderDestination.FRAGMENT
);
- shaderBuilder.addUniform(
- "sampler2D",
- "u_megatextureTextures[METADATA_COUNT]",
- ShaderDestination.FRAGMENT
- );
shaderBuilder.addDefine(
`SHAPE_${shapeType}`,
@@ -1825,15 +1959,15 @@ function buildDrawCommands(that, context) {
const isDefaultMaxX = maxBounds.x === defaultMaxBounds.x;
const isDefaultMaxY = maxBounds.y === defaultMaxBounds.y;
const isDefaultMaxZ = maxBounds.z === defaultMaxBounds.z;
-
- if (
+ const useBounds =
!isDefaultMinX ||
!isDefaultMinY ||
!isDefaultMinZ ||
!isDefaultMaxX ||
!isDefaultMaxY ||
- !isDefaultMaxZ
- ) {
+ !isDefaultMaxZ;
+
+ if (useBounds) {
shaderBuilder.addDefine("BOUNDS", undefined, ShaderDestination.FRAGMENT);
}
@@ -1851,7 +1985,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
}
-
if (!isDefaultMinY) {
shaderBuilder.addDefine(
"BOUNDS_1_MIN",
@@ -1866,7 +1999,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
}
-
if (!isDefaultMinZ) {
shaderBuilder.addDefine(
"BOUNDS_2_MIN",
@@ -1976,327 +2108,22 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
- if (
+ const useClippingBounds =
minClippingBounds.x !== defaultMinBounds.x ||
minClippingBounds.y !== defaultMinBounds.y ||
minClippingBounds.z !== defaultMinBounds.z ||
maxClippingBounds.x !== defaultMaxBounds.x ||
maxClippingBounds.y !== defaultMaxBounds.y ||
- maxClippingBounds.z !== defaultMaxBounds.z
- ) {
- shaderBuilder.addDefine(
- "CLIPPING_BOUNDS",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
+ maxClippingBounds.z !== defaultMaxBounds.z;
- // clearAttributes function
- {
- const clearAttributesFunctionId = "clearAttributes";
- const clearAttributesFunctionName = "clearAttributes";
-
- shaderBuilder.addFunction(
- clearAttributesFunctionId,
- `${attributeStructName} ${clearAttributesFunctionName}()`,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
- `${attributeStructName} attributes;`,
- ]);
-
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const componentType = componentTypes[i];
- const glslType = getGlslType(type, componentType);
- shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
- `attributes.${name} = ${glslType}(0.0);`,
- ]);
- }
- shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
- `return attributes;`,
- ]);
- }
-
- // sumAttributes function
- {
- const sumAttributesFunctionId = "sumAttributes";
- const sumAttributesFunctionName = "sumAttributes";
- const sumAttributesFunctionDeclaration = `${attributeStructName} ${sumAttributesFunctionName}(${attributeStructName} attributesA, ${attributeStructName} attributesB)`;
-
- shaderBuilder.addFunction(
- sumAttributesFunctionId,
- sumAttributesFunctionDeclaration,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
- `${attributeStructName} attributes;`,
- ]);
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
- `${attributeFieldName}.${name} = attributesA.${name} + attributesB.${name};`,
- ]);
- }
- shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
- `return attributes;`,
- ]);
- }
-
- // mixAttributes
- {
- const mixAttributesFunctionId = "mixAttributes";
- const mixAttributesFunctionName = "mixAttributes";
- const mixAttributesFieldAttributesA = "attributesA";
- const mixAttributesFieldAttributesB = "attributesB";
- const mixAttributesFieldMixAmount = "mixFactor";
- shaderBuilder.addFunction(
- mixAttributesFunctionId,
- `${attributeStructName} ${mixAttributesFunctionName}(${attributeStructName} ${mixAttributesFieldAttributesA}, ${attributeStructName} ${mixAttributesFieldAttributesB}, float ${mixAttributesFieldMixAmount})`,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
- `${attributeStructName} attributes;`,
- ]);
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
- `attributes.${name} = mix(${mixAttributesFieldAttributesA}.${name}, ${mixAttributesFieldAttributesB}.${name}, ${mixAttributesFieldMixAmount});`,
- ]);
- }
- shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
- `return attributes;`,
- ]);
- }
-
- // setMinMaxAttributes function
- if (defined(minimumValues) && defined(maximumValues)) {
+ if (useClippingBounds) {
shaderBuilder.addDefine(
- "HAS_MIN_MAX",
+ "CLIPPING_BOUNDS",
undefined,
ShaderDestination.FRAGMENT
);
- const minMaxAttributesFunctionId = "setMinMaxAttributes";
- const minMaxAttributesFunctionName = "setMinMaxAttributes";
- shaderBuilder.addFunction(
- minMaxAttributesFunctionId,
- `void ${minMaxAttributesFunctionName}(inout ${voxelStructName} ${voxelFieldName})`,
- ShaderDestination.FRAGMENT
- );
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const componentCount = MetadataType.getComponentCount(type);
- for (let j = 0; j < componentCount; j++) {
- const minimumValue = minimumValues[i][j];
- const maximumValue = maximumValues[i][j];
-
- // glsl needs to have `.0` at the end of whole numbers floats.
- let minimumValueString = minimumValue.toString();
- if (minimumValueString.indexOf(".") === -1) {
- minimumValueString = `${minimumValue}.0`;
- }
- let maximumValueString = maximumValue.toString();
- if (maximumValueString.indexOf(".") === -1) {
- maximumValueString = `${maximumValue}.0`;
- }
-
- const glslField = getGlslField(type, j);
- const minLine = `${voxelFieldName}.${name}Minimum${glslField} = ${minimumValueString};`;
- const maxLine = `${voxelFieldName}.${name}Maximum${glslField} = ${maximumValueString};`;
- shaderBuilder.addFunctionLines(minMaxAttributesFunctionId, [
- minLine,
- maxLine,
- ]);
- }
- }
- }
-
- // for (i = 0; i < propertiesLength; i++) {
- // property = properties[i];
- // type = property.type;
- // name = property.name;
- // channelCount = AttributeType.getNumberOfComponents(type);
- // minValue = property.min;
- // maxValue = property.max;
- // sampleType = getStyleInputSampleType(property);
- // sampleScale = getTypeScale(property);
-
- // const mins = [];
- // const maxs = [];
-
- // if (channelCount === 1) {
- // mins.push(`${sampleScale} * ${toTypedString(minValue, property)}`);
- // maxs.push(`${sampleScale} * ${toTypedString(maxValue, property)}`);
- // } else {
- // mins.push(`${sampleScale} * ${toTypedString(minValue.x, property)}`);
- // maxs.push(`${sampleScale} * ${toTypedString(maxValue.x, property)}`);
-
- // mins.push(`${sampleScale} * ${toTypedString(minValue.y, property)}`);
- // maxs.push(`${sampleScale} * ${toTypedString(maxValue.y, property)}`);
-
- // if (channelCount >= 3) {
- // mins.push(`${sampleScale} * ${toTypedString(minValue.z, property)}`);
- // maxs.push(`${sampleScale} * ${toTypedString(maxValue.z, property)}`);
- // }
- // if (channelCount >= 4) {
- // mins.push(`${sampleScale} * ${toTypedString(minValue.w, property)}`);
- // maxs.push(`${sampleScale} * ${toTypedString(maxValue.w, property)}`);
- // }
- // }
-
- // const minVec = `${sampleType}(${mins.join(", ")})`;
- // const maxVec = `${sampleType}(${maxs.join(", ")})`;
-
- // shaderBuilder.addFunctionLines("setMinMax", [
- // `styleInput.${name}Minimum = ${minVec};`,
- // `styleInput.${name}Maximum = ${maxVec};`,
- // ]);
- // }
-
- // const styleShaderSource = primitive._styleMaterial.shaderSource;
- // shaderBuilder.addDefine("STYLE_USE_NORMAL");
- // if (styleShaderSource.includes("styleInput.positionEC")) {
- // shaderBuilder.addDefine("STYLE_USE_POSITION_EC");
- // }
- // shaderBuilder.addFragmentLines([styleShaderSource]);
-
- // Generate shaders
- shaderBuilder.setPositionAttribute("vec4", "a_position");
-
- // // set normals
- // const setNormalsArgs = [
- // `in vec3 normalLocal[${propertiesLength}]`,
- // `in vec3 normalWorld[${propertiesLength}]`,
- // `in vec3 normalView[${propertiesLength}]`,
- // `in bool normalValid[${propertiesLength}]`,
- // "inout StyleInput styleInput",
- // ];
-
- // const setNormalsSig = `void setStyleInputNormals(${setNormalsArgs.join(
- // ", "
- // )})`;
-
- // shaderBuilder.addFunction(
- // "setNormals",
- // setNormalsSig,
- // ShaderDestination.FRAGMENT
- // );
-
- // for (i = 0; i < propertiesLength; i++) {
- // property = properties[i];
- // name = property.name;
- // shaderBuilder.addFunctionLines("setNormals", [
- // `styleInput.${name}NormalLocal` + ` = normalLocal[${i}];`,
- // `styleInput.${name}NormalWorld` + ` = normalWorld[${i}];`,
- // `styleInput.${name}NormalView` + ` = normalView[${i}];`,
- // `styleInput.${name}NormalValid` + ` = normalValid[${i}];`,
- // ]);
- // }
-
- // // set samples
- // const setSamplesFunctionId = "setSamples";
- // const setSamplesDefinition = `void setSamples(in vec4 samples[${attributeLength}], inout ${CustomShaderPipelineStage.STRUCT_NAME_ATTRIBUTES} ${attributesFieldName})`;
-
- // shaderBuilder.addFunction(
- // setSamplesFunctionId,
- // setSamplesDefinition,
- // ShaderDestination.FRAGMENT
- // );
-
- // for (let i = 0; i < attributeLength; i++) {
- // const name = names[i];
- // // const scaleVar = `scale_${name}`;
-
- // shaderBuilder.addFunctionLines(setSamplesFunctionId, [
- // // `vec4 ${scaleVar} = vec4(${sampleScale});`,
- // // `attributes.${name} = ${sampleType}(${scaleVar} * samples[${i}]${swizzler});`,
- // `${attributesFieldName}.${name} = `,
- // ]);
- // }
-
- // shaderBuilder.addFunction(
- // "decodeSamples",
- // "void decodeTextureSamples(inout vec4 samples[METADATA_COUNT])",
- // ShaderDestination.FRAGMENT
- // );
-
- // shaderBuilder.addFunctionLines("decodeSamples", ["vec4 sample;"]);
-
- // for (i = 0; i < propertiesLength; i++) {
- // property = properties[i];
- // type = property.type;
- // channelCount = AttributeType.getNumberOfComponents(type);
-
- // shaderBuilder.addFunctionLines("decodeSamples", [
- // `sample = samples[${i}];`,
- // ]);
-
- // if (!defined(channelCount) || channelCount === 1) {
- // shaderBuilder.addFunctionLines("decodeSamples", [
- // "sample = vec4(1.0, 1.0, 1.0, sample.r);",
- // ]);
- // } else if (channelCount === 2) {
- // shaderBuilder.addFunctionLines("decodeSamples", [
- // "sample = vec4(sample.r, sample.a, 1.0, 1.0);",
- // ]);
- // }
-
- // if (type === MetadataType.UINT8) {
- // shaderBuilder.addFunctionLines("decodeSamples", [
- // `sample = mix(u_minimumValues[${i}], u_maximumValues[${i}], sample);`,
- // ]);
- // }
-
- // shaderBuilder.addFunctionLines("decodeSamples", [
- // `samples[${i}] = sample;`,
- // ]);
- // }
-
- // Looping over the sampler array was causing strange rendering artifacts even though the shader compiled fine.
- // Unroling the for loop fixed the problem.
- // for (int i = 0; i < METADATA_COUNT; i++)
- // {
- // vec4 value0 = texture2D(u_megatextureTextures[i], uv0);
- // vec4 value1 = texture2D(u_megatextureTextures[i], uv1);
- // samples[i] = mix(value0, value1, lerp);
- // }
-
- const sampleFrom2DMegatextureId = "sampleFrom2DMegatextureAtUv";
- const sampleFrom2DMegatextureName = "sampleFrom2DMegatextureAtUv";
- shaderBuilder.addFunction(
- sampleFrom2DMegatextureId,
- `${attributeStructName} ${sampleFrom2DMegatextureName}(vec2 uv)`,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
- `${attributeStructName} attributes;`,
- ]);
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const componentType = componentTypes[i];
- const glslTextureSwizzle = getGlslTextureSwizzle(type, componentType);
- shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
- `attributes.${name} = texture2D(u_megatextureTextures[${i}], uv)${glslTextureSwizzle};`,
- ]);
}
- shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
- `return attributes;`,
- ]);
-
- // shaderBuilder.addFunctionLines("sampleUv", [
- // "decodeTextureSamples(samples);",
- // ]);
- // const voxelPrimitive = renderResources.model.voxelPrimitive;
- // addRuntimeVoxelProperties(shaderBuilder, voxelPrimitive);
- // // primitive.update(frameState);
const shaderBuilderPick = shaderBuilder.clone();
shaderBuilderPick.addDefine("PICKING", undefined, ShaderDestination.FRAGMENT);
@@ -2315,10 +2142,7 @@ function buildDrawCommands(that, context) {
blending: BlendingState.ALPHA_BLEND,
});
- // var baseUniformMap = that._uniformMap;
- // var uniformMap = combine(baseUniformMap, styleUniformMap);
const uniformMap = that._uniformMap;
-
const viewportQuadVertexArray = context.getViewportQuadVertexArray();
const drawCommand = new DrawCommand({
vertexArray: viewportQuadVertexArray,
@@ -2329,9 +2153,6 @@ function buildDrawCommands(that, context) {
pass: Pass.VOXELS,
executeInClosestFrustum: true,
owner: this,
- // boundingVolume: primitiveRenderResources.boundingSphere,
- // modelMatrix: primitiveRenderResources.modelMatrix,
- // debugShowBoundingVolume : true
});
const drawCommandPick = DrawCommand.shallowClone(
@@ -2355,8 +2176,6 @@ function buildDrawCommands(that, context) {
that._drawCommand = drawCommand;
that._drawCommandPick = drawCommandPick;
-
- console.log(drawCommand.shaderProgram._fragmentShaderText);
}
/**
From ea60548c3834c23b849a5040b547a7808119292f Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 31 Mar 2022 22:03:09 -0400
Subject: [PATCH 003/679] custom shader improvements and relatively large
shader cleanup
---
Apps/Sandcastle/gallery/Voxels.html | 4 +-
CHANGES.md | 1 +
Source/Scene/VoxelPrimitive.js | 621 +++++++++++++----------
Source/Shaders/VoxelFS.glsl | 735 +++++++++++++---------------
4 files changed, 704 insertions(+), 657 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index 8ec79d69033..a55ffc38e8a 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -356,7 +356,7 @@
const customShaderColor = new Cesium.CustomShader({
fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
- material.diffuse = fsInput.attributes.color.rgb;
+ material.diffuse = fsInput.metadata.color.rgb;
material.alpha = 1.0;
}`,
});
@@ -364,7 +364,7 @@
const customShaderAlpha = new Cesium.CustomShader({
fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
- material.diffuse = vec3(1.0);//fsInput.attributes.a);
+ material.diffuse = vec3(1.0);//fsInput.metadata.a);
material.alpha = 1.0;
}`,
});
diff --git a/CHANGES.md b/CHANGES.md
index 371ed97dd90..666a47bddc5 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -21,6 +21,7 @@
- Refactored metadata API so `tileset.metadata` and `content.group.metadata` are more symmetric with `content.metadata` and `tile.metadata`. [#10224](https://github.com/CesiumGS/cesium/pull/10224)
- Added support for `EXT_structural_metadata` property attributes in `CustomShader` [#10228](https://github.com/CesiumGS/cesium/pull/10228)
- Added partial support for `EXT_structural_metadata` property textures in `CustomShader` [#10247](https://github.com/CesiumGS/cesium/pull/10247)
+- Added experimental voxel rendering that supports glTF with EXT_primitive_voxels, 3D Tiles, and procedural data. [#10253](https://github.com/CesiumGS/cesium/pull/10253).
##### Fixes :wrench:
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 838ccb3b3e7..034777b6919 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1583,8 +1583,12 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
};
+// Shader builder helpers
+
/**
* @param {MetadataType} type
+ * @returns {String}
+ * @private
*/
function getGlslType(type) {
if (type === MetadataType.SCALAR) {
@@ -1596,10 +1600,11 @@ function getGlslType(type) {
} else if (type === MetadataType.VEC4) {
return "vec4";
}
- return "vec4";
}
/**
* @param {MetadataType} type
+ * @returns {String}
+ * @private
*/
function getGlslTextureSwizzle(type) {
if (type === MetadataType.SCALAR) {
@@ -1611,12 +1616,45 @@ function getGlslTextureSwizzle(type) {
} else if (type === MetadataType.VEC4) {
return "";
}
- return "";
+}
+
+/**
+ * @param {MetadataType} type
+ * @returns {String}
+ * @private
+ */
+function getGlslPartialDerivativeType(type) {
+ if (type === MetadataType.SCALAR) {
+ return "vec3";
+ } else if (type === MetadataType.VEC2) {
+ return "mat2";
+ } else if (type === MetadataType.VEC3) {
+ return "mat3";
+ } else if (type === MetadataType.VEC4) {
+ return "mat4";
+ }
+}
+
+/**
+ * GLSL needs to have `.0` at the end of whole number floats or else it's
+ * treated like an integer.
+ * @param {Number} number
+ * @returns {String}
+ * @private
+ */
+function getGlslNumberAsFloat(number) {
+ let numberString = number.toString();
+ if (numberString.indexOf(".") === -1) {
+ numberString = `${number}.0`;
+ }
+ return numberString;
}
/**
* @param {MetadataType} type
* @param {Number} index
+ * @returns {String}
+ * @private
*/
function getGlslField(type, index) {
if (type === MetadataType.SCALAR) {
@@ -1652,261 +1690,49 @@ function buildDrawCommands(that, context) {
const nearestSampling = that._nearestSampling;
const customShader = that._customShader;
const attributeLength = types.length;
+ const hasStatistics = defined(minimumValues) && defined(maximumValues);
+
+ // Build shader
const shaderBuilder = new ShaderBuilder();
// Vertex shader
- shaderBuilder.addVertexLines(["#line 0", VoxelVS]);
- shaderBuilder.setPositionAttribute("vec4", "a_position");
+
+ shaderBuilder.addVertexLines([VoxelVS]);
// Fragment shader
+
shaderBuilder.addFragmentLines([
customShader.fragmentShaderText,
"#line 0",
VoxelFS,
]);
- const fragmentStructId = "FragmentInput";
- const fragmentStructName = "FragmentInput";
- const attributeStructId = "Attributes";
- const attributeStructName = "Attributes";
- const attributeFieldName = "attributes";
- const voxelStructId = "Voxel";
- const voxelStructName = "Voxel";
- const voxelFieldName = "voxel";
-
- // Attribute struct
- shaderBuilder.addStruct(
- attributeStructId,
- attributeStructName,
- ShaderDestination.FRAGMENT
- );
-
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const glslType = getGlslType(type);
- shaderBuilder.addStructField(attributeStructId, glslType, name);
- }
-
- // Voxel struct
- shaderBuilder.addStruct(
- voxelStructId,
- voxelStructName,
- ShaderDestination.FRAGMENT
- );
-
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const glslType = getGlslType(type);
- shaderBuilder.addStructField(voxelStructId, glslType, `${name}Minimum`);
- shaderBuilder.addStructField(voxelStructId, glslType, `${name}Maximum`);
- shaderBuilder.addStructField(voxelStructId, "vec3", `${name}NormalLocal`);
- shaderBuilder.addStructField(voxelStructId, "vec3", `${name}NormalWorld`);
- shaderBuilder.addStructField(voxelStructId, "vec3", `${name}NormalView`);
- shaderBuilder.addStructField(voxelStructId, "bool", `${name}NormalValid`);
- }
-
- shaderBuilder.addStructField(voxelStructId, "vec3", "positionEC");
- shaderBuilder.addStructField(voxelStructId, "vec3", "positionUv");
- shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvShapeSpace");
- shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvLocal");
- shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirUv");
- shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirWorld");
- shaderBuilder.addStructField(voxelStructId, "float", "travelDistance");
-
- // FragmentInput struct
- shaderBuilder.addStruct(
- fragmentStructId,
- fragmentStructName,
- ShaderDestination.FRAGMENT
- );
- shaderBuilder.addStructField(
- fragmentStructId,
- attributeStructName,
- attributeFieldName
- );
- shaderBuilder.addStructField(
- fragmentStructId,
- voxelStructName,
- voxelFieldName
- );
-
- shaderBuilder.addUniform(
- "sampler2D",
- "u_megatextureTextures[METADATA_COUNT]",
- ShaderDestination.FRAGMENT
- );
-
- // clearAttributes function
- {
- const clearAttributesFunctionId = "clearAttributes";
- const clearAttributesFunctionName = "clearAttributes";
-
- shaderBuilder.addFunction(
- clearAttributesFunctionId,
- `${attributeStructName} ${clearAttributesFunctionName}()`,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
- `${attributeStructName} attributes;`,
- ]);
-
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const componentType = componentTypes[i];
- const glslType = getGlslType(type, componentType);
- shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
- `attributes.${name} = ${glslType}(0.0);`,
- ]);
- }
- shaderBuilder.addFunctionLines(clearAttributesFunctionId, [
- `return attributes;`,
- ]);
- }
-
- // sumAttributes function
- {
- const sumAttributesFunctionId = "sumAttributes";
- const sumAttributesFunctionName = "sumAttributes";
- const sumAttributesFunctionDeclaration = `${attributeStructName} ${sumAttributesFunctionName}(${attributeStructName} attributesA, ${attributeStructName} attributesB)`;
-
- shaderBuilder.addFunction(
- sumAttributesFunctionId,
- sumAttributesFunctionDeclaration,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
- `${attributeStructName} attributes;`,
- ]);
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
- `${attributeFieldName}.${name} = attributesA.${name} + attributesB.${name};`,
- ]);
- }
- shaderBuilder.addFunctionLines(sumAttributesFunctionId, [
- `return attributes;`,
- ]);
- }
-
- // mixAttributes
- {
- const mixAttributesFunctionId = "mixAttributes";
- const mixAttributesFunctionName = "mixAttributes";
- const mixAttributesFieldAttributesA = "attributesA";
- const mixAttributesFieldAttributesB = "attributesB";
- const mixAttributesFieldMixAmount = "mixFactor";
- shaderBuilder.addFunction(
- mixAttributesFunctionId,
- `${attributeStructName} ${mixAttributesFunctionName}(${attributeStructName} ${mixAttributesFieldAttributesA}, ${attributeStructName} ${mixAttributesFieldAttributesB}, float ${mixAttributesFieldMixAmount})`,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
- `${attributeStructName} attributes;`,
- ]);
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
- `attributes.${name} = mix(${mixAttributesFieldAttributesA}.${name}, ${mixAttributesFieldAttributesB}.${name}, ${mixAttributesFieldMixAmount});`,
- ]);
- }
- shaderBuilder.addFunctionLines(mixAttributesFunctionId, [
- `return attributes;`,
- ]);
- }
-
- // setMinMaxAttributes function
- if (defined(minimumValues) && defined(maximumValues)) {
- shaderBuilder.addDefine(
- "HAS_MIN_MAX",
- undefined,
- ShaderDestination.FRAGMENT
- );
- const minMaxAttributesFunctionId = "setMinMaxAttributes";
- const minMaxAttributesFunctionName = "setMinMaxAttributes";
- shaderBuilder.addFunction(
- minMaxAttributesFunctionId,
- `void ${minMaxAttributesFunctionName}(inout ${voxelStructName} ${voxelFieldName})`,
- ShaderDestination.FRAGMENT
- );
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const componentCount = MetadataType.getComponentCount(type);
- for (let j = 0; j < componentCount; j++) {
- const minimumValue = minimumValues[i][j];
- const maximumValue = maximumValues[i][j];
-
- // glsl needs to have `.0` at the end of whole numbers floats.
- let minimumValueString = minimumValue.toString();
- if (minimumValueString.indexOf(".") === -1) {
- minimumValueString = `${minimumValue}.0`;
- }
- let maximumValueString = maximumValue.toString();
- if (maximumValueString.indexOf(".") === -1) {
- maximumValueString = `${maximumValue}.0`;
- }
-
- const glslField = getGlslField(type, j);
- const minLine = `${voxelFieldName}.${name}Minimum${glslField} = ${minimumValueString};`;
- const maxLine = `${voxelFieldName}.${name}Maximum${glslField} = ${maximumValueString};`;
- shaderBuilder.addFunctionLines(minMaxAttributesFunctionId, [
- minLine,
- maxLine,
- ]);
- }
- }
- }
-
- const sampleFrom2DMegatextureId = "sampleFrom2DMegatextureAtUv";
- const sampleFrom2DMegatextureName = "sampleFrom2DMegatextureAtUv";
- shaderBuilder.addFunction(
- sampleFrom2DMegatextureId,
- `${attributeStructName} ${sampleFrom2DMegatextureName}(vec2 uv)`,
- ShaderDestination.FRAGMENT
- );
-
- shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
- `${attributeStructName} attributes;`,
- ]);
- for (let i = 0; i < attributeLength; i++) {
- const name = names[i];
- const type = types[i];
- const componentType = componentTypes[i];
- const glslTextureSwizzle = getGlslTextureSwizzle(type, componentType);
- shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
- `attributes.${name} = texture2D(u_megatextureTextures[${i}], uv)${glslTextureSwizzle};`,
- ]);
- }
- shaderBuilder.addFunctionLines(sampleFrom2DMegatextureId, [
- `return attributes;`,
- ]);
+ // Fragment shader defines
shaderBuilder.addDefine(
"METADATA_COUNT",
attributeLength,
ShaderDestination.FRAGMENT
);
-
shaderBuilder.addDefine(
`SHAPE_${shapeType}`,
undefined,
ShaderDestination.FRAGMENT
);
+ shaderBuilder.addDefine(
+ "MEGATEXTURE_2D",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+
if (
!Cartesian3.equals(paddingBefore, Cartesian3.ZERO) ||
!Cartesian3.equals(paddingAfter, Cartesian3.ZERO)
) {
shaderBuilder.addDefine("PADDING", undefined, ShaderDestination.FRAGMENT);
}
-
if (depthTest) {
shaderBuilder.addDefine(
"DEPTH_TEST",
@@ -1925,7 +1751,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
}
-
if (jitter) {
shaderBuilder.addDefine("JITTER", undefined, ShaderDestination.FRAGMENT);
}
@@ -1937,10 +1762,16 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
}
-
if (despeckle) {
shaderBuilder.addDefine("DESPECKLE", undefined, ShaderDestination.FRAGMENT);
}
+ if (hasStatistics) {
+ shaderBuilder.addDefine(
+ "STATISTICS",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
const sampleCount = keyframeCount > 1 ? 2 : 1;
shaderBuilder.addDefine(
@@ -1952,7 +1783,6 @@ function buildDrawCommands(that, context) {
const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
const defaultMinBounds = ShapeConstructor.DefaultMinBounds;
const defaultMaxBounds = ShapeConstructor.DefaultMaxBounds;
-
const isDefaultMinX = minBounds.x === defaultMinBounds.x;
const isDefaultMinY = minBounds.y === defaultMinBounds.y;
const isDefaultMinZ = minBounds.z === defaultMinBounds.z;
@@ -1966,11 +1796,9 @@ function buildDrawCommands(that, context) {
!isDefaultMaxX ||
!isDefaultMaxY ||
!isDefaultMaxZ;
-
if (useBounds) {
shaderBuilder.addDefine("BOUNDS", undefined, ShaderDestination.FRAGMENT);
}
-
if (!isDefaultMinX) {
shaderBuilder.addDefine(
"BOUNDS_0_MIN",
@@ -2013,7 +1841,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
}
-
let intersectionCount = 0;
if (shapeType === VoxelShapeType.BOX) {
// A bounded box is still a box, so it has the same number of shape intersections: 1
@@ -2028,7 +1855,6 @@ function buildDrawCommands(that, context) {
);
intersectionCount++;
}
-
// Intersects an inner ellipsoid for the min radius
if (!isDefaultMinZ) {
shaderBuilder.addDefine(
@@ -2038,7 +1864,6 @@ function buildDrawCommands(that, context) {
);
intersectionCount++;
}
-
// Intersects a cone for min latitude
if (!isDefaultMinY) {
shaderBuilder.addDefine(
@@ -2048,7 +1873,6 @@ function buildDrawCommands(that, context) {
);
intersectionCount++;
}
-
// Intersects a cone for max latitude
if (!isDefaultMaxY) {
shaderBuilder.addDefine(
@@ -2058,7 +1882,6 @@ function buildDrawCommands(that, context) {
);
intersectionCount++;
}
-
// Intersects a wedge for the min and max longitude
if (!isDefaultMinX || !isDefaultMaxX) {
shaderBuilder.addDefine(
@@ -2079,7 +1902,6 @@ function buildDrawCommands(that, context) {
);
intersectionCount++;
}
-
// Intersects an inner infinite cylinder for the min radius
if (!isDefaultMinX) {
shaderBuilder.addDefine(
@@ -2089,7 +1911,6 @@ function buildDrawCommands(that, context) {
);
intersectionCount++;
}
-
// Intersects a wedge for the min and max theta
if (!isDefaultMinZ || !isDefaultMaxZ) {
shaderBuilder.addDefine(
@@ -2100,14 +1921,12 @@ function buildDrawCommands(that, context) {
intersectionCount++;
}
}
-
// The intersection count is multiplied by 2 because there is an enter and exit for each intersection
shaderBuilder.addDefine(
"SHAPE_INTERSECTION_COUNT",
intersectionCount * 2,
ShaderDestination.FRAGMENT
);
-
const useClippingBounds =
minClippingBounds.x !== defaultMinBounds.x ||
minClippingBounds.y !== defaultMinBounds.y ||
@@ -2115,7 +1934,6 @@ function buildDrawCommands(that, context) {
maxClippingBounds.x !== defaultMaxBounds.x ||
maxClippingBounds.y !== defaultMaxBounds.y ||
maxClippingBounds.z !== defaultMaxBounds.z;
-
if (useClippingBounds) {
shaderBuilder.addDefine(
"CLIPPING_BOUNDS",
@@ -2124,12 +1942,309 @@ function buildDrawCommands(that, context) {
);
}
+ // Fragment shader uniforms
+
+ shaderBuilder.addUniform(
+ "sampler2D",
+ "u_megatextureTextures[METADATA_COUNT]",
+ ShaderDestination.FRAGMENT
+ );
+
+ // Fragment shader structs
+
+ // PropertyStatistics structs
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const propertyStatisticsStructId = `PropertyStatistics_${name}`;
+ const propertyStatisticsStructName = `PropertyStatistics_${name}`;
+ shaderBuilder.addStruct(
+ propertyStatisticsStructId,
+ propertyStatisticsStructName,
+ ShaderDestination.FRAGMENT
+ );
+ const glslType = getGlslType(type);
+ shaderBuilder.addStructField(propertyStatisticsStructId, glslType, "min");
+ shaderBuilder.addStructField(propertyStatisticsStructId, glslType, "max");
+ }
+
+ // Statistics struct
+ const statisticsStructId = "Statistics";
+ const statisticsStructName = "Statistics";
+ const statisticsFieldName = "statistics";
+ shaderBuilder.addStruct(
+ statisticsStructId,
+ statisticsStructName,
+ ShaderDestination.FRAGMENT
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const propertyStructName = `PropertyStatistics_${name}`;
+ const propertyFieldName = name;
+ shaderBuilder.addStructField(
+ statisticsStructId,
+ propertyStructName,
+ propertyFieldName
+ );
+ }
+
+ // Metadata struct
+ const metadataStructId = "Metadata";
+ const metadataStructName = "Metadata";
+ const metadataFieldName = "metadata";
+ shaderBuilder.addStruct(
+ metadataStructId,
+ metadataStructName,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addStructField(
+ metadataStructId,
+ statisticsStructName,
+ statisticsFieldName
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const glslType = getGlslType(type);
+ shaderBuilder.addStructField(metadataStructId, glslType, name);
+ }
+
+ // VoxelProperty structs
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const glslType = getGlslPartialDerivativeType(type);
+ const voxelPropertyStructId = `VoxelProperty_${name}`;
+ const voxelPropertyStructName = `VoxelProperty_${name}`;
+ shaderBuilder.addStruct(
+ voxelPropertyStructId,
+ voxelPropertyStructName,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addStructField(
+ voxelPropertyStructId,
+ glslType,
+ "partialDerivativeLocal"
+ );
+ shaderBuilder.addStructField(
+ voxelPropertyStructId,
+ glslType,
+ "partialDerivativeWorld"
+ );
+ shaderBuilder.addStructField(
+ voxelPropertyStructId,
+ glslType,
+ "partialDerivativeView"
+ );
+ shaderBuilder.addStructField(
+ voxelPropertyStructId,
+ glslType,
+ "partialDerivativeValid"
+ );
+ }
+
+ // Voxel struct
+ const voxelStructId = "Voxel";
+ const voxelStructName = "Voxel";
+ const voxelFieldName = "voxel";
+ shaderBuilder.addStruct(
+ voxelStructId,
+ voxelStructName,
+ ShaderDestination.FRAGMENT
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const voxelPropertyStructName = `VoxelProperty_${name}`;
+ shaderBuilder.addStructField(voxelStructId, voxelPropertyStructName, name);
+ }
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionEC");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionUv");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvShapeSpace");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvLocal");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirUv");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirWorld");
+ shaderBuilder.addStructField(voxelStructId, "float", "travelDistance");
+
+ // FragmentInput struct
+ const fragmentInputStructId = "FragmentInput";
+ const fragmentInputStructName = "FragmentInput";
+ shaderBuilder.addStruct(
+ fragmentInputStructId,
+ fragmentInputStructName,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addStructField(
+ fragmentInputStructId,
+ metadataStructName,
+ metadataFieldName
+ );
+ shaderBuilder.addStructField(
+ fragmentInputStructId,
+ voxelStructName,
+ voxelFieldName
+ );
+
+ // Properties struct
+ const propertiesStructId = "Properties";
+ const propertiesStructName = "Properties";
+ const propertiesFieldName = "properties";
+ shaderBuilder.addStruct(
+ propertiesStructId,
+ propertiesStructName,
+ ShaderDestination.FRAGMENT
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const glslType = getGlslType(type);
+ shaderBuilder.addStructField(propertiesStructId, glslType, name);
+ }
+
+ // Fragment shader functions
+
+ // clearProperties function
+ {
+ const functionId = "clearProperties";
+ shaderBuilder.addFunction(
+ functionId,
+ `${propertiesStructName} clearProperties()`,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesStructName} ${propertiesFieldName};`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const glslType = getGlslType(type, componentType);
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesFieldName}.${name} = ${glslType}(0.0);`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(functionId, [
+ `return ${propertiesFieldName};`,
+ ]);
+ }
+
+ // sumProperties function
+ {
+ const functionId = "sumProperties";
+ shaderBuilder.addFunction(
+ functionId,
+ `${propertiesStructName} sumProperties(${propertiesStructName} propertiesA, ${propertiesStructName} propertiesB)`,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesStructName} ${propertiesFieldName};`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesFieldName}.${name} = propertiesA.${name} + propertiesB.${name};`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(functionId, [
+ `return ${propertiesFieldName};`,
+ ]);
+ }
+
+ // mixProperties
+ {
+ const functionId = "mixProperties";
+ shaderBuilder.addFunction(
+ functionId,
+ `${propertiesStructName} mixProperties(${propertiesStructName} propertiesA, ${propertiesStructName} propertiesB, float mixFactor)`,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesStructName} ${propertiesFieldName};`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesFieldName}.${name} = mix(propertiesA.${name}, propertiesB.${name}, mixFactor);`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(functionId, [
+ `return ${propertiesFieldName};`,
+ ]);
+ }
+
+ // copyPropertiesToMetadata
+ {
+ const functionId = "copyPropertiesToMetadata";
+ shaderBuilder.addFunction(
+ functionId,
+ `void copyPropertiesToMetadata(in ${propertiesStructName} ${propertiesFieldName}, inout ${metadataStructName} ${metadataFieldName})`,
+ ShaderDestination.FRAGMENT
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(functionId, [
+ `${metadataFieldName}.${name} = ${propertiesFieldName}.${name};`,
+ ]);
+ }
+ }
+
+ // setStatistics function
+ if (hasStatistics) {
+ const functionId = "setStatistics";
+ shaderBuilder.addFunction(
+ functionId,
+ `void setStatistics(inout ${statisticsStructName} ${statisticsFieldName})`,
+ ShaderDestination.FRAGMENT
+ );
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentCount = MetadataType.getComponentCount(type);
+ for (let j = 0; j < componentCount; j++) {
+ const glslField = getGlslField(type, j);
+ const minimumValue = minimumValues[i][j];
+ const maximumValue = maximumValues[i][j];
+ shaderBuilder.addFunctionLines(functionId, [
+ `${statisticsFieldName}.${name}.min${glslField} = ${getGlslNumberAsFloat(
+ minimumValue
+ )};`,
+ `${statisticsFieldName}.${name}.max${glslField} = ${getGlslNumberAsFloat(
+ maximumValue
+ )};`,
+ ]);
+ }
+ }
+ }
+
+ // getPropertiesFrom2DMegatextureAtUv
+ {
+ const functionId = "getPropertiesFrom2DMegatextureAtUv";
+ shaderBuilder.addFunction(
+ functionId,
+ `${propertiesStructName} getPropertiesFrom2DMegatextureAtUv(vec2 texcoord)`,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesStructName} ${propertiesFieldName};`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ const type = types[i];
+ const componentType = componentTypes[i];
+ const glslTextureSwizzle = getGlslTextureSwizzle(type, componentType);
+ shaderBuilder.addFunctionLines(functionId, [
+ `properties.${name} = texture2D(u_megatextureTextures[${i}], texcoord)${glslTextureSwizzle};`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(functionId, [
+ `return ${propertiesFieldName};`,
+ ]);
+ }
+
+ // Compile shaders
const shaderBuilderPick = shaderBuilder.clone();
shaderBuilderPick.addDefine("PICKING", undefined, ShaderDestination.FRAGMENT);
-
const shaderProgram = shaderBuilder.buildShaderProgram(context);
const shaderProgramPick = shaderBuilderPick.buildShaderProgram(context);
-
const renderState = RenderState.fromCache({
cull: {
enabled: true,
@@ -2142,6 +2257,7 @@ function buildDrawCommands(that, context) {
blending: BlendingState.ALPHA_BLEND,
});
+ // Create the draw commands
const uniformMap = that._uniformMap;
const viewportQuadVertexArray = context.getViewportQuadVertexArray();
const drawCommand = new DrawCommand({
@@ -2155,6 +2271,7 @@ function buildDrawCommands(that, context) {
owner: this,
});
+ // Create the pick draw command
const drawCommandPick = DrawCommand.shallowClone(
drawCommand,
new DrawCommand()
@@ -2176,6 +2293,8 @@ function buildDrawCommands(that, context) {
that._drawCommand = drawCommand;
that._drawCommandPick = drawCommandPick;
+
+ // console.log(drawCommand.shaderProgram._fragmentShaderText);
}
/**
@@ -2239,20 +2358,6 @@ VoxelPrimitive.prototype.destroy = function () {
return destroyObject(this);
};
-VoxelPrimitive.prototype.queryMetadataAtCartographic = function (
- cartographic,
- metadata
-) {
- return this._traversal.getMetadataAtCartographic(cartographic, metadata);
-};
-
-VoxelPrimitive.prototype.queryMetadataAtCartesian = function (
- cartesian,
- metadata
-) {
- return this._traversal.getMetadataAtWorldCartesian(cartesian, metadata);
-};
-
const corners = new Array(
new Cartesian4(-1.0, -1.0, -1.0, 1.0),
new Cartesian4(+1.0, -1.0, -1.0, 1.0),
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 3ff9e885c75..ee2650d6b04 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1,65 +1,149 @@
-// world space: Cartesian WGS84
-// local space: Cartesian [-0.5, 0.5] aligned with shape.
-// For box, the origin is the center of the box, and the six sides sit on the planes x = -0.5, x = 0.5 etc.
-// For cylinder, the origin is the center of the cylinder with the cylinder enclosed by the [-0.5, 0.5] box on xy-plane. Positive x-axis points to theta = 0. The top and bottom caps sit at planes z = -0.5, z = 0.5. Positive y points to theta = pi/2
-// For ellipsoid, the origin is the center of the ellipsoid. The maximum height of the ellipsoid touches -0.5, 0.5 in xyz directions.
-// intersection space: local space times 2 to be [-1, 1]. Used for ray intersection calculation
-// UV space: local space plus 0.5 to be [0, 1].
-// shape space: In the coordinate system of the shape [0, 1]
-// For box, this is the same as UV space
-// For cylinder, the coordinate system is (radius, theta, z). theta = 0 is aligned with x axis
-// For ellipsoid, the coordinate system is (longitude, latitude, height). where 0 is the minimum value in each dimension, and 1 is the max.
-
-
-// TODO is this necessary? Or should it go somewhere else?
-precision highp int;
-
-// Defines that are filled in from VoxelPrimitive.js
-// #define METADATA_COUNT XYZ
-// #define SAMPLE_COUNT XYZ
-// #define NEAREST_SAMPLING
-
-// Uniforms that are filled in from VoxelPrimitive.js
-// uniform sampler2D u_megatextureTextures[METADATA_COUNT];
-
-// Functions that are filled in from VoxelPrimitive.js
-// Attributes sampleFrom2DMegatextureAtUv(vec2 uv);
-// Attributes clearAttributes();
-// Attributes sumAttributes(Attributes attributesA, Attributes attributesB);
-// Attributes mixAttributes(Attributes attributesA, Attributes attributesB, float mixFactor);
-// void setMinMaxAttributes(inout Voxel voxel);
-
-#define OCTREE_MAX_LEVELS 32
+/*
+Don't delete this comment!
+Some shader code is dynamically generated in VoxelPrimitive.js to support custom shaders with arbitrary metadata.
+Below is an example of how this code might look. Properties like "temperature" and "direction" are just examples.
+
+// Defines
+#define PROPERTY_COUNT ###
+#define SAMPLE_COUNT ###
+#define SHAPE_BOX
+#define SHAPE_ELLIPSOID
+#define SHAPE_CYLINDER
+#define SHAPE_INTERSECTION_COUNT ###
+#define MEGATEXTURE_2D
+#define MEGATEXTURE_3D
+#define DEPTH_TEST
+#define JITTER
+#define NEAREST_SAMPLING
+#define DESPECKLE
+#define STATISTICS
+#define PADDING
+#define BOUNDS
+#define CLIPPING_BOUNDS
+#define PICKING
+
+// Uniforms
+uniform sampler2D u_megatextureTextures[PROPERTY_COUNT];
+
+// Structs
+struct PropertyStatistics_temperature {
+ float min;
+ float max;
+};
+struct PropertyStatistics_direction {
+ vec3 min;
+ vec3 max;
+};
+struct Statistics {
+ PropertyStatistics_temperature temperature;
+ PropertyStatistics_direction direction;
+};
+struct Metadata {
+ Statistics statistics;
+ float temperature;
+ vec3 direction;
+};
+struct VoxelProperty_temperature {
+ vec3 partialDerivativeLocal;
+ vec3 partialDerivativeWorld;
+ vec3 partialDerivativeView;
+ bool partialDerivativeValid;
+};
+struct VoxelProperty_direction {
+ mat3 partialDerivativeLocal;
+ mat3 partialDerivativeWorld;
+ mat3 partialDerivativeView;
+ bool partialDerivativeValid;
+};
+struct Voxel {
+ VoxelProperty_temperature temperature;
+ VoxelProperty_direction direction;
+ vec3 positionEC;
+ vec3 positionUv;
+ vec3 positionUvShapeSpace;
+ vec3 positionUvLocal;
+ vec3 viewDirUv;
+ vec3 viewDirWorld;
+ float travelDistance;
+};
+struct FragmentInput {
+ Metadata metadata;
+ Voxel voxel;
+};
+struct Properties {
+ // This struct is similar to Metadata but is not part of the custom shader API and
+ // is intended to be used internally as a lightweight way to pass around properties.
+ float temperature;
+ vec3 direction;
+};
+// Functions
+Properties clearProperties() {
+ Properties properties;
+ properties.temperature = 0.0;
+ properties.direction = vec3(0.0);
+ return properties;
+}
+Properties sumProperties(Properties propertiesA, Properties propertiesB) {
+ Properties properties;
+ properties.temperature = propertiesA.temperature + propertiesB.temperature;
+ properties.direction = propertiesA.direction + propertiesB.direction;
+ return properties;
+}
+Properties mixProperties(Properties propertiesA, Properties propertiesB, float mixFactor) {
+ Properties properties;
+ properties.temperature = mix(propertiesA.temperature, propertiesB.temperature, mixFactor);
+ properties.direction = mix(propertiesA.direction, propertiesB.direction, mixFactor);
+ return properties;
+}
+void copyPropertiesToMetadata(in Properties properties, inout Metadata metadata) {
+ metadata.temperature = properties.temperature;
+ metadata.direction = properties.direction;
+}
+void setStatistics(inout Statistics statistics) {
+ // Assume the "direction" property has no min/max
+ statistics.temperature.min = 20.0;
+ statistics.temperature.max = 50.0;
+}
+Properties getPropertiesFrom2DMegatextureAtUv(vec2 texcoord) {
+ Properties properties;
+ properties.temperature = texture2D(u_megatextureTextures[0], texcoord).r;
+ properties.direction = texture2D(u_megatextureTextures[1], texcoord).rgb;
+ return properties;
+}
+Properties getPropertiesFrom3DMegatextureAtUv(vec3 texcoord) {
+ Properties properties;
+ properties.temperature = texture3D(u_megatextureTextures[0], texcoord).r;
+ properties.direction = texture3D(u_megatextureTextures[1], texcoord).rgb;
+ return properties;
+}
+void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
+ vec3 direction = fsInput.metadata.direction;
+ float temperature = fsInput.metadata.temperature;
+ float minTemperature = fsInput.metadata.statistics.temperature.min;
+ float maxTemperature = fsInput.metadata.statistics.temperature.max;
+
+ material.diffuse = abs(direction);
+ material.alpha = (temperature - minTemperature) / (maxTemperature - minTemperature);
+}
+*/
+
+// These octree flags must be in sync with GpuOctreeFlag in VoxelTraversal.js
#define OCTREE_FLAG_INTERNAL 0
#define OCTREE_FLAG_LEAF 1
#define OCTREE_FLAG_PACKED_LEAF_FROM_PARENT 2
-struct OctreeNodeData {
- int data;
- int flag;
-};
-
-struct SampleData {
- int megatextureIndex;
- int levelsAbove;
- #if (SAMPLE_COUNT > 1)
- float weight;
- #endif
-};
+#define STEP_COUNT_MAX 1000 // Harcoded value because GLSL doesn't like variable length loops
+#define OCTREE_MAX_LEVELS 32 // Harcoded value because GLSL doesn't like variable length loops
+#define ALPHA_ACCUM_MAX 0.98 // Must be > 0.0 and <= 1.0
-#if defined(SHAPE_ELLIPSOID)
-uniform float u_ellipsoidHeightDifferenceUv;
-uniform vec3 u_ellipsoidOuterRadiiLocal; // [0,1]
-uniform vec3 u_ellipsoidInverseRadiiSquaredLocal;
-#endif
-
-// 2D megatexture
+#if defined(MEGATEXTURE_2D)
uniform ivec2 u_megatextureSliceDimensions; // number of slices per tile, in two dimensions
uniform ivec2 u_megatextureTileDimensions; // number of tiles per megatexture, in two dimensions
uniform vec2 u_megatextureVoxelSizeUv;
uniform vec2 u_megatextureSliceSizeUv;
uniform vec2 u_megatextureTileSizeUv;
+#endif
uniform ivec3 u_dimensions; // does not include padding
#if defined(PADDING)
@@ -67,13 +151,6 @@ uniform ivec3 u_paddingBefore;
uniform ivec3 u_paddingAfter;
#endif
-uniform vec4 u_minimumValues[METADATA_COUNT];
-uniform vec4 u_maximumValues[METADATA_COUNT];
-// uniform bool u_voxelQuantization[METADATA_COUNT];
-uniform int u_channelCount[METADATA_COUNT];
-
-uniform float u_stepSize;
-
uniform sampler2D u_octreeInternalNodeTexture;
uniform vec2 u_octreeInternalNodeTexelSizeUv;
uniform int u_octreeInternalNodeTilesPerRow;
@@ -86,6 +163,7 @@ uniform mat4 u_transformPositionUvToView;
uniform mat3 u_transformDirectionViewToLocal;
uniform mat3 u_transformNormalLocalToWorld;
uniform vec3 u_cameraPositionUv;
+uniform float u_stepSize;
#if defined(BOUNDS)
uniform vec3 u_minBounds; // Bounds from the voxel primitive
@@ -101,19 +179,38 @@ uniform vec3 u_minClippingBounds;
uniform vec3 u_maxClippingBounds;
#endif
+#if defined(SHAPE_ELLIPSOID)
+uniform float u_ellipsoidHeightDifferenceUv;
+uniform vec3 u_ellipsoidOuterRadiiLocal; // [0,1]
+uniform vec3 u_ellipsoidInverseRadiiSquaredLocal;
+#endif
+
#if defined(PICKING)
uniform vec4 u_pickColor;
#endif
+struct OctreeNodeData {
+ int data;
+ int flag;
+};
+
+struct SampleData {
+ int megatextureIndex;
+ int levelsAbove;
+ #if (SAMPLE_COUNT > 1)
+ float weight;
+ #endif
+};
+
// --------------------------------------------------------
// Misc math
// --------------------------------------------------------
#if defined(JITTER)
-#define HASHSCALE1 50.0
-float hash12(vec2 p)
+#define HASHSCALE 50.0
+float hash(vec2 p)
{
- vec3 p3 = fract(vec3(p.xyx) * HASHSCALE1);
+ vec3 p3 = fract(vec3(p.xyx) * HASHSCALE);
p3 += dot(p3, p3.yzx + 19.19);
return fract((p3.x + p3.y) * p3.z);
}
@@ -146,9 +243,15 @@ int normU8x2_toInt(vec2 value) {
float normU8x2_toFloat(vec2 value) {
return float(normU8x2_toInt(value)) / 65535.0;
}
+vec2 index1DTo2DTexcoord(int index, ivec2 dimensions, vec2 uvScale)
+{
+ int indexX = intMod(index, dimensions.x);
+ int indexY = index / dimensions.x;
+ return vec2(indexX, indexY) * uvScale;
+}
// --------------------------------------------------------
-// Intersection tests, coordinate conversions, etc
+// Intersection tests, shape coordinate conversions, etc
// --------------------------------------------------------
struct Ray
@@ -164,7 +267,7 @@ const float InfHit = czm_infinity;
vec2 resolveIntersections(vec2 intersections[SHAPE_INTERSECTION_COUNT])
{
// TODO: completely skip shape if both of its Ts are below 0.0?
- vec2 tEntryExit = vec2(NoHit, NoHit);
+ vec2 entryExitT = vec2(NoHit, NoHit);
// Sort the intersections from min T to max T with bubble sort.
// Note: If this sorting function changes, some of the intersection test may
@@ -213,19 +316,19 @@ vec2 resolveIntersections(vec2 intersections[SHAPE_INTERSECTION_COUNT])
// entering positive or exiting negative
if (surroundCount == 1 && surroundIsPositive && enter == currShapeIsPositive) {
- tEntryExit.x = t;
+ entryExitT.x = t;
}
// exiting positive or entering negative after being inside positive
// TODO: Can this be simplified?
if ((!enter && currShapeIsPositive && surroundCount == 0) || (enter && !currShapeIsPositive && surroundCount == 2 && surroundIsPositive)) {
- tEntryExit.y = t;
+ entryExitT.y = t;
// entry and exit have been found, so the loop can stop
break;
}
}
- return tEntryExit;
+ return entryExitT;
}
#endif
@@ -701,6 +804,47 @@ float ellipseDistanceIterative (vec2 p, in vec2 ab) {
}
#endif
+vec2 intersectShape(vec3 positionUv, vec3 directionUv) {
+ // Do a ray-shape intersection to find the exact starting and ending points.
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ Ray ray = Ray(positionUv * 2.0 - 1.0, directionUv * 2.0);
+
+ #if defined(SHAPE_BOX)
+ vec2 entryExitT = intersectBoxShape(ray);
+ #elif defined(SHAPE_CYLINDER)
+ vec2 entryExitT = intersectCylinderShape(ray);
+ #elif defined(SHAPE_ELLIPSOID)
+ vec2 entryExitT = intersectEllipsoidShape(ray);
+ #endif
+
+ if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
+ // Intersection is invalid when start and end are behind the ray.
+ return vec2(NoHit, NoHit);
+ }
+
+ // Set start to 0 when ray is inside the shape.
+ entryExitT.x = max(entryExitT.x, 0.0);
+
+ return entryExitT;
+}
+
+#if defined(DEPTH_TEST)
+float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 viewPosUv, vec3 viewDirUv) {
+ float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenUv));
+ if (logDepthOrDepth != 0.0) {
+ // Calculate how far the ray must travel before it hits the depth buffer.
+ vec4 eyeCoordinateDepth = czm_windowToEyeCoordinates(fragCoord, logDepthOrDepth);
+ eyeCoordinateDepth /= eyeCoordinateDepth.w;
+ vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
+ return dot(viewDirUv, depthPositionUv - viewPosUv);
+ } else {
+ // There's no depth at this position so set it to some really far value.
+ return czm_infinity;
+ }
+}
+#endif
+
#if defined(SHAPE_BOX)
vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
return positionUv;
@@ -771,7 +915,7 @@ vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
#if defined(BOUNDS)
positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
// TODO: This breaks down when minBounds == maxBounds. To fix it, this
- // function would have to know if ray is intersecting the front or back of a shape
+ // function would have to know if ray is intersecting the front or back of the shape
// and set the shape space position to 1 (front) or 0 (back) accordingly.
#endif
@@ -828,43 +972,9 @@ vec3 transformFromShapeSpaceToUv(in vec3 positionUvShapeSpace) {
// Megatexture
// --------------------------------------------------------
-#if defined(MEGATEXTURE_IS_3D)
-// TODO: 3D textures have not been implemented yet
-
-void sampleFrom3DMegatextureAtUv(vec3 uv, out Attributes attributes)
-{
- // Looping over the sampler array was causing strange rendering artifacts even though the shader compiled fine.
- // Unroling the for loop fixed the problem.
-
- // for (int i = 0; i < METADATA_COUNT; i++)
- // {
- // samples[i] = texture3D(u_megatextureTextures[i], uv);
- // }
-
- #if (METADATA_COUNT >= 1)
- samples[0] = texture3D(u_megatextureTextures[0], uv);
- #endif
- #if (METADATA_COUNT >= 2)
- samples[1] = texture3D(u_megatextureTextures[1], uv);
- #endif
- #if (METADATA_COUNT >= 3)
- samples[2] = texture3D(u_megatextureTextures[2], uv);
- #endif
- #if (METADATA_COUNT >= 4)
- samples[3] = texture3D(u_megatextureTextures[3], uv);
- #endif
-
- decodeTextureSamples(samples);
-}
-
-// TODO: this function has not been implemented
-vec3 indexToUv3D(int index, ivec3 dimensions, vec3 uvScale)
-{
- return vec3(0.0);
-}
-
-// TODO: this function has not been tested
-void sampleFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex, out Attributes attributes)
+// TODO: 3D megatexture has not been implemented yet
+#if defined(MEGATEXTURE_3D)
+Properties getPropertiesFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
{
// Tile location
vec3 tileUvOffset = indexToUv3d(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
@@ -875,33 +985,36 @@ void sampleFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int til
// Final location in the megatexture
vec3 uv = tileUvOffset + voxelUvOffset;
- for (int i = 0; i < METADATA_COUNT; i++) {
+ for (int i = 0; i < PROPERTY_COUNT; i++) {
vec4 sample = texture3D(u_megatextureTextures[i], uv);
samples[i] = decodeTextureSample(sample);
}
}
-
-#else // MEGATEXTURE_IS_2D
+#elif defined(MEGATEXTURE_2D)
/*
- How 3D data is stored in a 2D megatexture
+ How is 3D data stored in a 2D megatexture?
- 2D megatexture with a single 2x2x2 voxel tile:
- The tile is split into two "slices" by Z:
+ In this example there is only one loaded tile and it has 2x2x2 voxels (8 voxels total).
+ The data is sliced by Z. The data at Z = 0 is placed in texels (0,0), (0,1), (1,0), (1,1) and
+ the data at Z = 1 is placed in texels (2,0), (2,1), (3,0), (3,1).
+ Note that there could be empty space in the megatexture because it's a power of two.
0 1 2 3
+---+---+---+---+
| | | | | 3
+---+---+---+---+
| | | | | 2
- +---+---+---+---+
+ +-------+-------+
|010|110|011|111| 1
- +---+---+---+---+
+ |--- ---|--- ---|
|000|100|001|101| 0
- +---+---+---+---+
+ +-------+-------+
- (The megatexture likes to be power of two even if it means some empty space)
-
- When the 3D coordinate's Z value is between two slices:
+ When doing linear interpolation the megatexture needs to be sampled twice: once for
+ the Z slice above the voxel coordinate and once for the slice below. The two slices
+ are interpolated with fract(coord.z - 0.5). For example, a Z coordinate of 1.0 is
+ halfway between two Z slices so the interpolation factor is 0.5. Below is a side view
+ of the 3D voxel grid with voxel coordinates on the left side.
2 +---+
|001|
@@ -909,16 +1022,9 @@ void sampleFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int til
|000|
0 +---+
- The interpolation between the bottom and the top voxel is 0.5
- More generally, the interpolation is: fract(coord.z - 0.5)
+ When doing nearest neighbor the megatexture only needs to be sampled once at the closest Z slice.
*/
-
-vec2 indexToUv2D(int index, ivec2 dimensions, vec2 uvScale) {
- int indexX = intMod(index, dimensions.x);
- int indexY = index / dimensions.x;
- return vec2(indexX, indexY) * uvScale;
-}
-Attributes sampleFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
+Properties getPropertiesFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
{
#if defined(NEAREST_SAMPLING)
// Round to the center of the nearest voxel
@@ -926,35 +1032,35 @@ Attributes sampleFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims,
#endif
// Tile location
- vec2 tileUvOffset = indexToUv2D(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
+ vec2 tileUvOffset = index1DTo2DTexcoord(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
- // Slice locations
+ // Slice location
float slice = voxelCoord.z - 0.5;
- float sliceLerp = fract(slice);
int sliceIndex = int(floor(slice));
int sliceIndex0 = intMax(sliceIndex, 0);
- int sliceIndex1 = intMin(sliceIndex + 1, voxelDims.z - 1);
- vec2 sliceUvOffset0 = indexToUv2D(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
- vec2 sliceUvOffset1 = indexToUv2D(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
+ vec2 sliceUvOffset0 = index1DTo2DTexcoord(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
// Voxel location
vec2 voxelUvOffset = clamp(voxelCoord.xy, vec2(0.5), vec2(voxelDims.xy) - vec2(0.5)) * u_megatextureVoxelSizeUv;
// Final location in the megatexture
vec2 uv0 = tileUvOffset + sliceUvOffset0 + voxelUvOffset;
- vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
#if defined(NEAREST_SAMPLING)
- return sampleFrom2DMegatextureAtUv(uv0);
+ return getPropertiesFrom2DMegatextureAtUv(uv0);
#else
- Attributes attributes0 = sampleFrom2DMegatextureAtUv(uv0);
- Attributes attributes1 = sampleFrom2DMegatextureAtUv(uv1);
- return mixAttributes(attributes0, attributes1, sliceLerp);
+ float sliceLerp = fract(slice);
+ int sliceIndex1 = intMin(sliceIndex + 1, voxelDims.z - 1);
+ vec2 sliceUvOffset1 = index1DTo2DTexcoord(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
+ vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
+ Properties properties0 = getPropertiesFrom2DMegatextureAtUv(uv0);
+ Properties properties1 = getPropertiesFrom2DMegatextureAtUv(uv1);
+ return mixProperties(properties0, properties1, sliceLerp);
#endif
}
#endif
-Attributes sampleFromMegatextureAtTileUv(vec3 tileUv, int tileIndex) {
+Properties getPropertiesFromMegatextureAtTileUv(vec3 tileUv, int tileIndex) {
vec3 voxelCoord = tileUv * vec3(u_dimensions);
ivec3 dimensions = u_dimensions;
@@ -963,15 +1069,49 @@ Attributes sampleFromMegatextureAtTileUv(vec3 tileUv, int tileIndex) {
voxelCoord += vec3(u_paddingBefore);
#endif
- #if defined(MEGATEXTURE_IS_3D)
- return sampleFrom3DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
+ #if defined(MEGATEXTURE_3D)
+ return getPropertiesFrom3DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
+ #elif defined(MEGATEXTURE_2D)
+ return getPropertiesFrom2DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
+ #endif
+}
+
+vec3 computeAncestorUv(vec3 positionUvLocal, int levelsAbove, ivec4 octreeCoords) {
+ if (levelsAbove > 0) {
+ // In some cases positionUvLocal goes outside the 0 to 1 bounds, such as when sampling neighbor voxels on the edge of a tile.
+ // This needs to be handled carefully, especially for mixed resolution, or else the wrong part of the tile is read.
+ // https://www.wolframalpha.com/input/?i=sign%28x%29+*+max%280%2C+%28abs%28x-0.5%29-0.5%29%29
+ vec3 overflow = sign(positionUvLocal) * max(abs(positionUvLocal - vec3(0.5)) - vec3(0.5), vec3(0.0));
+ positionUvLocal = clamp(positionUvLocal, vec3(0.0), vec3(1.0 - czm_epsilon6)); // epsilon to avoid fract(1) = 0 situation
+
+ // Calcuate a new local uv relative to the ancestor tile.
+ float levelsAboveFactor = 1.0 / pow(2.0, float(levelsAbove));
+ positionUvLocal = fract((vec3(octreeCoords.xyz) + positionUvLocal) * levelsAboveFactor) + overflow * levelsAboveFactor;
+ } else {
+ positionUvLocal = clamp(positionUvLocal, vec3(0.0), vec3(1.0));
+ }
+ return positionUvLocal;
+}
+
+// Convert an array of mixed-resolution sample datas to a final weighted properties.
+Properties getPropertiesFromMegatextureAtLocalPosition(vec3 positionUvLocal, ivec4 octreeCoords, SampleData sampleDatas[SAMPLE_COUNT]) {
+ #if (SAMPLE_COUNT == 1)
+ vec3 actualUv = computeAncestorUv(positionUvLocal, sampleDatas[0].levelsAbove, octreeCoords);
+ return getPropertiesFromMegatextureAtTileUv(actualUv, sampleDatas[0].megatextureIndex);
#else
- return sampleFrom2DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
+ // When more than one sample is taken the accumulator needs to start at 0
+ Properties properties = clearProperties();
+ for (int i = 0; i < SAMPLE_COUNT; i++) {
+ vec3 actualUv = computeAncestorUv(positionUvLocal, sampleDatas[i].levelsAbove, octreeCoords);
+ Properties tempProperties = getPropertiesFromMegatextureAtTileUv(actualUvLocal, sampleDatas[i].megatextureIndex);
+ properties = sumProperties(properties, tempProperties)
+ }
+ return properties;
#endif
}
// --------------------------------------------------------
-// Octree traversal
+// Tree traversal
// --------------------------------------------------------
void getOctreeLeafData(OctreeNodeData data, inout SampleData sampleDatas[SAMPLE_COUNT]) {
@@ -1118,236 +1258,36 @@ void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpac
}
}
-// Convert an array of mixed-resolution sample datas to a final weighted sample.
-Attributes getSamplesAtLocalPosition(in vec3 positionUvLocal, in ivec4 octreeCoords, in SampleData sampleDatas[SAMPLE_COUNT]) {
- // In some cases positionUvLocal goes outside the 0 to 1 bounds, such as when sampling neighbor voxels on the edge of a tile.
- // This needs to be handled carefully, especially for mixed resolution, or else the wrong part of the tile is read.
- // https://www.wolframalpha.com/input/?i=sign%28x%29+*+max%280%2C+%28abs%28x-0.5%29-0.5%29%29
- vec3 overflow = sign(positionUvLocal) * max(abs(positionUvLocal - vec3(0.5)) - vec3(0.5), vec3(0.0));
- positionUvLocal = clamp(positionUvLocal, vec3(0.0), vec3(1.0 - czm_epsilon6)); // epsilon to avoid fract(1) = 0 situation
-
- Attributes attributes;
-
- // When more than one sample is taken the accumulator needs to start at 0
- #if (SAMPLE_COUNT > 1)
- attributes = clearAttributes();
- #endif
-
- for (int i = 0; i < SAMPLE_COUNT; i++) {
- SampleData sampleData = sampleDatas[i];
- vec3 actualUvLocal = positionUvLocal;
- int levelsAbove = sampleData.levelsAbove;
- if (levelsAbove > 0) {
- // Calcuate a new local uv relative to the ancestor tile.
- float levelsAboveFactor = 1.0 / pow(2.0, float(levelsAbove));
- actualUvLocal = fract((vec3(octreeCoords.xyz) + positionUvLocal) * levelsAboveFactor) + overflow * levelsAboveFactor;
- }
-
- Attributes tempAttributes = sampleFromMegatextureAtTileUv(actualUvLocal, sampleData.megatextureIndex);
-
- #if (SAMPLE_COUNT == 1)
- attributes = tempAttributes;
- #else
- attributes = sumAttributes(attributes, tempAttributes)
- #endif
- }
- return attributes;
-}
-
-Attributes getSamplesAtPosition(vec3 positionUv, vec4 outOfBoundsValue) {
- vec3 positionUvShapeSpace;
- vec3 positionUvLocal;
- float levelStepMult;
- ivec4 octreeCoords;
- int parentOctreeIndex;
- SampleData sampleDatas[SAMPLE_COUNT];
- traverseOctree(positionUv, positionUvShapeSpace, positionUvLocal, levelStepMult, octreeCoords, parentOctreeIndex, sampleDatas);
- return getSamplesAtLocalPosition(positionUvLocal, octreeCoords, sampleDatas);
-}
-Attributes getSamplesAtPosition(vec3 positionUv) {
- return getSamplesAtPosition(positionUv, vec4(0.0));
-}
-
-// void getNormalAtPosition(ivec4 octreeCoords, vec3 positionUvShapeSpace, vec3 positionUvLocal, SampleData sampleDatas[SAMPLE_COUNT], out vec3 normalLocalSpace[METADATA_COUNT], out vec3 normalWorldSpace[METADATA_COUNT], out vec3 normalViewSpace[METADATA_COUNT], out bool valid[METADATA_COUNT]) {
-// for (int i = 0; i < METADATA_COUNT; i++){
-// valid[i] = true;
-// }
-
-// Attributes attributes;
-// #define USE_SIMPLE_NORMALS
-// #if defined(NEIGHBORS_INCLUDED_ON_TILE_EDGES) || defined(USE_SIMPLE_NORMALS)
-// // There might be small seam artifacts when the edge count is 1 or less.
-// float sampleL[METADATA_COUNT];
-// float sampleR[METADATA_COUNT];
-// float sampleD[METADATA_COUNT];
-// float sampleU[METADATA_COUNT];
-// float sampleB[METADATA_COUNT];
-// float sampleF[METADATA_COUNT];
-// getSamplesAtLocalPosition(positionUvLocal + vec3(-1.0, 0.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
-// for(int i = 0; i < METADATA_COUNT; i++) {
-// sampleL[i] = attributes[i].a;
-// }
-// getSamplesAtLocalPosition(positionUvLocal + vec3(+1.0, 0.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
-// for(int i = 0; i < METADATA_COUNT; i++) {
-// sampleR[i] = attributes[i].a;
-// }
-// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, -1.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
-// for(int i = 0; i < METADATA_COUNT; i++) {
-// sampleD[i] = attributes[i].a;
-// }
-// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, +1.0, 0.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
-// for(int i = 0; i < METADATA_COUNT; i++) {
-// sampleU[i] = attributes[i].a;
-// }
-// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, 0.0, -1.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
-// for(int i = 0; i < METADATA_COUNT; i++) {
-// sampleB[i] = attributes[i].a;
-// }
-// getSamplesAtLocalPosition(positionUvLocal + vec3(0.0, 0.0, +1.0) / vec3(u_dimensions), octreeCoords, sampleDatas, attributes);
-// for(int i = 0; i < METADATA_COUNT; i++) {
-// sampleF[i] = attributes[i].a;
-// }
-// #else
-// float dimAtLevel = pow(2.0, float(octreeCoords.w));
-// vec3 voxelSizeShapeSpace = 1.0 / (dimAtLevel * vec3(u_dimensions));
-
-// // There might be small seam artifacts when the edge count is 0
-// float sampleL[METADATA_COUNT];
-// float sampleR[METADATA_COUNT];
-// float sampleD[METADATA_COUNT];
-// float sampleU[METADATA_COUNT];
-// float sampleB[METADATA_COUNT];
-// float sampleF[METADATA_COUNT];
-// getSamplesAtPosition(transformFromShapeSpaceToUv(positionUvShapeSpace - vec3(voxelSizeShapeSpace.x, 0.0, 0.0), attributes));
-// for(int i; i= nonZeroMax) {
colorAccum.a = colorAccumTemp.a;
@@ -1429,12 +1366,13 @@ void main()
}
}
#else
- colorAccum += (1.0 - colorAccum.a) * vec4(finalSample.rgb * finalSample.a, finalSample.a);
+ // Pre-multiplied alpha blend
+ colorAccum += (1.0 - colorAccum.a) * vec4(color.rgb * color.a, color.a);
#endif
// Stop traversing if the alpha has been fully saturated
- if (colorAccum.a > alphaAccumMax) {
- colorAccum.a = alphaAccumMax;
+ if (colorAccum.a > ALPHA_ACCUM_MAX) {
+ colorAccum.a = ALPHA_ACCUM_MAX;
break;
}
@@ -1442,38 +1380,41 @@ void main()
currT += stepT;
positionUv += stepT * viewDirUv;
- // Check if the ray is occluded by the depth
+ // Exit early if the ray is occluded by depth texture
#if defined(DEPTH_TEST)
if (currT >= depthT) {
break;
}
#endif
- // Check if the ray has entered empty space. If so, do another intersection test
- // to see if there is more of the shape to intersect. If there isn't, the raymarch is over.
+ // Do another intersection test against the shape if the ray has entered empty space
if (currT > endT) {
- vec2 tEntryExit = intersectShape(positionUv, viewDirUv);
- if (tEntryExit == vec2(NoHit, NoHit)) {
+ vec2 entryExitT = intersectShape(positionUv, viewDirUv);
+
+ // Stop raymarching if it doesn't hit anything
+ if (entryExitT == vec2(NoHit, NoHit)) {
break;
- }
- currT += tEntryExit.x;
- endT += tEntryExit.y;
- positionUv += tEntryExit.x * viewDirUv;
+ }
+
+ currT += entryExitT.x;
+ endT += entryExitT.y;
+ positionUv += entryExitT.x * viewDirUv;
}
- // Traverse the octree from the current ray position.
- // This is an optimized alternative to traverseOctree that expects the
- // ray to stay in the same tile on average. Otherwise it will traverse
- // upwards and back downwards.
+ // Traverse the tree from the current ray position.
+ // This is similar to traverseOctree but is optimized for the common
+ // case where the ray is in the same tile as the previous step.
traverseOctreeFromExisting(positionUv, positionUvShapeSpace, positionUvLocal, levelStepMult, octreeCoords, parentOctreeIndex, sampleDatas);
+
+ // Adjust the step size based on the level in the tree
stepT = u_stepSize * levelStepMult;
}
- // Convert the alpha from [0,alphaAccumMax] to [0,1]
- colorAccum.a /= alphaAccumMax;
+ // Convert the alpha from [0,ALPHA_ACCUM_MAX] to [0,1]
+ colorAccum.a /= ALPHA_ACCUM_MAX;
#if defined(PICKING)
- // If alpha is 0.0, there is nothing to pick
+ // If alpha is 0.0 there is nothing to pick
if (colorAccum.a == 0.0) {
discard;
}
From 47ca8ea7e1230d761f520657ff849d610be6f258 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 1 Apr 2022 09:25:48 -0400
Subject: [PATCH 004/679] pretty printing sample data
---
.../Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf | 89 ++++++++++++++-
.../Voxel/VoxelBox3DTiles/schema.json | 15 ++-
.../Voxel/VoxelBox3DTiles/tileset.json | 72 ++++++++++++-
.../VoxelCylinder3DTiles/0/0/0/0/tile.gltf | 89 ++++++++++++++-
.../Voxel/VoxelCylinder3DTiles/schema.json | 15 ++-
.../Voxel/VoxelCylinder3DTiles/tileset.json | 72 ++++++++++++-
.../VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf | 101 +++++++++++++++++-
.../Voxel/VoxelEllipsoid3DTiles/schema.json | 15 ++-
.../Voxel/VoxelEllipsoid3DTiles/tileset.json | 66 +++++++++++-
.../Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf | 89 ++++++++++++++-
.../Voxel/VoxelBox3DTiles/schema.json | 15 ++-
.../Voxel/VoxelBox3DTiles/tileset.json | 72 ++++++++++++-
.../VoxelCylinder3DTiles/0/0/0/0/tile.gltf | 89 ++++++++++++++-
.../Voxel/VoxelCylinder3DTiles/schema.json | 15 ++-
.../Voxel/VoxelCylinder3DTiles/tileset.json | 72 ++++++++++++-
.../VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf | 101 +++++++++++++++++-
.../Voxel/VoxelEllipsoid3DTiles/schema.json | 15 ++-
.../Voxel/VoxelEllipsoid3DTiles/tileset.json | 66 +++++++++++-
18 files changed, 1050 insertions(+), 18 deletions(-)
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
index 227fd904003..1cb53f70a4d 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
@@ -1 +1,88 @@
-{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483648,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxel":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxel","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxel","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483648,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "../../../../schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
index 74b24b34572..d6b1e9d87f0 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
@@ -1 +1,14 @@
-{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
index 39e46626920..529913b1cb2 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
@@ -1 +1,71 @@
-{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
+{
+ "asset": {
+ "version": "1.1"
+ },
+ "schemaUri": "schema.json",
+ "statistics": {
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "min": 0.0,
+ "max": 1.0
+ }
+ }
+ },
+ "tile": {
+ "count": 1
+ }
+ }
+ },
+ "geometricError": 0.0,
+ "root": {
+ "geometricError": 0.0,
+ "refine": "REPLACE",
+ "boundingVolume": {
+ "box": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ "content": {
+ "uri": "{level}/{x}/{y}/{z}/tile.gltf"
+ },
+ "implicitTiling": {
+ "subdivisionScheme": "OCTREE",
+ "subtreeLevels": 3,
+ "availableLevels": 1,
+ "subtrees": {
+ "uri": "{level}/{x}/{y}/{z}/subtree.bin"
+ }
+ },
+ "transform": [
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
index bf65877f714..d7247ac82e1 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
@@ -1 +1,88 @@
-{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483650,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483650,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "../../../../schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
index 74b24b34572..d6b1e9d87f0 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
@@ -1 +1,14 @@
-{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
index 39e46626920..529913b1cb2 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
@@ -1 +1,71 @@
-{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
+{
+ "asset": {
+ "version": "1.1"
+ },
+ "schemaUri": "schema.json",
+ "statistics": {
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "min": 0.0,
+ "max": 1.0
+ }
+ }
+ },
+ "tile": {
+ "count": 1
+ }
+ }
+ },
+ "geometricError": 0.0,
+ "root": {
+ "geometricError": 0.0,
+ "refine": "REPLACE",
+ "boundingVolume": {
+ "box": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ "content": {
+ "uri": "{level}/{x}/{y}/{z}/tile.gltf"
+ },
+ "implicitTiling": {
+ "subdivisionScheme": "OCTREE",
+ "subtreeLevels": 3,
+ "availableLevels": 1,
+ "subtrees": {
+ "uri": "{level}/{x}/{y}/{z}/subtree.bin"
+ }
+ },
+ "transform": [
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
index 90e35e2f214..5a1815f6d10 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
@@ -1 +1,100 @@
-{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483649,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2],"bounds":{"min":[0.0,0.0,-1.0],"max":[1.0,1.0,0.0]}}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483649,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ],
+ "bounds": {
+ "min": [
+ 0.0,
+ 0.0,
+ -1.0
+ ],
+ "max": [
+ 1.0,
+ 1.0,
+ 0.0
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "../../../../schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
index 74b24b34572..d6b1e9d87f0 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
@@ -1 +1,14 @@
-{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
index f68ae62e2ab..a3e86ab1444 100644
--- a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
@@ -1 +1,65 @@
-{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"region":[0.0,0.0,1.0,1.0,-1.0,0.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[6378137.0,0.0,0.0,0.0,0.0,6378137.0,0.0,0.0,0.0,0.0,6356752.314245179,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
+{
+ "asset": {
+ "version": "1.1"
+ },
+ "schemaUri": "schema.json",
+ "statistics": {
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "min": 0.0,
+ "max": 1.0
+ }
+ }
+ },
+ "tile": {
+ "count": 1
+ }
+ }
+ },
+ "geometricError": 0.0,
+ "root": {
+ "geometricError": 0.0,
+ "refine": "REPLACE",
+ "boundingVolume": {
+ "region": [
+ 0.0,
+ 0.0,
+ 1.0,
+ 1.0,
+ -1.0,
+ 0.0
+ ]
+ },
+ "content": {
+ "uri": "{level}/{x}/{y}/{z}/tile.gltf"
+ },
+ "implicitTiling": {
+ "subdivisionScheme": "OCTREE",
+ "subtreeLevels": 3,
+ "availableLevels": 1,
+ "subtrees": {
+ "uri": "{level}/{x}/{y}/{z}/subtree.bin"
+ }
+ },
+ "transform": [
+ 6378137.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 6378137.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 6356752.314245179,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
index 227fd904003..1cb53f70a4d 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/0/0/0/0/tile.gltf
@@ -1 +1,88 @@
-{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483648,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxel":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxel","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxel","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483648,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "../../../../schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
index 74b24b34572..d6b1e9d87f0 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/schema.json
@@ -1 +1,14 @@
-{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
index 39e46626920..529913b1cb2 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json
@@ -1 +1,71 @@
-{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
+{
+ "asset": {
+ "version": "1.1"
+ },
+ "schemaUri": "schema.json",
+ "statistics": {
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "min": 0.0,
+ "max": 1.0
+ }
+ }
+ },
+ "tile": {
+ "count": 1
+ }
+ }
+ },
+ "geometricError": 0.0,
+ "root": {
+ "geometricError": 0.0,
+ "refine": "REPLACE",
+ "boundingVolume": {
+ "box": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ "content": {
+ "uri": "{level}/{x}/{y}/{z}/tile.gltf"
+ },
+ "implicitTiling": {
+ "subdivisionScheme": "OCTREE",
+ "subtreeLevels": 3,
+ "availableLevels": 1,
+ "subtrees": {
+ "uri": "{level}/{x}/{y}/{z}/subtree.bin"
+ }
+ },
+ "transform": [
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
index bf65877f714..d7247ac82e1 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/0/0/0/0/tile.gltf
@@ -1 +1,88 @@
-{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483650,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2]}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483650,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "../../../../schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
index 74b24b34572..d6b1e9d87f0 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/schema.json
@@ -1 +1,14 @@
-{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
index 39e46626920..529913b1cb2 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json
@@ -1 +1,71 @@
-{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"box":[0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,2.0,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
+{
+ "asset": {
+ "version": "1.1"
+ },
+ "schemaUri": "schema.json",
+ "statistics": {
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "min": 0.0,
+ "max": 1.0
+ }
+ }
+ },
+ "tile": {
+ "count": 1
+ }
+ }
+ },
+ "geometricError": 0.0,
+ "root": {
+ "geometricError": 0.0,
+ "refine": "REPLACE",
+ "boundingVolume": {
+ "box": [
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ },
+ "content": {
+ "uri": "{level}/{x}/{y}/{z}/tile.gltf"
+ },
+ "implicitTiling": {
+ "subdivisionScheme": "OCTREE",
+ "subtreeLevels": 3,
+ "availableLevels": 1,
+ "subtrees": {
+ "uri": "{level}/{x}/{y}/{z}/subtree.bin"
+ }
+ },
+ "transform": [
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 2.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
index 90e35e2f214..5a1815f6d10 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf
@@ -1 +1,100 @@
-{"asset":{"version":"2.0"},"scene":0,"scenes":[{"nodes":[0]}],"nodes":[{"mesh":0}],"meshes":[{"primitives":[{"mode":2147483649,"attributes":{"_a":0},"extensions":{"EXT_primitive_voxels":{"dimensions":[2,2,2],"bounds":{"min":[0.0,0.0,-1.0],"max":[1.0,1.0,0.0]}}}}]}],"extensionsUsed":["EXT_primitive_voxels","EXT_structural_metadata"],"extensionsRequired":["EXT_primitive_voxels","EXT_structural_metadata"],"extensions":{"EXT_structural_metadata":{"schemaUri":"../../../../schema.json","propertyAttributes":[{"class":"voxel","properties":{"a":{"attribute":"_a"}}}]}},"accessors":[{"bufferView":0,"type":"SCALAR","componentType":5126,"min":[0.0],"max":[1.0],"count":8}],"bufferViews":[{"buffer":0,"byteLength":32}],"buffers":[{"uri":"a.bin","byteLength":32}]}
\ No newline at end of file
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483649,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ],
+ "bounds": {
+ "min": [
+ 0.0,
+ 0.0,
+ -1.0
+ ],
+ "max": [
+ 1.0,
+ 1.0,
+ 0.0
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "../../../../schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
index 74b24b34572..d6b1e9d87f0 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/schema.json
@@ -1 +1,14 @@
-{"classes":{"voxel":{"properties":{"a":{"type":"FLOAT32","required":false}}},"tile":{}}}
\ No newline at end of file
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
index f68ae62e2ab..a3e86ab1444 100644
--- a/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
+++ b/Specs/Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json
@@ -1 +1,65 @@
-{"asset":{"version":"1.1"},"schemaUri":"schema.json","statistics":{"classes":{"voxel":{"properties":{"a":{"min":0.0,"max":1.0}}},"tile":{"count":1}}},"geometricError":0.0,"root":{"geometricError":0.0,"refine":"REPLACE","boundingVolume":{"region":[0.0,0.0,1.0,1.0,-1.0,0.0]},"content":{"uri":"{level}/{x}/{y}/{z}/tile.gltf"},"implicitTiling":{"subdivisionScheme":"OCTREE","subtreeLevels":3,"availableLevels":1,"subtrees":{"uri":"{level}/{x}/{y}/{z}/subtree.bin"}},"transform":[6378137.0,0.0,0.0,0.0,0.0,6378137.0,0.0,0.0,0.0,0.0,6356752.314245179,0.0,0.0,0.0,0.0,1.0]}}
\ No newline at end of file
+{
+ "asset": {
+ "version": "1.1"
+ },
+ "schemaUri": "schema.json",
+ "statistics": {
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "min": 0.0,
+ "max": 1.0
+ }
+ }
+ },
+ "tile": {
+ "count": 1
+ }
+ }
+ },
+ "geometricError": 0.0,
+ "root": {
+ "geometricError": 0.0,
+ "refine": "REPLACE",
+ "boundingVolume": {
+ "region": [
+ 0.0,
+ 0.0,
+ 1.0,
+ 1.0,
+ -1.0,
+ 0.0
+ ]
+ },
+ "content": {
+ "uri": "{level}/{x}/{y}/{z}/tile.gltf"
+ },
+ "implicitTiling": {
+ "subdivisionScheme": "OCTREE",
+ "subtreeLevels": 3,
+ "availableLevels": 1,
+ "subtrees": {
+ "uri": "{level}/{x}/{y}/{z}/subtree.bin"
+ }
+ },
+ "transform": [
+ 6378137.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 6378137.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 6356752.314245179,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 1.0
+ ]
+ }
+}
\ No newline at end of file
From 68be4e6ffe15e2c908d545fbf13f485efa5874ee Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 1 Apr 2022 09:50:52 -0400
Subject: [PATCH 005/679] fixed some doc for ext_primitive_voxels
---
Source/Core/PrimitiveType.js | 6 +++---
Source/Scene/ModelComponents.js | 4 ++--
Source/Scene/VoxelPrimitive.js | 4 ----
Source/Scene/VoxelShapeType.js | 6 +++---
4 files changed, 8 insertions(+), 12 deletions(-)
diff --git a/Source/Core/PrimitiveType.js b/Source/Core/PrimitiveType.js
index eb86d821493..2b53747c613 100644
--- a/Source/Core/PrimitiveType.js
+++ b/Source/Core/PrimitiveType.js
@@ -67,7 +67,7 @@ const PrimitiveType = {
TRIANGLE_FAN: WebGLConstants.TRIANGLE_FAN,
/**
- * Box voxel primitive from EXT_primitive_voxels.
+ * Box voxel primitive from EXT_primitive_voxels
.
*
* @type {Number}
* @constant
@@ -75,7 +75,7 @@ const PrimitiveType = {
VOXEL_BOX: 0x80000000,
/**
- * Ellipsoid voxel primitive from EXT_primitive_voxels.
+ * Ellipsoid voxel primitive from EXT_primitive_voxels
.
*
* @type {Number}
* @constant
@@ -83,7 +83,7 @@ const PrimitiveType = {
VOXEL_ELLIPSOID: 0x80000001,
/**
- * Cylinder voxel primitive from EXT_primitive_voxels.
+ * Cylinder voxel primitive from EXT_primitive_voxels
.
*
* @type {Number}
* @constant
diff --git a/Source/Scene/ModelComponents.js b/Source/Scene/ModelComponents.js
index 2ce5fb6eba7..31e955441a9 100644
--- a/Source/Scene/ModelComponents.js
+++ b/Source/Scene/ModelComponents.js
@@ -543,7 +543,7 @@ function MorphTarget() {
}
/**
- * Properties from EXT_primitive_voxels
+ * Properties from EXT_primitive_voxels
*
* @alias ModelComponents.Voxel
* @constructor
@@ -667,7 +667,7 @@ function Primitive() {
this.propertyAttributeIds = [];
/**
- * Properties from EXT_primitive_voxels.
+ * Properties from EXT_primitive_voxels
.
* @type {ModelComponents.Voxel}
* @private
*/
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 034777b6919..24b5bf805bf 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -2353,8 +2353,6 @@ VoxelPrimitive.prototype.destroy = function () {
this._traversal = undefined;
}
- // TODO: I assume the custom shader does not have to be destroyed
-
return destroyObject(this);
};
@@ -2607,8 +2605,6 @@ function debugDraw(that, frameState) {
* @type {CustomShader}
*/
VoxelPrimitive.DefaultCustomShader = new CustomShader({
- // TODO what should happen when lightingModel undefined?
- // lightingModel: Cesium.LightingModel.UNLIT,
fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
{
material.diffuse = vec3(1.0);
diff --git a/Source/Scene/VoxelShapeType.js b/Source/Scene/VoxelShapeType.js
index 8b0c4f6a515..15742bfca39 100644
--- a/Source/Scene/VoxelShapeType.js
+++ b/Source/Scene/VoxelShapeType.js
@@ -5,8 +5,8 @@ import VoxelCylinderShape from "./VoxelCylinderShape.js";
import VoxelEllipsoidShape from "./VoxelEllipsoidShape.js";
/**
- * An enum of voxel shapes. The shape controls how the voxel grid is mapped to
- * 3D space.
+ * An enum of voxel shapes supported by EXT_primitive_voxels
. The shape controls
+ * how the voxel grid is mapped to 3D space.
*
* @enum VoxelShapeType
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
@@ -44,7 +44,7 @@ const VoxelShapeType = {
/**
* Converts a primitive type to a voxel shape. glTF voxel primitive types are
- * defined in EXT_primitive_voxels.
+ * defined in EXT_primitive_voxels.
* @param {PrimitiveType} primitiveType The primitive type.
* @returns {VoxelShapeType} The shape type.
* @private
From 9e39196c54248d3b00bb5859f15fe497b685459b Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 1 Apr 2022 11:16:06 -0400
Subject: [PATCH 006/679] made shape bounds retrievable from VoxelShapeType
---
Source/Scene/VoxelPrimitive.js | 33 +++++------
Source/Scene/VoxelShapeType.js | 57 +++++++++++++++----
.../Widgets/VoxelInspector/VoxelInspector.js | 28 +++++----
Specs/Scene/VoxelShapeTypeSpec.js | 50 ++++++++++++++--
4 files changed, 117 insertions(+), 51 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 24b5bf805bf..82e1f5c6118 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1005,27 +1005,23 @@ VoxelPrimitive.prototype.update = function (frameState) {
const dimensions = provider.dimensions;
const shapeType = provider.shape;
- const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
- const minBounds = defaultValue(
- provider.minBounds,
- ShapeConstructor.DefaultMinBounds
- );
- const maxBounds = defaultValue(
- provider.maxBounds,
- ShapeConstructor.DefaultMaxBounds
- );
+ const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
+ const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
+ const minBounds = defaultValue(provider.minBounds, defaultMinBounds);
+ const maxBounds = defaultValue(provider.maxBounds, defaultMaxBounds);
const minimumValues = provider.minimumValues;
const maximumValues = provider.maximumValues;
+ const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
this._shape = new ShapeConstructor();
this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
this._minClippingBounds = Cartesian3.clone(
- ShapeConstructor.DefaultMinBounds,
+ defaultMinBounds,
this._minClippingBounds
);
this._maxClippingBounds = Cartesian3.clone(
- ShapeConstructor.DefaultMaxBounds,
+ defaultMaxBounds,
this._maxClippingBounds
);
this._paddingBefore = Cartesian3.clone(
@@ -1096,9 +1092,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
if (minBoundsDirty || maxBoundsDirty) {
- const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
- const defaultMinBounds = ShapeConstructor.DefaultMinBounds;
- const defaultMaxBounds = ShapeConstructor.DefaultMaxBounds;
+ const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
+ const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
const isDefaultBoundsMinX = minBounds.x === defaultMinBounds.x;
const isDefaultBoundsMinY = minBounds.y === defaultMinBounds.y;
const isDefaultBoundsMinZ = minBounds.z === defaultMinBounds.z;
@@ -1435,9 +1430,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
const maxClipDirty = !Cartesian3.equals(maxClip, maxClipOld);
const clippingBoundsDirty = minClipDirty || maxClipDirty;
if (clippingBoundsDirty) {
- const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
- const defaultMinBounds = ShapeConstructor.DefaultMinBounds;
- const defaultMaxBounds = ShapeConstructor.DefaultMaxBounds;
+ const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
+ const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
const isDefaultClippingBoundsMinX = minClip.x === defaultMinBounds.x;
const isDefaultClippingBoundsMinY = minClip.y === defaultMinBounds.y;
const isDefaultClippingBoundsMinZ = minClip.z === defaultMinBounds.z;
@@ -1780,9 +1774,8 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
- const ShapeConstructor = VoxelShapeType.toShapeConstructor(shapeType);
- const defaultMinBounds = ShapeConstructor.DefaultMinBounds;
- const defaultMaxBounds = ShapeConstructor.DefaultMaxBounds;
+ const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
+ const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
const isDefaultMinX = minBounds.x === defaultMinBounds.x;
const isDefaultMinY = minBounds.y === defaultMinBounds.y;
const isDefaultMinZ = minBounds.z === defaultMinBounds.z;
diff --git a/Source/Scene/VoxelShapeType.js b/Source/Scene/VoxelShapeType.js
index 15742bfca39..fa256157039 100644
--- a/Source/Scene/VoxelShapeType.js
+++ b/Source/Scene/VoxelShapeType.js
@@ -8,12 +8,9 @@ import VoxelEllipsoidShape from "./VoxelEllipsoidShape.js";
* An enum of voxel shapes supported by EXT_primitive_voxels
. The shape controls
* how the voxel grid is mapped to 3D space.
*
+ * @namespace
* @enum VoxelShapeType
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- * @see VoxelShape
- * @see VoxelBoxShape
- * @see VoxelEllipsoidShape
- * @see VoxelCylinderShape
*/
const VoxelShapeType = {
/**
@@ -42,9 +39,49 @@ const VoxelShapeType = {
CYLINDER: "CYLINDER",
};
+/**
+ * Gets the minimum bounds as defined by EXT_primitive_voxels
.
+ * @param {VoxelShapeType} shapeType The voxel shape type.
+ * @returns {Cartesian3} The minimum bounds.
+ */
+VoxelShapeType.getMinBounds = function (shapeType) {
+ switch (shapeType) {
+ case VoxelShapeType.BOX:
+ return VoxelBoxShape.DefaultMinBounds;
+ case VoxelShapeType.ELLIPSOID:
+ return VoxelEllipsoidShape.DefaultMinBounds;
+ case VoxelShapeType.CYLINDER:
+ return VoxelCylinderShape.DefaultMinBounds;
+ //>>includeStart('debug', pragmas.debug);
+ default:
+ throw new DeveloperError(`Invalid shape type ${shapeType}`);
+ //>>includeEnd('debug');
+ }
+};
+
+/**
+ * Gets the maximum bounds as defined by EXT_primitive_voxels
.
+ * @param {VoxelShapeType} shapeType The voxel shape type.
+ * @returns {Cartesian3} The maximum bounds.
+ */
+VoxelShapeType.getMaxBounds = function (shapeType) {
+ switch (shapeType) {
+ case VoxelShapeType.BOX:
+ return VoxelBoxShape.DefaultMaxBounds;
+ case VoxelShapeType.ELLIPSOID:
+ return VoxelEllipsoidShape.DefaultMaxBounds;
+ case VoxelShapeType.CYLINDER:
+ return VoxelCylinderShape.DefaultMaxBounds;
+ //>>includeStart('debug', pragmas.debug);
+ default:
+ throw new DeveloperError(`Invalid shape type ${shapeType}`);
+ //>>includeEnd('debug');
+ }
+};
+
/**
* Converts a primitive type to a voxel shape. glTF voxel primitive types are
- * defined in EXT_primitive_voxels.
+ * defined by EXT_primitive_voxels.
* @param {PrimitiveType} primitiveType The primitive type.
* @returns {VoxelShapeType} The shape type.
* @private
@@ -53,10 +90,10 @@ VoxelShapeType.fromPrimitiveType = function (primitiveType) {
switch (primitiveType) {
case PrimitiveType.VOXEL_BOX:
return VoxelShapeType.BOX;
- case PrimitiveType.VOXEL_CYLINDER:
- return VoxelShapeType.CYLINDER;
case PrimitiveType.VOXEL_ELLIPSOID:
return VoxelShapeType.ELLIPSOID;
+ case PrimitiveType.VOXEL_CYLINDER:
+ return VoxelShapeType.CYLINDER;
//>>includeStart('debug', pragmas.debug);
default:
throw new DeveloperError(`Invalid primitive type ${primitiveType}`);
@@ -72,14 +109,14 @@ VoxelShapeType.fromPrimitiveType = function (primitiveType) {
* @returns {Function} The shape's constructor.
* @private
*/
-VoxelShapeType.toShapeConstructor = function (shapeType) {
+VoxelShapeType.getShapeConstructor = function (shapeType) {
switch (shapeType) {
case VoxelShapeType.BOX:
return VoxelBoxShape;
- case VoxelShapeType.CYLINDER:
- return VoxelCylinderShape;
case VoxelShapeType.ELLIPSOID:
return VoxelEllipsoidShape;
+ case VoxelShapeType.CYLINDER:
+ return VoxelCylinderShape;
//>>includeStart('debug', pragmas.debug);
default:
throw new DeveloperError(`Invalid shape type ${shapeType}`);
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
index 1f360e67ef2..c1eed5c1631 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspector.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -7,9 +7,7 @@ import knockout from "../../ThirdParty/knockout.js";
import getElement from "../getElement.js";
import InspectorShared from "../InspectorShared.js";
import VoxelInspectorViewModel from "./VoxelInspectorViewModel.js";
-import VoxelBoxShape from "../../Scene/VoxelBoxShape.js";
-import VoxelCylinderShape from "../../Scene/VoxelCylinderShape.js";
-import VoxelEllipsoidShape from "../../Scene/VoxelEllipsoidShape.js";
+import VoxelShapeType from "../../Scene/VoxelShapeType.js";
/**
* Inspector widget to aid in debugging voxels
@@ -142,8 +140,8 @@ function VoxelInspector(container, scene) {
"boundsBoxMinY",
"boundsBoxMaxZ",
"boundsBoxMinZ",
- VoxelBoxShape.DefaultMinBounds,
- VoxelBoxShape.DefaultMaxBounds,
+ VoxelShapeType.getMinBounds(VoxelShapeType.BOX),
+ VoxelShapeType.getMaxBounds(VoxelShapeType.BOX),
"shapeIsBox",
boundsPanelContents
);
@@ -161,8 +159,8 @@ function VoxelInspector(container, scene) {
"boundsEllipsoidMinLatitude",
"boundsEllipsoidMaxHeight",
"boundsEllipsoidMinHeight",
- VoxelEllipsoidShape.DefaultMinBounds,
- VoxelEllipsoidShape.DefaultMaxBounds,
+ VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID),
+ VoxelShapeType.getMaxBounds(VoxelShapeType.ELLIPSOID),
"shapeIsEllipsoid",
boundsPanelContents
);
@@ -180,8 +178,8 @@ function VoxelInspector(container, scene) {
"boundsCylinderMinHeight",
"boundsCylinderMaxAngle",
"boundsCylinderMinAngle",
- VoxelCylinderShape.DefaultMinBounds,
- VoxelCylinderShape.DefaultMaxBounds,
+ VoxelShapeType.getMinBounds(VoxelShapeType.CYLINDER),
+ VoxelShapeType.getMaxBounds(VoxelShapeType.CYLINDER),
"shapeIsCylinder",
boundsPanelContents
);
@@ -207,8 +205,8 @@ function VoxelInspector(container, scene) {
"clippingBoxMinY",
"clippingBoxMaxZ",
"clippingBoxMinZ",
- VoxelBoxShape.DefaultMinBounds,
- VoxelBoxShape.DefaultMaxBounds,
+ VoxelShapeType.getMinBounds(VoxelShapeType.BOX),
+ VoxelShapeType.getMaxBounds(VoxelShapeType.BOX),
"shapeIsBox",
clippingPanelContents
);
@@ -226,8 +224,8 @@ function VoxelInspector(container, scene) {
"clippingEllipsoidMinLatitude",
"clippingEllipsoidMaxHeight",
"clippingEllipsoidMinHeight",
- VoxelEllipsoidShape.DefaultMinBounds,
- VoxelEllipsoidShape.DefaultMaxBounds,
+ VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID),
+ VoxelShapeType.getMaxBounds(VoxelShapeType.ELLIPSOID),
"shapeIsEllipsoid",
clippingPanelContents
);
@@ -245,8 +243,8 @@ function VoxelInspector(container, scene) {
"clippingCylinderMinHeight",
"clippingCylinderMaxAngle",
"clippingCylinderMinAngle",
- VoxelCylinderShape.DefaultMinBounds,
- VoxelCylinderShape.DefaultMaxBounds,
+ VoxelShapeType.getMinBounds(VoxelShapeType.CYLINDER),
+ VoxelShapeType.getMaxBounds(VoxelShapeType.CYLINDER),
"shapeIsCylinder",
clippingPanelContents
);
diff --git a/Specs/Scene/VoxelShapeTypeSpec.js b/Specs/Scene/VoxelShapeTypeSpec.js
index 240f2815997..242d3caabd7 100644
--- a/Specs/Scene/VoxelShapeTypeSpec.js
+++ b/Specs/Scene/VoxelShapeTypeSpec.js
@@ -7,6 +7,42 @@ import {
} from "../../Source/Cesium.js";
describe("Scene/VoxelShapeType", function () {
+ it("getMinBounds works", function () {
+ expect(VoxelShapeType.getMinBounds(VoxelShapeType.BOX)).toEqual(
+ VoxelBoxShape.DefaultMinBounds
+ );
+ expect(VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID)).toEqual(
+ VoxelEllipsoidShape.DefaultMinBounds
+ );
+ expect(VoxelShapeType.getMinBounds(VoxelShapeType.CYLINDER)).toEqual(
+ VoxelCylinderShape.DefaultMinBounds
+ );
+ });
+
+ it("getMinBounds throws for invalid type", function () {
+ expect(function () {
+ return VoxelShapeType.getMinBounds("NOT_A_SHAPE_TYPE");
+ }).toThrowDeveloperError();
+ });
+
+ it("getMaxBounds works", function () {
+ expect(VoxelShapeType.getMaxBounds(VoxelShapeType.BOX)).toEqual(
+ VoxelBoxShape.DefaultMaxBounds
+ );
+ expect(VoxelShapeType.getMaxBounds(VoxelShapeType.ELLIPSOID)).toEqual(
+ VoxelEllipsoidShape.DefaultMaxBounds
+ );
+ expect(VoxelShapeType.getMaxBounds(VoxelShapeType.CYLINDER)).toEqual(
+ VoxelCylinderShape.DefaultMaxBounds
+ );
+ });
+
+ it("getMaxBounds throws for invalid type", function () {
+ expect(function () {
+ return VoxelShapeType.getMaxBounds("NOT_A_SHAPE_TYPE");
+ }).toThrowDeveloperError();
+ });
+
it("fromPrimitiveType works", function () {
expect(VoxelShapeType.fromPrimitiveType(PrimitiveType.VOXEL_BOX)).toBe(
VoxelShapeType.BOX
@@ -18,26 +54,28 @@ describe("Scene/VoxelShapeType", function () {
VoxelShapeType.CYLINDER
);
});
+
it("fromPrimitiveType throws for invalid type", function () {
expect(function () {
return VoxelShapeType.fromPrimitiveType("NOT_A_PRIMITIVE_TYPE");
}).toThrowDeveloperError();
});
- it("toShapeConstructor works", function () {
- expect(VoxelShapeType.toShapeConstructor(VoxelShapeType.BOX)).toBe(
+ it("getShapeConstructor works", function () {
+ expect(VoxelShapeType.getShapeConstructor(VoxelShapeType.BOX)).toBe(
VoxelBoxShape
);
- expect(VoxelShapeType.toShapeConstructor(VoxelShapeType.ELLIPSOID)).toBe(
+ expect(VoxelShapeType.getShapeConstructor(VoxelShapeType.ELLIPSOID)).toBe(
VoxelEllipsoidShape
);
- expect(VoxelShapeType.toShapeConstructor(VoxelShapeType.CYLINDER)).toBe(
+ expect(VoxelShapeType.getShapeConstructor(VoxelShapeType.CYLINDER)).toBe(
VoxelCylinderShape
);
});
- it("toShapeConstructor throws for invalid type", function () {
+
+ it("getShapeConstructor throws for invalid type", function () {
expect(function () {
- return VoxelShapeType.toShapeConstructor("NOT_A_SHAPE_TYPE");
+ return VoxelShapeType.getShapeConstructor("NOT_A_SHAPE_TYPE");
}).toThrowDeveloperError();
});
});
From 1a90c229091d14329fe092203c425acfd303be1b Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 1 Apr 2022 16:59:05 -0400
Subject: [PATCH 007/679] documentation passthrough
---
CHANGES.md | 1 +
Source/Scene/Cesium3DTilesVoxelProvider.js | 80 ++-
Source/Scene/GltfVoxelProvider.js | 160 +++---
Source/Scene/VoxelBoxShape.js | 174 +++----
Source/Scene/VoxelCylinderShape.js | 312 +++++++-----
Source/Scene/VoxelEllipsoidShape.js | 13 +-
Source/Scene/VoxelPrimitive.js | 373 ++++++++++----
Source/Scene/VoxelProvider.js | 80 ++-
Source/Scene/VoxelShape.js | 47 +-
Source/Scene/VoxelShapeType.js | 6 +-
Source/Scene/VoxelTraversal.js | 479 +++++++++++-------
Specs/Scene/Cesium3DTilesVoxelProviderSpec.js | 2 +-
Specs/Scene/GltfVoxelProviderSpec.js | 2 +-
13 files changed, 1047 insertions(+), 682 deletions(-)
diff --git a/CHANGES.md b/CHANGES.md
index bd2e064f38e..98777b9bb17 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -3,6 +3,7 @@
### 1.93 - 2022-05-02
##### Additions :tada:
+
- Added experimental voxel rendering that supports glTF with EXT_primitive_voxels extension, 3D Tiles, and procedural data. [#10253](https://github.com/CesiumGS/cesium/pull/10253).
### 1.92 - 2022-04-01
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
index 7b5037b3167..4611faf0bd0 100644
--- a/Source/Scene/Cesium3DTilesVoxelProvider.js
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -28,7 +28,12 @@ import VoxelShapeType from "./VoxelShapeType.js";
* @param {Object} options Object with the following properties:
* @param {String|Resource|Uint8Array} options.url The URL to the tileset directory
*
+ * @see GltfVoxelProvider
* @see VoxelProvider
+ * @see VoxelPrimitive
+ * @see VoxelShapeType
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
function Cesium3DTilesVoxelProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
@@ -38,15 +43,21 @@ function Cesium3DTilesVoxelProvider(options) {
/**
* Gets a value indicating whether or not the provider is ready for use.
+ *
* @type {Boolean}
* @readonly
*/
this.ready = false;
+ /**
+ * @type {Promise.}
+ * @private
+ */
this._readyPromise = defer();
/**
* Gets the promise that will be resolved when the provider is ready for use.
+ *
* @type {Promise.}
* @readonly
*/
@@ -54,13 +65,15 @@ function Cesium3DTilesVoxelProvider(options) {
/**
* An optional model matrix that is applied to all tiles
- * @type {Matrix4}
+ *
+ * @type {Matrix4|undefined}
* @readonly
*/
this.modelMatrix = undefined;
/**
* Gets the {@link VoxelShapeType}
+ *
* @type {VoxelShapeType}
* @readonly
*/
@@ -70,7 +83,8 @@ function Cesium3DTilesVoxelProvider(options) {
* Gets the minimum bounds.
* If undefined, the shape's default minimum bounds will be used instead.
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @type {Cartesian3|undefined}
* @readonly
*/
this.minBounds = undefined;
@@ -79,7 +93,8 @@ function Cesium3DTilesVoxelProvider(options) {
* Gets the maximum bounds.
* If undefined, the shape's default maximum bounds will be used instead.
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @type {Cartesian3|undefined}
* @readonly
*/
this.maxBounds = undefined;
@@ -87,6 +102,7 @@ function Cesium3DTilesVoxelProvider(options) {
/**
* Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
* This should not be called before {@link VoxelProvider#ready} returns true.
+ *
* @type {Cartesian3}
* @readonly
*/
@@ -96,7 +112,8 @@ function Cesium3DTilesVoxelProvider(options) {
* Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
* TODO: mark this optional
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Number}
+ *
+ * @type {Number|undefined}
* @readonly
*/
this.paddingBefore = undefined;
@@ -105,7 +122,8 @@ function Cesium3DTilesVoxelProvider(options) {
* Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
* This should not be called before {@link VoxelProvider#ready} returns true.
* TODO: mark this optional
- * @type {Number}
+ *
+ * @type {Number|undefined}
* @readonly
*/
this.paddingAfter = undefined;
@@ -114,69 +132,89 @@ function Cesium3DTilesVoxelProvider(options) {
/**
* Gets stuff
+ *
* @type {String[]}
+ * @readonly
*/
this.names = new Array();
/**
* Gets stuff
+ *
* @type {MetadataType[]}
+ * @readonly
*/
this.types = new Array();
/**
* Gets stuff
+ *
* @type {MetadataComponentType[]}
+ * @readonly
*/
this.componentTypes = new Array();
/**
* TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
* Gets the minimum value
- * @type {Number[]}
+ *
+ * @type {Number[][]|undefined}
+ * @readonly
*/
this.minimumValues = undefined;
/**
* TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
* Gets the maximum value
- * @type {Number[][]}
+ *
+ * @type {Number[][]|undefined}
+ * @readonly
*/
this.maximumValues = undefined;
/**
* The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be set to 0.
- * @type {Number}
+ *
+ * @type {Number|undefined}
+ * @readonly
*/
this.maximumTileCount = undefined;
/**
* @type {ImplicitTileset}
+ * @private
*/
this._implicitTileset = undefined;
/**
* @type {ImplicitSubtreeCache}
+ * @private
*/
this._subtreeCache = new ImplicitSubtreeCache();
/**
* glTFs that are in the process of being loaded.
+ *
* @type {GltfLoader[]}
+ * @private
*/
this._gltfLoaders = new Array();
/**
* Subtrees that are in the process of being loaded.
- * This member exists for unit test purposes only. See _doneLoading.
+ * This member exists for unit test purposes only. See doneLoading.
+ *
* @type {Subtree[]}
+ * @private
*/
this._subtreeLoaders = new Array();
/**
* Subtree resources that are in the process of being loaded.
- * This member exists for unit test purposes only. See _doneLoading.
+ * This member exists for unit test purposes only. See doneLoading.
+ *
* @type {Resource[]}
+ * @private
*/
this._subtreeResourceLoaders = new Array();
@@ -322,6 +360,7 @@ const scratchImplicitTileCoordinates = new ImplicitTileCoordinates({
/**
* Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z.
* This function should not be called before {@link VoxelProvider#ready} returns true.
+ *
* @param {Object} [options] Object with the following properties:
* @param {Number} [options.tileLevel=0] The tile's level.
* @param {Number} [options.tileX=0] The tile's X coordinate.
@@ -329,8 +368,7 @@ const scratchImplicitTileCoordinates = new ImplicitTileCoordinates({
* @param {Number} [options.tileZ=0] The tile's Z coordinate.
* @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
*
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ * @exception {DeveloperError} The provider must be ready.
*/
Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
@@ -458,7 +496,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
};
/**
- * Optional per-frame processing. Not all {@link VoxelProvder} need to do this.
+ * A hook to update the provider every frame, called from {@link VoxelPrimitive.update}.
*
* @param {FrameState} frameState
*/
@@ -472,11 +510,13 @@ Cesium3DTilesVoxelProvider.prototype.update = function (frameState) {
/**
* Check if anything is still being loaded.
- * This is intended to be used for unit tests only.
+ * This is intended for unit test purposes only.
+ *
* @returns {Boolean}
+ *
* @private
*/
-Cesium3DTilesVoxelProvider.prototype._doneLoading = function () {
+Cesium3DTilesVoxelProvider.prototype.doneLoading = function () {
return (
this._gltfLoaders.length === 0 &&
this._subtreeLoaders.length === 0 &&
@@ -485,9 +525,13 @@ Cesium3DTilesVoxelProvider.prototype._doneLoading = function () {
};
/**
+ * @function
+ *
* @param {ArrayBuffer} gltfBuffer The buffer that comes when the promise from gltfResource.fetchArrayBuffer() resolves.
* @param {Resource} gltfResource Resource derived from base that points to gltf.
* @returns {GltfLoader}
+ *
+ * @private
*/
function getGltfLoader(implicitTileset, tileCoord) {
const gltfRelative = implicitTileset.contentUriTemplates[0].getDerivedResource(
@@ -510,7 +554,9 @@ function getGltfLoader(implicitTileset, tileCoord) {
}
/**
+ * @alias ImplicitSubtreeCacheNode
* @constructor
+ *
* @param {ImplicitSubtree} subtree
* @param {Number} stamp
*
@@ -522,7 +568,9 @@ function ImplicitSubtreeCacheNode(subtree, stamp) {
}
/**
+ * @alias ImplicitSubtreeCache
* @constructor
+ *
* @param {Object} [options] Object with the following properties
* @param {Number} [options.maximumSubtreeCount=0] The total number of subtrees this cache can store. If adding a new subtree would exceed this limit, the lowest priority subtrees will be removed until there is room, unless the subtree that is going to be removed is the parent of the new subtree, in which case it will not be removed and the new subtree will still be added, exceeding the memory limit.
*
@@ -614,8 +662,6 @@ ImplicitSubtreeCache.prototype.find = function (subtreeCoord) {
* @param {ImplicitSubtreeCacheNode} a
* @param {ImplicitSubtreeCacheNode} b
* @returns {Number}
- *
- * @private
*/
ImplicitSubtreeCache.comparator = function (a, b) {
const aCoord = a.subtree.implicitCoordinates;
diff --git a/Source/Scene/GltfVoxelProvider.js b/Source/Scene/GltfVoxelProvider.js
index a795aae74c4..60536d7dc03 100644
--- a/Source/Scene/GltfVoxelProvider.js
+++ b/Source/Scene/GltfVoxelProvider.js
@@ -21,9 +21,12 @@ import VoxelShapeType from "./VoxelShapeType.js";
* @param {Object} options Object with the following properties:
* @param {String|Resource|Uint8Array|Object|GltfLoader} options.gltf A Resource/URL to a glTF/glb file, a binary glTF buffer, or a JSON object containing the glTF contents
*
- * @see VoxelProvider
* @see Cesium3DTilesVoxelProvider
+ * @see VoxelProvider
* @see VoxelPrimitive
+ * @see VoxelShapeType
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
function GltfVoxelProvider(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
@@ -34,15 +37,21 @@ function GltfVoxelProvider(options) {
/**
* Gets a value indicating whether or not the provider is ready for use.
+ *
* @type {Boolean}
* @readonly
*/
this.ready = false;
+ /**
+ * @type {Promise.}
+ * @private
+ */
this._readyPromise = defer();
/**
* Gets the promise that will be resolved when the provider is ready for use.
+ *
* @type {Promise.}
* @readonly
*/
@@ -50,13 +59,15 @@ function GltfVoxelProvider(options) {
/**
* An optional model matrix that is applied to all tiles
- * @type {Matrix4}
+ *
+ * @type {Matrix4|undefined}
* @readonly
*/
this.modelMatrix = undefined;
/**
* Gets the {@link VoxelShapeType}
+ *
* @type {VoxelShapeType}
* @readonly
*/
@@ -66,7 +77,8 @@ function GltfVoxelProvider(options) {
* Gets the minimum bounds.
* If undefined, the shape's default minimum bounds will be used instead.
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @type {Cartesian3|undefined}
* @readonly
*/
this.minBounds = undefined;
@@ -75,7 +87,8 @@ function GltfVoxelProvider(options) {
* Gets the maximum bounds.
* If undefined, the shape's default maximum bounds will be used instead.
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @type {Cartesian3|undefined}
* @readonly
*/
this.maxBounds = undefined;
@@ -83,6 +96,7 @@ function GltfVoxelProvider(options) {
/**
* Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
* This should not be called before {@link VoxelProvider#ready} returns true.
+ *
* @type {Cartesian3}
* @readonly
*/
@@ -92,7 +106,8 @@ function GltfVoxelProvider(options) {
* Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
* TODO: mark this optional
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Number}
+ *
+ * @type {Number|undefined}
* @readonly
*/
this.paddingBefore = undefined;
@@ -101,7 +116,8 @@ function GltfVoxelProvider(options) {
* Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
* This should not be called before {@link VoxelProvider#ready} returns true.
* TODO: mark this optional
- * @type {Number}
+ *
+ * @type {Number|undefined}
* @readonly
*/
this.paddingAfter = undefined;
@@ -110,49 +126,70 @@ function GltfVoxelProvider(options) {
/**
* Gets stuff
+ *
* @type {String[]}
+ * @readonly
*/
this.names = new Array();
/**
* Gets stuff
+ *
* @type {MetadataType[]}
+ * @readonly
*/
this.types = new Array();
/**
* Gets stuff
+ *
* @type {MetadataComponentType[]}
+ * @readonly
*/
this.componentTypes = new Array();
/**
* TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
* Gets the minimum value
- * @type {Number[]}
+ *
+ * @type {Number[][]|undefined}
+ * @readonly
*/
this.minimumValues = undefined;
/**
* TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
* Gets the maximum value
- * @type {Number[][]}
+ *
+ * @type {Number[][]|undefined}
+ * @readonly
*/
this.maximumValues = undefined;
/**
* The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be set to 0.
- * @type {Number}
+ *
+ * @type {Number|undefined}
+ * @readonly
*/
- this.maximumTileCount = undefined;
+ this.maximumTileCount = 1;
/**
* A {@link GltfLoader} that is processed each frame until ready.
+ *
* @type {GltfLoader}
* @private
*/
this._loader = undefined;
+ /**
+ * The voxel data.
+ *
+ * @type {Array[]}
+ * @private
+ */
+ this._data = undefined;
+
const gltf = options.gltf;
let promise;
if (defined(gltf.components) && defined(gltf.components.asset)) {
@@ -207,114 +244,30 @@ function GltfVoxelProvider(options) {
that.ready = true;
that._readyPromise = Promise.resolve(that);
- /**
- * An optional model matrix that is applied to all tiles
- * @type {Matrix4}
- * @readonly
- */
that.modelMatrix = modelMatrix;
-
- /**
- * Gets the {@link VoxelShapeType}
- * @type {VoxelShapeType}
- * @readonly
- */
that.shape = shape;
-
- /**
- * Gets the minimum bounds.
- * This should not be called before {@link GltfVoxelProvider#ready} returns true.
- * @type {Cartesian3}
- * @readonly
- */
that.minBounds = defined(voxel.minBounds)
? Cartesian3.clone(voxel.minBounds, new Cartesian3())
: undefined;
-
- /**
- * Gets the maximum bounds.
- * This should not be called before {@link GltfVoxelProvider#ready} returns true.
- * @type {Cartesian3}
- * @readonly
- */
that.maxBounds = defined(voxel.maxBounds)
? Cartesian3.clone(voxel.maxBounds, new Cartesian3())
: undefined;
-
- /**
- * Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
- * This should not be called before {@link GltfVoxelProvider#ready} returns true.
- * @type {Cartesian3}
- * @readonly
- */
that.dimensions = Cartesian3.clone(voxel.dimensions, new Cartesian3());
-
- /**
- * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
- * TODO: mark this optional
- * This should not be called before {@link GltfVoxelProvider#ready} returns true.
- * @type {Number}
- * @readonly
- */
that.paddingBefore = defined(voxel.paddingBefore)
? Cartesian3.clone(voxel.paddingBefore, new Cartesian3())
: undefined;
-
- /**
- * Gets the number of padding voxels on the edge of a tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage.
- * This should not be called before {@link GltfVoxelProvider#ready} returns true.
- * TODO: mark this optional
- * @type {Number}
- * @readonly
- */
that.paddingAfter = defined(voxel.paddingAfter)
? Cartesian3.clone(voxel.paddingAfter, new Cartesian3())
: undefined;
// TODO is there a good user-facing way to set names, types, componentTypes, min, max, etc? MetadataComponents.Primitive is close, but private and has some fields that voxels don't use
- /**
- * Gets stuff
- * @type {String[]}
- */
that.names = names;
-
- /**
- * Gets stuff
- * @type {MetadataType[]}
- */
that.types = types;
-
- /**
- * Gets stuff
- * @type {MetadataComponentType[]}
- */
that.componentTypes = componentTypes;
-
- /**
- * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
- * Gets the minimum value
- * @type {Number[]}
- */
that.minimumValues = minimumValues;
-
- /**
- * TODO is [][] valid JSDOC? https://stackoverflow.com/questions/25602978/jsdoc-two-dimensional-array
- * Gets the maximum value
- * @type {Number[][]}
- */
that.maximumValues = maximumValues;
- /**
- * The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be set to 0.
- * @type {Number}
- */
- that.maximumTileCount = 1;
-
- /**
- * @private
- * @type {Float32Array|Uint16Array|Uint8Array}
- */
that._data = new Array(attributesLength);
for (let i = 0; i < attributesLength; i++) {
const attribute = attributes[i];
@@ -332,7 +285,8 @@ function GltfVoxelProvider(options) {
);
}
- // Now that the data is loaded there's no need to keep around the loader.
+ // Now that the data is loaded the loader can be unloaded.
+ that._loader.unload();
that._loader = undefined;
})
.catch(function (error) {
@@ -343,11 +297,8 @@ function GltfVoxelProvider(options) {
/**
* A hook to update the provider every frame, called from {@link VoxelPrimitive.update}.
* If the provider doesn't need this functionality it should leave this function undefined.
- * @function
- * @param {FrameState} frameState
*
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ * @param {FrameState} frameState
*/
GltfVoxelProvider.prototype.update = function (frameState) {
const loader = this._loader;
@@ -360,15 +311,16 @@ GltfVoxelProvider.prototype.update = function (frameState) {
* Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z.
* Note that there is only one "tile" for a glTF so the only valid input is the "root" tile coordinates.
* This function should not be called before {@link GltfVoxelProvider#ready} returns true.
- * @function
*
* @param {Object} [options] Object with the following properties:
* @param {Number} [options.tileLevel=0] The tile's level.
* @param {Number} [options.tileX=0] The tile's X coordinate.
* @param {Number} [options.tileY=0] The tile's Y coordinate.
* @param {Number} [options.tileZ=0] The tile's Z coordinate.
- *
* @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
+ *
+ * @exception {DeveloperError} The provider must be ready.
+ * @exception {DeveloperError} Only level 0 can be requested.
*/
GltfVoxelProvider.prototype.requestData = function (options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
@@ -394,11 +346,13 @@ GltfVoxelProvider.prototype.requestData = function (options) {
/**
* Check if the data is still being loaded.
* This is intended to be used for unit tests only.
+ *
* @returns {Boolean}
+ *
* @private
*/
-GltfVoxelProvider.prototype._doneLoading = function () {
- return !defined(this._loader);
+GltfVoxelProvider.prototype.doneLoading = function () {
+ return defined(this._data);
};
export default GltfVoxelProvider;
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index a66e5cf3073..0f7cbec3b08 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -12,19 +12,19 @@ import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
* @alias VoxelBoxShape
* @constructor
*
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
* @see VoxelShape
* @see VoxelEllipsoidShape
* @see VoxelCylinderShape
* @see VoxelShapeType
+ *
+ * @private
*/
function VoxelBoxShape() {
/**
* An oriented bounding box containing the bounded shape.
* The update function must be called before accessing this value.
* @type {OrientedBoundingBox}
+ * @readonly
*/
this.orientedBoundingBox = new OrientedBoundingBox();
@@ -32,6 +32,7 @@ function VoxelBoxShape() {
* A bounding sphere containing the bounded shape.
* The update function must be called before accessing this value.
* @type {BoundingSphere}
+ * @readonly
*/
this.boundingSphere = new BoundingSphere();
@@ -39,6 +40,7 @@ function VoxelBoxShape() {
* A transformation matrix containing the bounded shape.
* The update function must be called before accessing this value.
* @type {Matrix4}
+ * @readonly
*/
this.boundTransform = new Matrix4();
@@ -46,6 +48,7 @@ function VoxelBoxShape() {
* A transformation matrix containing the shape, ignoring the bounds.
* The update function must be called before accessing this value.
* @type {Matrix4}
+ * @readonly
*/
this.shapeTransform = new Matrix4();
@@ -53,13 +56,23 @@ function VoxelBoxShape() {
* Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
* The update function must be called before accessing this value.
* @type {Boolean}
+ * @readonly
*/
this.isVisible = false;
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
this._minBounds = Cartesian3.clone(
VoxelBoxShape.DefaultMinBounds,
new Cartesian3()
);
+
+ /**
+ * @type {Cartesian3}
+ * @private
+ */
this._maxBounds = Cartesian3.clone(
VoxelBoxShape.DefaultMaxBounds,
new Cartesian3()
@@ -70,73 +83,12 @@ const scratchTranslation = new Cartesian3();
const scratchScale = new Cartesian3();
const scratchRotation = new Matrix3();
-/**
- * @param {Cartesian3} minimumX
- * @param {Cartesian3} maximumX
- * @param {Cartesian3} minimumY
- * @param {Cartesian3} maximumY
- * @param {Cartesian3} minimumZ
- * @param {Cartesian3} maximumZ
- * @param {Matrix4} matrix
- * @param {OrientedBoundingBox} result
- * @returns {OrientedBoundingBox}
- */
-function getBoxChunkObb(
- minimumX,
- maximumX,
- minimumY,
- maximumY,
- minimumZ,
- maximumZ,
- matrix,
- result
-) {
- const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
- const defaultMaxBounds = VoxelBoxShape.DefaultMaxBounds;
-
- const isDefaultBounds =
- minimumX === defaultMinBounds.x &&
- minimumY === defaultMinBounds.y &&
- minimumZ === defaultMinBounds.z &&
- maximumX === defaultMaxBounds.x &&
- maximumY === defaultMaxBounds.y &&
- maximumZ === defaultMaxBounds.z;
-
- if (isDefaultBounds) {
- result.center = Matrix4.getTranslation(matrix, result.center);
- result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
- } else {
- let scale = Matrix4.getScale(matrix, scratchScale);
- const translation = Matrix4.getTranslation(matrix, scratchTranslation);
- result.center = Cartesian3.fromElements(
- translation.x + scale.x * 0.5 * (minimumX + maximumX),
- translation.y + scale.y * 0.5 * (maximumY + minimumY),
- translation.z + scale.z * 0.5 * (maximumZ + minimumZ),
- result.center
- );
-
- scale = Cartesian3.fromElements(
- scale.x * 0.5 * (maximumX - minimumX),
- scale.y * 0.5 * (maximumY - minimumY),
- scale.z * 0.5 * (maximumZ - minimumZ),
- scratchScale
- );
- const rotation = Matrix4.getRotation(matrix, scratchRotation);
- result.halfAxes = Matrix3.setScale(rotation, scale, result.halfAxes);
- }
-
- return result;
-}
-
/**
* Update the shape's state.
- * @function
+ *
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
//>>includeStart('debug', pragmas.debug);
@@ -205,16 +157,13 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
/**
* Computes an oriented bounding box for a specified tile.
* The update function must be called before calling this function.
- * @function
+ *
* @param {Number} tileLevel The tile's level.
* @param {Number} tileX The tile's x coordinate.
* @param {Number} tileY The tile's y coordinate.
* @param {Number} tileZ The tile's z coordinate.
* @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
* @returns {OrientedBoundingBox} The oriented bounding box.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
tileLevel,
@@ -283,12 +232,9 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
/**
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
- * @function
+ *
* @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
* @returns {Number} The step size.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelBoxShape.prototype.computeApproximateStepSize = function (dimensions) {
//>>includeStart('debug', pragmas.debug);
@@ -300,26 +246,84 @@ VoxelBoxShape.prototype.computeApproximateStepSize = function (dimensions) {
/**
* Defines the minimum bounds of the shape. Corresponds to minimum X, Y, Z.
- * @type {Cartesian3}
*
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ * @type {Cartesian3}
+ * @constant
+ * @readonly
*/
-VoxelBoxShape.DefaultMinBounds = Cartesian3.negate(
- Cartesian3.ONE,
- new Cartesian3()
-);
+VoxelBoxShape.DefaultMinBounds = new Cartesian3(-1.0, -1.0, -1.0);
/**
* Defines the maximum bounds of the shape. Corresponds to maximum X, Y, Z.
+ *
* @type {Cartesian3}
+ * @constant
+ * @readonly
+ */
+VoxelBoxShape.DefaultMaxBounds = new Cartesian3(+1.0, +1.0, +1.0);
+
+/**
+ * Computes an {@link OrientedBoundingBox} for a subregion of the shape.
+ *
+ * @function
+ *
+ * @param {Cartesian3} minimumX The minimumX.
+ * @param {Cartesian3} maximumX The maximumX.
+ * @param {Cartesian3} minimumY The minimumY.
+ * @param {Cartesian3} maximumY The maximumY.
+ * @param {Cartesian3} minimumZ The minimumZ.
+ * @param {Cartesian3} maximumZ The maximumZ.
+ * @param {Matrix4} matrix The matrix to transform the points.
+ * @param {OrientedBoundingBox} result The object onto which to store the result.
+ * @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
*
* @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
-VoxelBoxShape.DefaultMaxBounds = Cartesian3.clone(
- Cartesian3.ONE,
- new Cartesian3()
-);
+function getBoxChunkObb(
+ minimumX,
+ maximumX,
+ minimumY,
+ maximumY,
+ minimumZ,
+ maximumZ,
+ matrix,
+ result
+) {
+ const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelBoxShape.DefaultMaxBounds;
+
+ const isDefaultBounds =
+ minimumX === defaultMinBounds.x &&
+ minimumY === defaultMinBounds.y &&
+ minimumZ === defaultMinBounds.z &&
+ maximumX === defaultMaxBounds.x &&
+ maximumY === defaultMaxBounds.y &&
+ maximumZ === defaultMaxBounds.z;
+
+ if (isDefaultBounds) {
+ result.center = Matrix4.getTranslation(matrix, result.center);
+ result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
+ } else {
+ let scale = Matrix4.getScale(matrix, scratchScale);
+ const translation = Matrix4.getTranslation(matrix, scratchTranslation);
+ result.center = Cartesian3.fromElements(
+ translation.x + scale.x * 0.5 * (minimumX + maximumX),
+ translation.y + scale.y * 0.5 * (maximumY + minimumY),
+ translation.z + scale.z * 0.5 * (maximumZ + minimumZ),
+ result.center
+ );
+
+ scale = Cartesian3.fromElements(
+ scale.x * 0.5 * (maximumX - minimumX),
+ scale.y * 0.5 * (maximumY - minimumY),
+ scale.z * 0.5 * (maximumZ - minimumZ),
+ scratchScale
+ );
+ const rotation = Matrix4.getRotation(matrix, scratchRotation);
+ result.halfAxes = Matrix3.setScale(rotation, scale, result.halfAxes);
+ }
+
+ return result;
+}
export default VoxelBoxShape;
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index e379a8b0b11..bf9f0a748ce 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -12,19 +12,19 @@ import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
* @alias VoxelCylinderShape
* @constructor
*
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
* @see VoxelShape
- * @see VoxelCylinderShape
+ * @see VoxelBoxShape
* @see VoxelEllipsoidShape
* @see VoxelShapeType
+ *
+ * @private
*/
function VoxelCylinderShape() {
/**
* An oriented bounding box containing the bounded shape.
* The update function must be called before accessing this value.
* @type {OrientedBoundingBox}
+ * @readonly
*/
this.orientedBoundingBox = new OrientedBoundingBox();
@@ -32,6 +32,7 @@ function VoxelCylinderShape() {
* A bounding sphere containing the bounded shape.
* The update function must be called before accessing this value.
* @type {BoundingSphere}
+ * @readonly
*/
this.boundingSphere = new BoundingSphere();
@@ -39,6 +40,7 @@ function VoxelCylinderShape() {
* A transformation matrix containing the bounded shape.
* The update function must be called before accessing this value.
* @type {Matrix4}
+ * @readonly
*/
this.boundTransform = new Matrix4();
@@ -46,6 +48,7 @@ function VoxelCylinderShape() {
* A transformation matrix containing the shape, ignoring the bounds.
* The update function must be called before accessing this value.
* @type {Matrix4}
+ * @readonly
*/
this.shapeTransform = new Matrix4();
@@ -53,14 +56,44 @@ function VoxelCylinderShape() {
* Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
* The update function must be called before accessing this value.
* @type {Boolean}
+ * @readonly
*/
this.isVisible = false;
+ /**
+ * @type {Number}
+ * @private
+ */
this._minimumRadius = VoxelCylinderShape.DefaultMinBounds.x;
+
+ /**
+ * @type {Number}
+ * @private
+ */
this._maximumRadius = VoxelCylinderShape.DefaultMaxBounds.x;
+
+ /**
+ * @type {Number}
+ * @private
+ */
this._minimumHeight = VoxelCylinderShape.DefaultMinBounds.y;
+
+ /**
+ * @type {Number}
+ * @private
+ */
this._maximumHeight = VoxelCylinderShape.DefaultMaxBounds.y;
+
+ /**
+ * @type {Number}
+ * @private
+ */
this._minimumAngle = VoxelCylinderShape.DefaultMinBounds.z;
+
+ /**
+ * @type {Number}
+ * @private
+ */
this._maximumAngle = VoxelCylinderShape.DefaultMaxBounds.z;
}
@@ -167,136 +200,14 @@ const scratchPositions = [
),
];
-/**
- * @function
- * @param {Number} radiusStart
- * @param {Number} radiusEnd
- * @param {Number} heightStart
- * @param {Number} heightEnd
- * @param {Number} angleStart
- * @param {Number} angleEnd
- * @param {Matrix4} matrix
- * @param {OrientedBoundingBox} result
- * @returns {OrientedBoundingBox}
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- */
-function getCylinderChunkObb(
- radiusStart,
- radiusEnd,
- heightStart,
- heightEnd,
- angleStart,
- angleEnd,
- matrix,
- result
-) {
- const defaultMinBounds = VoxelCylinderShape.DefaultMinBounds;
- const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds;
- const defaultMinRadius = defaultMinBounds.x; // 0
- const defaultMaxRadius = defaultMaxBounds.x; // 1
- const defaultMinHeight = defaultMinBounds.y; // -1
- const defaultMaxHeight = defaultMaxBounds.y; // +1
- const defaultMinAngle = defaultMinBounds.z; // -pi/2
- const defaultMaxAngle = defaultMaxBounds.z; // +pi/2
-
- // Return early if using the default bounds
- if (
- radiusStart === defaultMinRadius &&
- radiusEnd === defaultMaxRadius &&
- heightStart === defaultMinHeight &&
- heightEnd === defaultMaxHeight &&
- angleStart === defaultMinAngle &&
- angleEnd === defaultMaxAngle
- ) {
- result.center = Matrix4.getTranslation(matrix, result.center);
- result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
- return result;
- }
-
- let testAngleCount = 0;
- const testAngles = scratchTestAngles;
- const halfPi = CesiumMath.PI_OVER_TWO;
-
- testAngles[testAngleCount++] = angleStart;
- testAngles[testAngleCount++] = angleEnd;
-
- if (angleStart > angleEnd) {
- if (angleStart > 0.0 && angleEnd > 0.0) {
- testAngles[testAngleCount++] = 0.0;
- }
- if (angleStart > +halfPi && angleEnd > +halfPi) {
- testAngles[testAngleCount++] = +halfPi;
- }
- if (angleStart > -halfPi && angleEnd > -halfPi) {
- testAngles[testAngleCount++] = -halfPi;
- }
- // It will always cross the 180th meridian
- testAngles[testAngleCount++] = CesiumMath.PI;
- } else {
- if (angleStart < 0.0 && angleEnd > 0.0) {
- testAngles[testAngleCount++] = 0.0;
- }
- if (angleStart < +halfPi && angleEnd > +halfPi) {
- testAngles[testAngleCount++] = +halfPi;
- }
- if (angleStart < -halfPi && angleEnd > -halfPi) {
- testAngles[testAngleCount++] = -halfPi;
- }
- }
-
- const positions = scratchPositions[testAngleCount];
-
- for (let i = 0; i < testAngleCount; i++) {
- const angle = testAngles[i];
- const cosAngle = Math.cos(angle);
- const sinAngle = Math.sin(angle);
-
- positions[i * 4 + 0] = Cartesian3.fromElements(
- cosAngle * radiusStart,
- sinAngle * radiusStart,
- heightStart,
- positions[i * 4 + 0]
- );
- positions[i * 4 + 1] = Cartesian3.fromElements(
- cosAngle * radiusEnd,
- sinAngle * radiusEnd,
- heightStart,
- positions[i * 4 + 1]
- );
- positions[i * 4 + 2] = Cartesian3.fromElements(
- cosAngle * radiusStart,
- sinAngle * radiusStart,
- heightEnd,
- positions[i * 4 + 2]
- );
- positions[i * 4 + 3] = Cartesian3.fromElements(
- cosAngle * radiusEnd,
- sinAngle * radiusEnd,
- heightEnd,
- positions[i * 4 + 3]
- );
- }
-
- for (let i = 0; i < testAngleCount * 4; i++) {
- positions[i] = Matrix4.multiplyByPoint(matrix, positions[i], positions[i]);
- }
-
- return OrientedBoundingBox.fromPoints(positions, result);
-}
-
const scratchScale = new Cartesian3();
/**
* Update the shape's state.
- * @function
+ *
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelCylinderShape.prototype.update = function (
modelMatrix,
@@ -384,16 +295,13 @@ VoxelCylinderShape.prototype.update = function (
/**
* Computes an oriented bounding box for a specified tile.
* The update function must be called before calling this function.
- * @function
+ *
* @param {Number} tileLevel The tile's level.
* @param {Number} tileX The tile's x coordinate.
* @param {Number} tileY The tile's y coordinate.
* @param {Number} tileZ The tile's z coordinate.
* @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
* @returns {OrientedBoundingBox} The oriented bounding box.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function (
tileLevel,
@@ -468,6 +376,13 @@ const scratchVoxelScale = new Cartesian3();
const scratchRootScale = new Cartesian3();
const scratchScaleRatio = new Cartesian3();
+/**
+ * Computes an approximate step size for raymarching the root tile of a voxel grid.
+ * The update function must be called before calling this function.
+ *
+ * @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
+ * @returns {Number} The step size.
+ */
VoxelCylinderShape.prototype.computeApproximateStepSize = function (
voxelDimensions
) {
@@ -515,15 +430,146 @@ VoxelCylinderShape.prototype.computeApproximateStepSize = function (
};
/**
- * @private
+ * Defines the minimum bounds of the shape. Corresponds to minimum radius, height, angle.
+ *
* @type {Cartesian3}
+ * @constant
+ * @readonly
+ *
+ * @private
*/
VoxelCylinderShape.DefaultMinBounds = new Cartesian3(0.0, -1.0, -CesiumMath.PI);
/**
- * @private
+ * Defines the maximum bounds of the shape. Corresponds to maximum radius, height, angle.
+ *
* @type {Cartesian3}
+ * @constant
+ * @readonly
+ *
+ * @private
*/
VoxelCylinderShape.DefaultMaxBounds = new Cartesian3(1.0, +1.0, +CesiumMath.PI);
+/**
+ * Computes an {@link OrientedBoundingBox} for a subregion of the shape.
+ *
+ * @function
+ *
+ * @param {Number} radiusStart The radiusStart.
+ * @param {Number} radiusEnd The radiusEnd.
+ * @param {Number} heightStart The heightStart.
+ * @param {Number} heightEnd The heightEnd.
+ * @param {Number} angleStart The angleStart.
+ * @param {Number} angleEnd The angleEnd.
+ * @param {Matrix4} matrix The matrix to transform the points.
+ * @param {OrientedBoundingBox} result The object onto which to store the result.
+ * @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
+ *
+ * @private
+ */
+function getCylinderChunkObb(
+ radiusStart,
+ radiusEnd,
+ heightStart,
+ heightEnd,
+ angleStart,
+ angleEnd,
+ matrix,
+ result
+) {
+ const defaultMinBounds = VoxelCylinderShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds;
+ const defaultMinRadius = defaultMinBounds.x; // 0
+ const defaultMaxRadius = defaultMaxBounds.x; // 1
+ const defaultMinHeight = defaultMinBounds.y; // -1
+ const defaultMaxHeight = defaultMaxBounds.y; // +1
+ const defaultMinAngle = defaultMinBounds.z; // -pi/2
+ const defaultMaxAngle = defaultMaxBounds.z; // +pi/2
+
+ // Return early if using the default bounds
+ if (
+ radiusStart === defaultMinRadius &&
+ radiusEnd === defaultMaxRadius &&
+ heightStart === defaultMinHeight &&
+ heightEnd === defaultMaxHeight &&
+ angleStart === defaultMinAngle &&
+ angleEnd === defaultMaxAngle
+ ) {
+ result.center = Matrix4.getTranslation(matrix, result.center);
+ result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
+ return result;
+ }
+
+ let testAngleCount = 0;
+ const testAngles = scratchTestAngles;
+ const halfPi = CesiumMath.PI_OVER_TWO;
+
+ testAngles[testAngleCount++] = angleStart;
+ testAngles[testAngleCount++] = angleEnd;
+
+ if (angleStart > angleEnd) {
+ if (angleStart > 0.0 && angleEnd > 0.0) {
+ testAngles[testAngleCount++] = 0.0;
+ }
+ if (angleStart > +halfPi && angleEnd > +halfPi) {
+ testAngles[testAngleCount++] = +halfPi;
+ }
+ if (angleStart > -halfPi && angleEnd > -halfPi) {
+ testAngles[testAngleCount++] = -halfPi;
+ }
+ // It will always cross the 180th meridian
+ testAngles[testAngleCount++] = CesiumMath.PI;
+ } else {
+ if (angleStart < 0.0 && angleEnd > 0.0) {
+ testAngles[testAngleCount++] = 0.0;
+ }
+ if (angleStart < +halfPi && angleEnd > +halfPi) {
+ testAngles[testAngleCount++] = +halfPi;
+ }
+ if (angleStart < -halfPi && angleEnd > -halfPi) {
+ testAngles[testAngleCount++] = -halfPi;
+ }
+ }
+
+ const positions = scratchPositions[testAngleCount];
+
+ for (let i = 0; i < testAngleCount; i++) {
+ const angle = testAngles[i];
+ const cosAngle = Math.cos(angle);
+ const sinAngle = Math.sin(angle);
+
+ positions[i * 4 + 0] = Cartesian3.fromElements(
+ cosAngle * radiusStart,
+ sinAngle * radiusStart,
+ heightStart,
+ positions[i * 4 + 0]
+ );
+ positions[i * 4 + 1] = Cartesian3.fromElements(
+ cosAngle * radiusEnd,
+ sinAngle * radiusEnd,
+ heightStart,
+ positions[i * 4 + 1]
+ );
+ positions[i * 4 + 2] = Cartesian3.fromElements(
+ cosAngle * radiusStart,
+ sinAngle * radiusStart,
+ heightEnd,
+ positions[i * 4 + 2]
+ );
+ positions[i * 4 + 3] = Cartesian3.fromElements(
+ cosAngle * radiusEnd,
+ sinAngle * radiusEnd,
+ heightEnd,
+ positions[i * 4 + 3]
+ );
+ }
+
+ for (let i = 0; i < testAngleCount * 4; i++) {
+ positions[i] = Matrix4.multiplyByPoint(matrix, positions[i], positions[i]);
+ }
+
+ return OrientedBoundingBox.fromPoints(positions, result);
+}
+
export default VoxelCylinderShape;
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index d6aeb46fda0..ec374131da2 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -19,17 +19,12 @@ import VoxelShapeType from "./VoxelShapeType.js";
* @alias VoxelEllipsoidShape
* @constructor
*
- * @param {Object} [options]
- * @param {Ellipsoid} [options.ellipsoid]
- * @param {Rectangle} [options.rectangle]
- * @param {Number} [options.minimumHeight]
- * @param {Number} [options.maximumHeight]
- * @param {Cartesian3} [options.translation]
- * @param {Cartesian3} [options.scale]
- * @param {Matrix3} [options.rotation]
- *
* @see VoxelShape
+ * @see VoxelBoxShape
+ * @see VoxelCylinderShape
* @see VoxelShapeType
+ *
+ * @private
*/
function VoxelEllipsoidShape(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 82e1f5c6118..17af662bfb9 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -54,12 +54,12 @@ import MetadataType from "./MetadataType.js";
* @param {CustomShader} [options.customShader] The custom shader used to style the primitive.
* @param {Clock} [options.clock] The clock used to control time dynamic behavior.
*
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
* @see VoxelProvider
* @see Cesium3DTilesVoxelProvider
* @see GltfVoxelProvider
* @see VoxelShapeType
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
function VoxelPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
@@ -86,14 +86,9 @@ function VoxelPrimitive(options) {
*/
this._provider = options.provider;
- // If the provider fails to initialize the primitive will fail too.
- const that = this;
- this._provider.readyPromise.catch(function (error) {
- that._readyPromise.reject(`provider failed with error:\n${error}`);
- });
-
/**
* This member is not created until the provider and shape are ready.
+ *
* @type {VoxelTraversal}
* @private
*/
@@ -101,6 +96,7 @@ function VoxelPrimitive(options) {
/**
* This member is not created until the provider is ready.
+ *
* @type {VoxelShape}
* @private
*/
@@ -108,6 +104,7 @@ function VoxelPrimitive(options) {
/**
* This member is not created until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -115,6 +112,7 @@ function VoxelPrimitive(options) {
/**
* This member is not created until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -122,6 +120,7 @@ function VoxelPrimitive(options) {
/**
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -130,6 +129,7 @@ function VoxelPrimitive(options) {
/**
* Used to detect if the shape is dirty.
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -137,6 +137,7 @@ function VoxelPrimitive(options) {
/**
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -145,6 +146,7 @@ function VoxelPrimitive(options) {
/**
* Used to detect if the shape is dirty.
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -152,6 +154,7 @@ function VoxelPrimitive(options) {
/**
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -160,6 +163,7 @@ function VoxelPrimitive(options) {
/**
* Used to detect if the clipping is dirty.
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -167,6 +171,7 @@ function VoxelPrimitive(options) {
/**
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
@@ -175,13 +180,15 @@ function VoxelPrimitive(options) {
/**
* Used to detect if the clipping is dirty.
* This member is not known until the provider is ready.
+ *
* @type {Cartesian3}
* @private
*/
this._maxClippingBoundsOld = new Cartesian3();
/**
- * The primitive's model matrix
+ * The primitive's model matrix.
+ *
* @type {Matrix4}
* @private
*/
@@ -193,6 +200,7 @@ function VoxelPrimitive(options) {
/**
* The primitive's model matrix multiplied by the provider's model matrix.
* This member is not known until the provider is ready.
+ *
* @type {Matrix4}
* @private
*/
@@ -201,6 +209,7 @@ function VoxelPrimitive(options) {
/**
* Used to detect if the shape is dirty.
* This member is not known until the provider is ready.
+ *
* @type {Matrix4}
* @private
*/
@@ -246,20 +255,20 @@ function VoxelPrimitive(options) {
this._pickId = undefined;
// /**
- // * @private
// * @type {TimeIntervalCollection}
+ // * @private
// */
// this._timeIntervalCollection = undefined;
// /**
- // * @private
// * @type {Clock}
+ // * @private
// */
// this._clock = options.clock;
// /**
- // * @private
// * @type {Number}
+ // * @private
// */
// this._keyframeCount = 1;
@@ -267,45 +276,107 @@ function VoxelPrimitive(options) {
/**
* @type {Matrix4}
+ * @private
*/
this._transformPositionWorldToUv = new Matrix4();
/**
* @type {Matrix4}
+ * @private
*/
this._transformPositionUvToWorld = new Matrix4();
/**
* @type {Matrix3}
+ * @private
*/
this._transformDirectionWorldToLocal = new Matrix3();
/**
* @type {Matrix3}
+ * @private
*/
this._transformNormalLocalToWorld = new Matrix3();
/**
* @type {Number}
+ * @private
*/
this._stepSizeUv = 1.0;
// Rendering
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._jitter = true;
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._nearestSampling = false;
+
+ /**
+ * @type {Number}
+ * @private
+ */
this._stepSizeMultiplier = 1.0;
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._despeckle = false;
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._depthTest = true;
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._useLogDepth = undefined;
+
+ /**
+ * @type {Number}
+ * @private
+ */
this._screenSpaceError = 4.0; // in pixels
// Debug / statistics
+ /**
+ * @type {PolylineCollection}
+ * @private
+ */
this._debugPolylines = new PolylineCollection();
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._debugDraw = false;
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._disableRender = false;
+
+ /**
+ * @type {Boolean}
+ * @private
+ */
this._disableUpdate = false;
// Uniforms
+ /**
+ * @type {Object.}
+ * @private
+ */
this._uniformMapValues = {
/**
* @ignore
@@ -359,8 +430,8 @@ function VoxelPrimitive(options) {
// Automatically generate uniform map from the uniform values
/**
- * @private
* @type {Object.}
+ * @private
*/
this._uniformMap = {};
const uniformMapValues = this._uniformMapValues;
@@ -374,11 +445,19 @@ function VoxelPrimitive(options) {
this._uniformMap[`u_${key}`] = getUniformFunction(key);
}
}
+
+ // If the provider fails to initialize the primitive will fail too.
+ const provider = this._provider;
+ const primitive = this;
+ provider.readyPromise.catch(function (error) {
+ primitive._readyPromise.reject(`provider failed with error:\n${error}`);
+ });
}
Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets a value indicating whether or not the primitive is ready for use.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Boolean}
* @readonly
@@ -391,6 +470,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets the promise that will be resolved when the primitive is ready for use.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Promise.}
* @readonly
@@ -403,6 +483,8 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets the {@link VoxelProvider} associated with this primitive.
+ *
+ * @memberof VoxelPrimitive.prototype
* @type {VoxelProvider}
* @readonly
*/
@@ -414,10 +496,12 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets the bounding sphere.
+ *
* @memberof VoxelPrimitive.prototype
* @type {BoundingSphere}
- * @throws {DeveloperError} If the primitive is not ready.
* @readonly
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
boundingSphere: {
get: function () {
@@ -435,10 +519,12 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets the oriented bounding box.
+ *
* @memberof VoxelPrimitive.prototype
* @type {OrientedBoundingBox}
- * @throws {DeveloperError} If the primitive is not ready.
* @readonly
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
orientedBoundingBox: {
get: function () {
@@ -455,6 +541,8 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
/**
+ * Gets the model matrix.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Matrix4}
* @readonly
@@ -473,10 +561,13 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
/**
+ * Gets the compound model matrix
+ *
* @memberof VoxelPrimitive.prototype
* @type {Matrix4}
- * @throws {DeveloperError} If the primitive is not ready.
* @readonly
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
compoundModelMatrix: {
get: function () {
@@ -494,10 +585,12 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets the shape type.
+ *
* @memberof VoxelPrimitive.prototype
* @type {VoxelShapeType}
- * @throws {DeveloperError} If the primitive is not ready.
* @readonly
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
shape: {
get: function () {
@@ -514,10 +607,13 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
/**
+ * Gets the voxel dimensions.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
- * @throws {DeveloperError} If the primitive is not ready.
* @readonly
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
dimensions: {
get: function () {
@@ -535,10 +631,12 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets the minimum value per channel of the voxel data.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Number[]}
- * @throws {DeveloperError} If the primitive is not ready.
* @readonly
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
minimumValues: {
get: function () {
@@ -556,10 +654,12 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets the maximum value per channel of the voxel data.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Number[]}
- * @throws {DeveloperError} If the primitive is not ready.
* @readonly
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
maximumValues: {
get: function () {
@@ -577,6 +677,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets whether or not this primitive should be displayed.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Boolean}
*/
@@ -595,6 +696,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets whether or not the primitive should update when the view changes.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Boolean}
*/
@@ -613,6 +715,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets whether or not to render debug visualizations.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Boolean}
*/
@@ -631,6 +734,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets whether or not to test against depth when rendering.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Boolean}
*/
@@ -653,6 +757,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets whether or not to jitter the view ray during the raymarch.
* This reduces stair-step artifacts but introduces noise.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Boolean}
*/
@@ -673,7 +778,10 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
/**
+ * Gets or sets the nearest sampling.
*
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
*/
nearestSampling: {
get: function () {
@@ -696,6 +804,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
* of a voxel is greater than the screen space error, the tile is subdivided.
* Lower screen space error corresponds with higher detail rendering, but could
* result in worse performance and higher memory consumption.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Number}
*/
@@ -716,6 +825,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
* Gets or sets the step size multiplier used during raymarching.
* The lower the value, the higher the rendering quality, but
* also the worse the performance.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Number}
*/
@@ -734,6 +844,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets whether to reduce thin and noisy details.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Boolean}
*/
@@ -755,9 +866,11 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets the minimum bounds. TODO: fill in the rest later
+ *
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
- * @throws {DeveloperError} If the primitive is not ready.
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
minBounds: {
get: function () {
@@ -786,10 +899,12 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
/**
- * Gets or sets the maximum bounds. TODO: fill in the rest later
+ * Gets or sets the maximum bounds. TODO: fill in the rest later.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
- * @throws {DeveloperError} If the primitive is not ready.
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
maxBounds: {
get: function () {
@@ -821,9 +936,11 @@ Object.defineProperties(VoxelPrimitive.prototype, {
* Gets or sets the minimum clipping location in the shape's local coordinate system.
* Any voxel content outside the range is clipped.
* The minimum value is 0 and the maximum value is 1.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
- * @throws {DeveloperError} If the primitive is not ready.
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
minClippingBounds: {
get: function () {
@@ -858,9 +975,11 @@ Object.defineProperties(VoxelPrimitive.prototype, {
* Gets or sets the maximum clipping location in the shape's local coordinate system.
* Any voxel content outside the range is clipped.
* The minimum value is 0 and the maximum value is 1.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Cartesian3}
- * @throws {DeveloperError} If the primitive is not ready.
+ *
+ * @exception {DeveloperError} If the primitive is not ready.
*/
maxClippingBounds: {
get: function () {
@@ -893,6 +1012,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets or sets the custom shader. If undefined, {@link VoxelPrimitive.DefaultCustomShader} is set.
+ *
* @memberof VoxelPrimitive.prototype
* @type {CustomShader}
*/
@@ -914,6 +1034,7 @@ Object.defineProperties(VoxelPrimitive.prototype, {
/**
* Gets an event that is raised whenever a custom shader is compiled.
+ *
* @memberof VoxelPrimitive.prototype
* @type {Event}
* @readonly
@@ -953,7 +1074,8 @@ const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
);
/**
- * @private
+ * Updates the voxel primitive.
+ *
* @param {FrameState} frameState
*/
VoxelPrimitive.prototype.update = function (frameState) {
@@ -975,10 +1097,10 @@ VoxelPrimitive.prototype.update = function (frameState) {
if (!this._ready) {
// Don't make the primitive ready until after its first update because
// external code may want to change some of its properties before it's rendered.
- const that = this;
+ const primitive = this;
frameState.afterRender.push(function () {
- that._ready = true;
- that._readyPromise.resolve(that);
+ primitive._ready = true;
+ primitive._readyPromise.resolve(primitive);
});
// Create pickId here instead of the constructor because it needs the context object.
@@ -1324,13 +1446,23 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Set uniforms that come from the traversal.
const traversal = this._traversal;
+ const useLeafNodeTexture = traversal.useLeafNodeTexture;
uniforms.octreeInternalNodeTexture = traversal.internalNodeTexture;
+ uniforms.octreeInternalNodeTexelSizeUv = Cartesian2.clone(
+ traversal.internalNodeTexelSizeUv,
+ uniforms.octreeInternalNodeTexelSizeUv
+ );
uniforms.octreeInternalNodeTilesPerRow = traversal.internalNodeTilesPerRow;
- uniforms.octreeInternalNodeTexelSizeUv = traversal.internalNodeTexelSizeUv;
- uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
- uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
- uniforms.octreeLeafNodeTexelSizeUv = traversal.leafNodeTexelSizeUv;
+
+ if (useLeafNodeTexture) {
+ uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
+ uniforms.octreeLeafNodeTexelSizeUv = Cartesian2.clone(
+ traversal.leafNodeTexelSizeUv,
+ uniforms.octreeLeafNodeTexelSizeUv
+ );
+ uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
+ }
const megatextures = traversal.megatextures;
const megatexture = megatextures[0];
@@ -1360,67 +1492,62 @@ VoxelPrimitive.prototype.update = function (frameState) {
megatexture.regionSizeUv,
uniforms.megatextureTileSizeUv
);
- } else {
- // Update the voxel traversal
- const traversal = this._traversal;
- if (shape.isVisible) {
- // Find the keyframe location to render at. Doesn't need to be a whole number.
- let keyframeLocation = 0.0;
- const clock = this._clock;
- const timeIntervalCollection = this._timeIntervalCollection;
- if (defined(timeIntervalCollection) && defined(clock)) {
- let date = clock.currentTime;
- let timeInterval;
- let timeIntervalIndex = timeIntervalCollection.indexOf(date);
- if (timeIntervalIndex >= 0) {
+ } else if (shape.isVisible) {
+ // Find the keyframe location to render at. Doesn't need to be a whole number.
+ let keyframeLocation = 0.0;
+ const clock = this._clock;
+ const timeIntervalCollection = this._timeIntervalCollection;
+ if (defined(timeIntervalCollection) && defined(clock)) {
+ let date = clock.currentTime;
+ let timeInterval;
+ let timeIntervalIndex = timeIntervalCollection.indexOf(date);
+ if (timeIntervalIndex >= 0) {
+ timeInterval = timeIntervalCollection.get(timeIntervalIndex);
+ } else {
+ // Date fell outside the range
+ timeIntervalIndex = ~timeIntervalIndex;
+ if (timeIntervalIndex === timeIntervalCollection.length) {
+ // Date past range
+ timeIntervalIndex = timeIntervalCollection.length - 1;
timeInterval = timeIntervalCollection.get(timeIntervalIndex);
+ date = timeInterval.stop;
} else {
- // Date fell outside the range
- timeIntervalIndex = ~timeIntervalIndex;
- if (timeIntervalIndex === timeIntervalCollection.length) {
- // Date past range
- timeIntervalIndex = timeIntervalCollection.length - 1;
- timeInterval = timeIntervalCollection.get(timeIntervalIndex);
- date = timeInterval.stop;
- } else {
- // Date before range
- timeInterval = timeIntervalCollection.get(timeIntervalIndex);
- date = timeInterval.start;
- }
+ // Date before range
+ timeInterval = timeIntervalCollection.get(timeIntervalIndex);
+ date = timeInterval.start;
}
-
- // De-lerp between the start and end of the interval
- const totalSeconds = JulianDate.secondsDifference(
- timeInterval.stop,
- timeInterval.start
- );
- const secondsDifferenceStart = JulianDate.secondsDifference(
- date,
- timeInterval.start
- );
- const t = secondsDifferenceStart / totalSeconds;
- keyframeLocation = timeIntervalIndex + t;
}
- traversal.update(
- frameState,
- keyframeLocation,
- shapeIsDirty, // recomputeBoundingVolumes
- this._disableUpdate // pauseUpdate
+ // De-lerp between the start and end of the interval
+ const totalSeconds = JulianDate.secondsDifference(
+ timeInterval.stop,
+ timeInterval.start
);
+ const secondsDifferenceStart = JulianDate.secondsDifference(
+ date,
+ timeInterval.start
+ );
+ const t = secondsDifferenceStart / totalSeconds;
+ keyframeLocation = timeIntervalIndex + t;
}
- // Debug draw bounding boxes and other things. Must go after traversal update
- // because that's what updates the tile bounding boxes.
- if (this._debugDraw) {
- debugDraw(this, frameState);
- }
+ // Update the voxel traversal
+ const traversal = this._traversal;
- const rootNodeLoaded = traversal.rootNode.isRenderable(
- traversal.frameNumber
+ const hasLoadedData = traversal.update(
+ frameState,
+ keyframeLocation,
+ shapeIsDirty, // recomputeBoundingVolumes
+ this._disableUpdate // pauseUpdate
);
- if (shape.isVisible && !this._disableRender && rootNodeLoaded) {
+ if (hasLoadedData && this._debugDraw) {
+ // Debug draw bounding boxes and other things. Must go after traversal update
+ // because that's what updates the tile bounding boxes.
+ debugDraw(this, frameState);
+ }
+
+ if (hasLoadedData && !this._disableRender) {
// Process clipping bounds.
const minClip = this._minClippingBounds;
const maxClip = this._maxClippingBounds;
@@ -1580,8 +1707,13 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Shader builder helpers
/**
- * @param {MetadataType} type
- * @returns {String}
+ * Converts a {@link MetadataType} to a GLSL type.
+ *
+ * @function
+ *
+ * @param {MetadataType} type The {@link MetadataType}.
+ * @returns {String} The GLSL type.
+ *
* @private
*/
function getGlslType(type) {
@@ -1595,9 +1727,15 @@ function getGlslType(type) {
return "vec4";
}
}
+
/**
- * @param {MetadataType} type
- * @returns {String}
+ * Gets the GLSL swizzle when reading data from a texture.
+ *
+ * @function
+ *
+ * @param {MetadataType} type The {@link MetadataType}.
+ * @returns {String} The GLSL swizzle.
+ *
* @private
*/
function getGlslTextureSwizzle(type) {
@@ -1613,8 +1751,13 @@ function getGlslTextureSwizzle(type) {
}
/**
- * @param {MetadataType} type
- * @returns {String}
+ * Gets the GLSL type of the partial derivative of {@link MetadataType}.
+ *
+ * @function
+ *
+ * @param {MetadataType} type The {@link MetadataType}.
+ * @returns {String} The GLSL type.
+ *
* @private
*/
function getGlslPartialDerivativeType(type) {
@@ -1632,8 +1775,12 @@ function getGlslPartialDerivativeType(type) {
/**
* GLSL needs to have `.0` at the end of whole number floats or else it's
* treated like an integer.
- * @param {Number} number
- * @returns {String}
+ *
+ * @function
+ *
+ * @param {Number} number The number to convert.
+ * @returns {String} The number as floating point in GLSL.
+ *
* @private
*/
function getGlslNumberAsFloat(number) {
@@ -1645,21 +1792,29 @@ function getGlslNumberAsFloat(number) {
}
/**
+ * Gets the GLSL field
+ *
+ * @function
+ *
* @param {MetadataType} type
* @param {Number} index
* @returns {String}
+ *
* @private
*/
function getGlslField(type, index) {
if (type === MetadataType.SCALAR) {
return "";
}
- return `.[${index}]`;
+ return `[${index}]`;
}
/**
+ * @function
+ *
* @param {VoxelPrimitive} that
* @param {Context} context
+ *
* @private
*/
function buildDrawCommands(that, context) {
@@ -2314,10 +2469,10 @@ VoxelPrimitive.prototype.isDestroyed = function () {
*
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
*
+ * @see VoxelPrimitive#isDestroyed
+ *
* @example
* voxelPrimitive = voxelPrimitive && voxelPrimitive.destroy();
- *
- * @see VoxelPrimitive#isDestroyed
*/
VoxelPrimitive.prototype.destroy = function () {
const drawCommand = this._drawCommand;
@@ -2332,19 +2487,7 @@ VoxelPrimitive.prototype.destroy = function () {
}
this._pickId = this._pickId && this._pickId.destroy();
-
- if (defined(this._traversal)) {
- const megatextures = this._traversal.megatextures;
- const length = megatextures.length;
- for (let i = 0; i < length; i++) {
- const megatexture = megatextures[i];
- // TODO: when would megatexture not be defined?
- if (defined(megatexture)) {
- megatexture.destroy();
- }
- }
- this._traversal = undefined;
- }
+ this._traversal = this._traversal && this._traversal.destroy();
return destroyObject(this);
};
@@ -2405,6 +2548,7 @@ const scratchCornersClipSpace = new Array(
* clip space prior to perspective division.
*
* @function
+ *
* @param {OrientedBoundingBox} orientedBoundingBox
* @param {Matrix4} worldToProjection
* @param {Cartesian4} result
@@ -2506,11 +2650,17 @@ const polylineYAxis = new Cartesian3(0.0, polylineAxisDistance, 0.0);
const polylineZAxis = new Cartesian3(0.0, 0.0, polylineAxisDistance);
/**
- * @ignore
+ * Draws the tile bounding boxes and axes.
+ *
+ * @function
+ *
* @param {VoxelPrimitive} that
* @param {FrameState} frameState
+ *
+ * @private
*/
function debugDraw(that, frameState) {
+ const frameNumber = frameState.frameNumber;
const traversal = that._traversal;
const polylines = that._debugPolylines;
const shapeVisible = that._shape.isVisible;
@@ -2545,7 +2695,6 @@ function debugDraw(that, frameState) {
}
function drawTile(tile) {
- const frameNumber = traversal.frameNumber;
if (!tile.isRenderable(frameNumber)) {
return;
}
@@ -2594,8 +2743,12 @@ function debugDraw(that, frameState) {
/**
* The default custom shader used by the primitive.
- * @private
+ *
* @type {CustomShader}
+ * @constant
+ * @readonly
+ *
+ * @private
*/
VoxelPrimitive.DefaultCustomShader = new CustomShader({
fragmentShaderText: `void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material)
diff --git a/Source/Scene/VoxelProvider.js b/Source/Scene/VoxelProvider.js
index ece761b2dce..d4f59eeb3c3 100644
--- a/Source/Scene/VoxelProvider.js
+++ b/Source/Scene/VoxelProvider.js
@@ -7,12 +7,12 @@ import DeveloperError from "../Core/DeveloperError.js";
* @alias VoxelProvider
* @constructor
*
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
* @see Cesium3DTilesVoxelProvider
* @see GltfVoxelProvider
* @see VoxelPrimitive
* @see VoxelShapeType
+ *
+ * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
function VoxelProvider() {
DeveloperError.throwInstantiationError();
@@ -21,6 +21,8 @@ function VoxelProvider() {
Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets a value indicating whether or not the provider is ready for use.
+ *
+ * @memberof VoxelProvider.prototype
* @type {Boolean}
* @readonly
*/
@@ -30,6 +32,8 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the promise that will be resolved when the provider is ready for use.
+ *
+ * @memberof VoxelProvider.prototype
* @type {Promise.}
* @readonly
*/
@@ -39,7 +43,9 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* A model matrix that is applied to all tiles. If undefined, the identity matrix will be used instead.
- * @type {Matrix4}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Matrix4|undefined}
* @readonly
*/
modelMatrix: {
@@ -49,6 +55,8 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the {@link VoxelShapeType}
* This should not be called before {@link VoxelProvider#ready} returns true.
+ *
+ * @memberof VoxelProvider.prototype
* @type {VoxelShapeType}
* @readonly
*/
@@ -60,7 +68,9 @@ Object.defineProperties(VoxelProvider.prototype, {
* Gets the minimum bounds.
* If undefined, the shape's default minimum bounds will be used instead.
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Cartesian3|undefined}
* @readonly
*/
minBounds: {
@@ -71,7 +81,9 @@ Object.defineProperties(VoxelProvider.prototype, {
* Gets the maximum bounds.
* If undefined, the shape's default maximum bounds will be used instead.
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Cartesian3|undefined}
* @readonly
*/
maxBounds: {
@@ -81,6 +93,8 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the number of voxels per dimension of a tile. This is the same for all tiles in the dataset.
* This should not be called before {@link VoxelProvider#ready} returns true.
+ *
+ * @memberof VoxelProvider.prototype
* @type {Cartesian3}
* @readonly
*/
@@ -91,7 +105,9 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the number of padding voxels before the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage. If
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Cartesian3|undefined}
* @readonly
*/
paddingBefore: {
@@ -101,7 +117,9 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the number of padding voxels after the tile. This improves rendering quality when sampling the edge of a tile, but it increases memory usage. If
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Cartesian3}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Cartesian3|undefined}
* @readonly
*/
paddingAfter: {
@@ -111,7 +129,10 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the metadata names.
* This should not be called before {@link VoxelProvider#ready} returns true.
+ *
+ * @memberof VoxelProvider.prototype
* @type {String[]}
+ * @readonly
*/
names: {
get: DeveloperError.throwInstantiationError,
@@ -120,7 +141,10 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the metadata types
* This should not be called before {@link VoxelProvider#ready} returns true.
+ *
+ * @memberof VoxelProvider.prototype
* @type {MetadataType[]}
+ * @readonly
*/
types: {
get: DeveloperError.throwInstantiationError,
@@ -129,7 +153,10 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the metadata component types
* This should not be called before {@link VoxelProvider#ready} returns true.
+ *
+ * @memberof VoxelProvider.prototype
* @type {MetadataComponentType[]}
+ * @readonly
*/
componentTypes: {
get: DeveloperError.throwInstantiationError,
@@ -137,7 +164,10 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the metadata minimum values.
- * @type {Number[]}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Number[][]|undefined}
+ * @readonly
*/
minimumValues: {
get: DeveloperError.throwInstantiationError,
@@ -145,7 +175,10 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* Gets the metadata maximum values.
- * @type {Number[]}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Number[][]|undefined}
+ * @readonly
*/
maximumValues: {
get: DeveloperError.throwInstantiationError,
@@ -154,28 +187,20 @@ Object.defineProperties(VoxelProvider.prototype, {
/**
* The maximum number of tiles that exist for this provider. This value is used as a hint to the voxel renderer to allocate an appropriate amount of GPU memory. If this value is not known it can be undefined.
* This should not be called before {@link VoxelProvider#ready} returns true.
- * @type {Number}
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Number|undefined}
+ * @readonly
*/
maximumTileCount: {
get: DeveloperError.throwInstantiationError,
},
});
-/**
- * A hook to update the provider every frame, called from {@link VoxelPrimitive.update}.
- * If the provider doesn't need this functionality it should leave this function undefined.
- * @function
- * @param {FrameState} frameState
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- */
-VoxelProvider.prototype.update = DeveloperError.throwInstantiationError;
-
/**
* Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z.
* This function should not be called before {@link VoxelProvider#ready} returns true.
- * @function
+ *
* @param {Object} [options] Object with the following properties:
* @param {Number} [options.tileLevel=0] The tile's level.
* @param {Number} [options.tileX=0] The tile's X coordinate.
@@ -183,9 +208,16 @@ VoxelProvider.prototype.update = DeveloperError.throwInstantiationError;
* @param {Number} [options.tileZ=0] The tile's Z coordinate.
* @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
*
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
+ * @exception {DeveloperError} The provider must be ready.
*/
VoxelProvider.prototype.requestData = DeveloperError.throwInstantiationError;
+/**
+ * A hook to update the provider every frame, called from {@link VoxelPrimitive.update}.
+ * If the provider doesn't need this functionality it should leave this function undefined.
+ *
+ * @param {FrameState} frameState
+ */
+VoxelProvider.prototype.update = DeveloperError.throwInstantiationError;
+
export default VoxelProvider;
diff --git a/Source/Scene/VoxelShape.js b/Source/Scene/VoxelShape.js
index 6588258a7db..0264d5a3b8b 100644
--- a/Source/Scene/VoxelShape.js
+++ b/Source/Scene/VoxelShape.js
@@ -7,13 +7,12 @@ import DeveloperError from "../Core/DeveloperError.js";
* @alias VoxelShape
* @constructor
*
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
- *
* @see VoxelBoxShape
* @see VoxelEllipsoidShape
* @see VoxelCylinderShape
* @see VoxelShapeType
+ *
+ * @private
*/
function VoxelShape() {
DeveloperError.throwInstantiationError();
@@ -23,7 +22,10 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* An oriented bounding box containing the bounded shape.
* The update function must be called before accessing this value.
+ *
+ * @memberof VoxelShape.prototype
* @type {OrientedBoundingBox}
+ * @readonly
*/
orientedBoundingBox: {
get: DeveloperError.throwInstantiationError,
@@ -32,7 +34,10 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* A bounding sphere containing the bounded shape.
* The update function must be called before accessing this value.
+ *
+ * @memberof VoxelShape.prototype
* @type {BoundingSphere}
+ * @readonly
*/
boundingSphere: {
get: DeveloperError.throwInstantiationError,
@@ -41,7 +46,10 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* A transformation matrix containing the bounded shape.
* The update function must be called before accessing this value.
+ *
+ * @memberof VoxelShape.prototype
* @type {Matrix4}
+ * @readonly
*/
boundTransform: {
get: DeveloperError.throwInstantiationError,
@@ -50,7 +58,10 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* A transformation matrix containing the shape, ignoring the bounds.
* The update function must be called before accessing this value.
+ *
+ * @memberof VoxelShape.prototype
* @type {Matrix4}
+ * @readonly
*/
shapeTransform: {
get: DeveloperError.throwInstantiationError,
@@ -59,7 +70,10 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
* The update function must be called before accessing this value.
+ *
+ * @memberof VoxelShape.prototype
* @type {Boolean}
+ * @readonly
*/
isVisible: {
get: DeveloperError.throwInstantiationError,
@@ -68,29 +82,23 @@ Object.defineProperties(VoxelShape.prototype, {
/**
* Update the shape's state.
- * @function
+ *
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelShape.prototype.update = DeveloperError.throwInstantiationError;
/**
* Computes an oriented bounding box for a specified tile.
* The update function must be called before calling this function.
- * @function
+ *
* @param {Number} tileLevel The tile's level.
* @param {Number} tileX The tile's x coordinate.
* @param {Number} tileY The tile's y coordinate.
* @param {Number} tileZ The tile's z coordinate.
* @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile.
* @returns {OrientedBoundingBox} The oriented bounding box.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelShape.prototype.computeOrientedBoundingBoxForTile =
DeveloperError.throwInstantiationError;
@@ -98,31 +106,32 @@ VoxelShape.prototype.computeOrientedBoundingBoxForTile =
/**
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
- * @function
+ *
* @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
* @returns {Number} The step size.
- *
- * @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelShape.prototype.computeApproximateStepSize =
DeveloperError.throwInstantiationError;
/**
- * Defines the minimum bounds of the shape. This can vary per-shape.
+ * Defines the minimum bounds of the shape. The meaning can vary per-shape.
+ *
* @type {Cartesian3}
+ * @constant
+ * @readonly
*
* @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelShape.DefaultMinBounds = DeveloperError.throwInstantiationError;
/**
- * Defines the maximum bounds of the shape. This can vary per-shape.
+ * Defines the maximum bounds of the shape. The meaning can vary per-shape.
+ *
* @type {Cartesian3}
+ * @constant
+ * @readonly
*
* @private
- * @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
VoxelShape.DefaultMaxBounds = DeveloperError.throwInstantiationError;
diff --git a/Source/Scene/VoxelShapeType.js b/Source/Scene/VoxelShapeType.js
index fa256157039..4e11072bd04 100644
--- a/Source/Scene/VoxelShapeType.js
+++ b/Source/Scene/VoxelShapeType.js
@@ -8,8 +8,8 @@ import VoxelEllipsoidShape from "./VoxelEllipsoidShape.js";
* An enum of voxel shapes supported by EXT_primitive_voxels
. The shape controls
* how the voxel grid is mapped to 3D space.
*
- * @namespace
* @enum VoxelShapeType
+ *
* @experimental This feature is not final and is subject to change without Cesium's standard deprecation policy.
*/
const VoxelShapeType = {
@@ -82,8 +82,10 @@ VoxelShapeType.getMaxBounds = function (shapeType) {
/**
* Converts a primitive type to a voxel shape. glTF voxel primitive types are
* defined by EXT_primitive_voxels.
+ *
* @param {PrimitiveType} primitiveType The primitive type.
* @returns {VoxelShapeType} The shape type.
+ *
* @private
*/
VoxelShapeType.fromPrimitiveType = function (primitiveType) {
@@ -105,8 +107,10 @@ VoxelShapeType.fromPrimitiveType = function (primitiveType) {
* Converts a shape type to a constructor that can be used to create a shape
* object or get per-shape properties like DefaultMinBounds and
* DefaultMaxBounds.
+ *
* @param {VoxelShapeType} shapeType The shape type.
* @returns {Function} The shape's constructor.
+ *
* @private
*/
VoxelShapeType.getShapeConstructor = function (shapeType) {
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index 320b75538ad..d99e412cf1a 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -26,6 +26,7 @@ import MetadataComponentType from "./MetadataComponentType.js";
/**
* Handles tileset traversal, tile requests, and GPU resources. Intended to be
* private and paired with a {@link VoxelPrimitive}, which has a user-facing API.
+ *
* @alias VoxelTraversal
* @constructor
*
@@ -49,16 +50,17 @@ function VoxelTraversal(
maximumTextureMemoryByteLength
) {
/**
- * @private
+ * TODO: maybe this shouldn't be stored?
* @type {VoxelPrimitive}
+ * @private
*/
- this.primitive = primitive;
+ this._primitive = primitive;
const length = types.length;
/**
- * @private
* @type {Megatexture[]}
+ * @readonly
*/
this.megatextures = new Array(length);
@@ -80,16 +82,16 @@ function VoxelTraversal(
const maximumTileCount = this.megatextures[0].maximumTileCount;
/**
- * @private
* @type {Number}
+ * @private
*/
- this.simultaneousRequestCount = 0;
+ this._simultaneousRequestCount = 0;
/**
- * @private
* @type {Boolean}
+ * @private
*/
- this.debugPrint = false;
+ this._debugPrint = false;
const shape = primitive._shape;
const rootLevel = 0;
@@ -99,8 +101,8 @@ function VoxelTraversal(
const rootParent = undefined;
/**
- * @private
* @type {SpatialNode}
+ * @readonly
*/
this.rootNode = new SpatialNode(
rootLevel,
@@ -113,43 +115,43 @@ function VoxelTraversal(
);
/**
- * @private
* @type {DoubleEndedPriorityQueue}
+ * @private
*/
- this.priorityQueue = new DoubleEndedPriorityQueue({
+ this._priorityQueue = new DoubleEndedPriorityQueue({
maximumLength: maximumTileCount,
comparator: KeyframeNode.priorityComparator,
});
/**
- * @private
* @type {KeyframeNode[]}
+ * @private
*/
- this.highPriorityKeyframeNodes = new Array(maximumTileCount);
+ this._highPriorityKeyframeNodes = new Array(maximumTileCount);
/**
- * @private
* @type {KeyframeNode[]}
+ * @private
*/
- this.keyframeNodesInMegatexture = new Array(maximumTileCount);
+ this._keyframeNodesInMegatexture = new Array(maximumTileCount);
/**
- * @private
* @type {Number}
+ * @private
*/
- this.keyframeCount = keyframeCount;
+ this._keyframeCount = keyframeCount;
/**
- * @private
* @type {Number}
+ * @private
*/
- this.keyframeLocation = 0;
+ this._keyframeLocation = 0;
/**
+ * @type {Number[]}
* @private
- * @type {Number}
*/
- this.frameNumber = -1;
+ this._binaryTreeKeyframeWeighting = new Array(keyframeCount);
function binaryTreeWeightingRecursive(arr, start, end, depth) {
if (start > end) {
@@ -160,11 +162,12 @@ function VoxelTraversal(
binaryTreeWeightingRecursive(arr, start, mid - 1, depth + 1);
binaryTreeWeightingRecursive(arr, mid + 1, end, depth + 1);
}
- this.binaryTreeKeyframeWeighting = new Array(keyframeCount);
- this.binaryTreeKeyframeWeighting[0] = 0;
- this.binaryTreeKeyframeWeighting[keyframeCount - 1] = 0;
+
+ const binaryTreeKeyframeWeighting = this._binaryTreeKeyframeWeighting;
+ binaryTreeKeyframeWeighting[0] = 0;
+ binaryTreeKeyframeWeighting[keyframeCount - 1] = 0;
binaryTreeWeightingRecursive(
- this.binaryTreeKeyframeWeighting,
+ binaryTreeKeyframeWeighting,
1,
keyframeCount - 2,
0
@@ -179,6 +182,10 @@ function VoxelTraversal(
maximumTileCount / internalNodeTilesPerRow
);
+ /**
+ * @type {Texture}
+ * @readonly
+ */
this.internalNodeTexture = new Texture({
context: context,
pixelFormat: PixelFormat.RGBA,
@@ -191,14 +198,48 @@ function VoxelTraversal(
magnificationFilter: TextureMagnificationFilter.NEAREST,
}),
});
+
+ /**
+ * @type {Number}
+ * @readonly
+ */
+ this.internalNodeTilesPerRow = internalNodeTilesPerRow;
+
+ /**
+ * @type {Cartesian2}
+ * @readonly
+ */
this.internalNodeTexelSizeUv = new Cartesian2(
1.0 / internalNodeTextureDimensionX,
1.0 / internalNodeTextureDimensionY
);
- this.internalNodeTilesPerRow = internalNodeTilesPerRow;
+ /**
+ * @type {Boolean}
+ * @readonly
+ */
this.useLeafNodeTexture = keyframeCount > 1;
- if (this.useLeafNodeTexture) {
+
+ /**
+ * @type {Texture|undefined}
+ * @readonly
+ */
+ this.leafNodeTexture = undefined;
+
+ /**
+ * @type {Number|undefined}
+ * @readonly
+ */
+ this.leafNodeTilesPerRow = undefined;
+
+ /**
+ * @type {Cartesian2|undefined}
+ * @readonly
+ */
+ this.leafNodeTexelSizeUv = undefined;
+
+ const useLeafNodeTexture = this._useLeafNodeTexture;
+ if (useLeafNodeTexture) {
const leafNodeTexelCount = 2;
const leafNodeTextureDimensionX = 1024;
const leafNodeTilesPerRow = Math.floor(
@@ -225,21 +266,17 @@ function VoxelTraversal(
1.0 / leafNodeTextureDimensionY
);
this.leafNodeTilesPerRow = leafNodeTilesPerRow;
- } else {
- this.leafNodeTexture = undefined;
- this.leafNodeTexelSizeUv = undefined;
- this.leafNodeTilesPerRow = undefined;
}
}
VoxelTraversal.simultaneousRequestCountMaximum = 50;
/**
- * @private
* @param {FrameState} frameState
* @param {Number} keyframeLocation
* @param {Boolean} recomputeBoundingVolumes
* @param {Boolean} pauseUpdate
+ * @returns {Boolean} True if the voxel grid has any loaded data.
*/
VoxelTraversal.prototype.update = function (
frameState,
@@ -247,51 +284,99 @@ VoxelTraversal.prototype.update = function (
recomputeBoundingVolumes,
pauseUpdate
) {
- const that = this;
-
- const keyframeCount = that.keyframeCount;
- that.keyframeLocation = CesiumMath.clamp(
+ const keyframeCount = this._keyframeCount;
+ this._keyframeLocation = CesiumMath.clamp(
keyframeLocation,
0.0,
keyframeCount - 1
);
if (recomputeBoundingVolumes) {
- recomputeBoundingVolumesRecursive(that, that.rootNode);
+ recomputeBoundingVolumesRecursive(this, this.rootNode);
}
if (!pauseUpdate) {
- that.frameNumber = frameState.frameNumber;
-
const timestamp0 = getTimestamp();
- loadAndUnload(that, frameState);
+ loadAndUnload(this, frameState);
const timestamp1 = getTimestamp();
- generateOctree(that);
+ generateOctree(this, frameState);
const timestamp2 = getTimestamp();
- const debugStatistics = that.debugPrint;
+ const debugStatistics = this._debugPrint;
if (debugStatistics) {
const loadAndUnloadTimeMs = timestamp1 - timestamp0;
const generateOctreeTimeMs = timestamp2 - timestamp1;
const totalTimeMs = timestamp2 - timestamp0;
printDebugInformation(
- that,
+ this,
loadAndUnloadTimeMs,
generateOctreeTimeMs,
totalTimeMs
);
}
}
+
+ const rootNode = this.rootNode;
+ const frameNumber = frameState.frameNumber;
+ return rootNode.isRenderable(frameNumber);
+};
+
+/**
+ * Returns true if this object was destroyed; otherwise, false.
+ *
+ * If this object was destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception.
+ *
+ * @returns {Boolean} true
if this object was destroyed; otherwise, false
.
+ *
+ * @see VoxelTraversal#destroy
+ */
+VoxelTraversal.prototype.isDestroyed = function () {
+ return false;
};
/**
- * @ignore
+ * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
+ * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
+ *
+ * Once an object is destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
+ * assign the return value (undefined
) to the object as done in the example.
+ *
+ * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
+ *
+ * @see VoxelTraversal#isDestroyed
+ *
+ * @example
+ * voxelTraversal = voxelTraversal && voxelTraversal.destroy();
+ */
+VoxelTraversal.prototype.destroy = function () {
+ const megatextures = this.megatextures;
+ const megatextureLength = megatextures.length;
+ for (let i = 0; i < megatextureLength; i++) {
+ megatextures[i] = megatextures[i] && megatextures[i].destroy();
+ }
+
+ this.internalNodeTexture =
+ this.internalNodeTexture && this.internalNodeTexture.destroy();
+
+ this.leafNodeTexture = this.leafNodeTexture && this.leafNodeTexture.destroy();
+
+ return destroyObject(this);
+};
+
+/**
+ * @function
+ *
* @param {VoxelTraversal} that
* @param {SpatialNode} node
+ *
+ * @private
*/
function recomputeBoundingVolumesRecursive(that, node) {
- const shape = that.primitive._shape;
- const dimensions = that.primitive._provider.dimensions;
+ const primitive = that._primitive;
+ const shape = primitive._shape;
+ const dimensions = primitive._provider.dimensions;
node.computeBoundingVolumes(shape, dimensions);
if (defined(node.children)) {
for (let i = 0; i < 8; i++) {
@@ -303,9 +388,13 @@ function recomputeBoundingVolumesRecursive(that, node) {
/**
* Call requestData for each metadata
- * @ignore
+ *
+ * @function
+ *
* @param {VoxelTraversal} that
* @param {KeyframeNode} keyframeNode
+ *
+ * @private
*/
function requestTiles(that, keyframeNode) {
const keys = Object.keys(that.megatextures);
@@ -316,20 +405,23 @@ function requestTiles(that, keyframeNode) {
}
}
/**
- * @ignore
+ * @function
+ *
* @param {VoxelTraversal} that
* @param {KeyframeNode} keyframeNode
* @param {String} metadataName
+ *
+ * @private
*/
function requestData(that, keyframeNode, metadataName) {
if (
- that.simultaneousRequestCount >=
+ that._simultaneousRequestCount >=
VoxelTraversal.simultaneousRequestCountMaximum
) {
return;
}
- const primitive = that.primitive;
+ const primitive = that._primitive;
const provider = primitive._provider;
const keyframe = keyframeNode.keyframe;
const spatialNode = keyframeNode.spatialNode;
@@ -339,7 +431,7 @@ function requestData(that, keyframeNode, metadataName) {
const tileZ = spatialNode.z;
const postRequestSuccess = function (result) {
- that.simultaneousRequestCount--;
+ that._simultaneousRequestCount--;
const length = primitive._provider.types.length;
if (!defined(result)) {
@@ -373,7 +465,7 @@ function requestData(that, keyframeNode, metadataName) {
};
const postRequestFailure = function () {
- that.simultaneousRequestCount--;
+ that._simultaneousRequestCount--;
keyframeNode.state = VoxelTraversal.LoadState.FAILED;
};
@@ -387,7 +479,7 @@ function requestData(that, keyframeNode, metadataName) {
});
if (defined(promise)) {
- that.simultaneousRequestCount++;
+ that._simultaneousRequestCount++;
keyframeNode.state = VoxelTraversal.LoadState.RECEIVING;
promise.then(postRequestSuccess).catch(postRequestFailure);
} else {
@@ -396,28 +488,34 @@ function requestData(that, keyframeNode, metadataName) {
}
/**
- * @ignore
+ * @function
+ *
* @param {Number} x
* @returns {Number}
+ *
+ * @private
*/
function mapInfiniteRangeToZeroOne(x) {
return x / (1.0 + x);
}
/**
- * @ignore
+ * @function
+ *
* @param {VoxelTraversal} that
* @param {FrameState} frameState
+ *
+ * @private
*/
function loadAndUnload(that, frameState) {
- const frameNumber = that.frameNumber;
- const primitive = that.primitive;
+ const frameNumber = frameState.frameNumber;
+ const primitive = that._primitive;
const shape = primitive._shape;
const voxelDimensions = primitive._provider.dimensions;
const targetScreenSpaceError = primitive._screenSpaceError;
- const priorityQueue = that.priorityQueue;
- const keyframeLocation = that.keyframeLocation;
- const keyframeCount = that.keyframeCount;
+ const priorityQueue = that._priorityQueue;
+ const keyframeLocation = that._keyframeLocation;
+ const keyframeCount = that._keyframeCount;
const rootNode = that.rootNode;
const cameraPosition = frameState.camera.positionWC;
@@ -511,7 +609,7 @@ function loadAndUnload(that, frameState) {
4.0
);
const binaryTreeFactor = Math.exp(
- -that.binaryTreeKeyframeWeighting[keyframe]
+ -that._binaryTreeKeyframeWeighting[keyframe]
);
keyframeNode.priority = 10.0 * ssePriority;
keyframeNode.priority += CesiumMath.lerp(
@@ -632,7 +730,7 @@ function loadAndUnload(that, frameState) {
priorityQueue.reset();
addToQueueRecursive(rootNode, CullingVolume.MASK_INDETERMINATE);
- const highPriorityKeyframeNodes = that.highPriorityKeyframeNodes;
+ const highPriorityKeyframeNodes = that._highPriorityKeyframeNodes;
let highPriorityKeyframeNodeCount = 0;
let highPriorityKeyframeNode;
while (priorityQueue.length > 0) {
@@ -644,7 +742,7 @@ function loadAndUnload(that, frameState) {
highPriorityKeyframeNodeCount++;
}
- const keyframeNodesInMegatexture = that.keyframeNodesInMegatexture;
+ const keyframeNodesInMegatexture = that._keyframeNodesInMegatexture;
// TODO: some of the megatexture state should be stored once, not duplicate for each megatexture
const megatexture = that.megatextures[0];
const keyframeNodesInMegatextureCount = megatexture.occupiedCount;
@@ -704,8 +802,11 @@ function loadAndUnload(that, frameState) {
}
/**
- * @ignore
+ * @function
+ *
* @param {VoxelTraversal} that
+ *
+ * @private
*/
function printDebugInformation(
that,
@@ -713,7 +814,7 @@ function printDebugInformation(
generateOctreeTimeMs,
totalTimeMs
) {
- const keyframeCount = that.keyframeCount;
+ const keyframeCount = that._keyframeCount;
const rootNode = that.rootNode;
const loadStateCount = Object.keys(VoxelTraversal.LoadState).length;
@@ -802,14 +903,17 @@ VoxelTraversal.LoadState = {
};
/**
- * @ignore
+ * @alias SpatialNode
* @constructor
+ *
* @param {Number} level
* @param {Number} x
* @param {Number} y
* @param {Number} z
* @param {SpatialNode} parent
* @param {VoxelShapeType} shape
+ *
+ * @private
*/
function SpatialNode(level, x, y, z, parent, shape, voxelDimensions) {
/**
@@ -855,6 +959,11 @@ function SpatialNode(level, x, y, z, parent, shape, voxelDimensions) {
this.computeBoundingVolumes(shape, voxelDimensions);
}
+/**
+ * @param {SpatialNode} a
+ * @param {SpatialNode} b
+ * @returns {Boolean}
+ */
SpatialNode.spatialComparator = function (a, b) {
// The higher of the two screen space errors is prioritized
return b.screenSpaceError - a.screenSpaceError;
@@ -863,7 +972,6 @@ SpatialNode.spatialComparator = function (a, b) {
const scratchObbHalfScale = new Cartesian3();
/**
- * @ignore
* @param {VoxelShape} shape
* @param {Cartesian3} voxelDimensions
*/
@@ -889,20 +997,17 @@ SpatialNode.prototype.computeBoundingVolumes = function (
};
/**
- * @ignore
* @param {FrameState} frameState
* @param {Number} visibilityPlaneMask
* @returns {Number} A plane mask as described in {@link CullingVolume#computeVisibilityWithPlaneMask}.
*/
SpatialNode.prototype.visibility = function (frameState, visibilityPlaneMask) {
- const that = this;
- const obb = that.orientedBoundingBox;
+ const obb = this.orientedBoundingBox;
const cullingVolume = frameState.cullingVolume;
return cullingVolume.computeVisibilityWithPlaneMask(obb, visibilityPlaneMask);
};
/**
- * @ignore
* @param {Cartesian3} cameraPosition
* @param {Number} screenSpaceErrorMultiplier
*/
@@ -910,15 +1015,14 @@ SpatialNode.prototype.computeScreenSpaceError = function (
cameraPosition,
screenSpaceErrorMultiplier
) {
- const that = this;
- const obb = that.orientedBoundingBox;
+ const obb = this.orientedBoundingBox;
let distance = Math.sqrt(obb.distanceSquaredTo(cameraPosition));
// Avoid divide-by-zero when viewer is inside the tile.
distance = Math.max(distance, CesiumMath.EPSILON7);
- const approximateVoxelSize = that.approximateVoxelSize;
+ const approximateVoxelSize = this.approximateVoxelSize;
const error = screenSpaceErrorMultiplier * (approximateVoxelSize / distance);
- that.screenSpaceError = error;
+ this.screenSpaceError = error;
};
// This object imitates a KeyframeNode. Only used for binary search function.
@@ -928,13 +1032,12 @@ const scratchBinarySearchKeyframeNode = {
/**
* Finds the index of the keyframe if it exists, or the complement (~) of the index where it would be in the sorted array.
- * @ignore
+ *
* @param {Number} keyframe
* @returns {Number}
*/
SpatialNode.prototype.findKeyframeIndex = function (keyframe) {
- const that = this;
- const keyframeNodes = that.keyframeNodes;
+ const keyframeNodes = this.keyframeNodes;
scratchBinarySearchKeyframeNode.keyframe = keyframe;
const index = binarySearch(
keyframeNodes,
@@ -943,15 +1046,15 @@ SpatialNode.prototype.findKeyframeIndex = function (keyframe) {
);
return index;
};
+
/**
* Finds the index of the renderable keyframe if it exists, or the complement (~) of the index where it would be in the sorted array.
- * @ignore
+ *
* @param {Number} keyframe
* @returns {Number}
*/
SpatialNode.prototype.findRenderableKeyframeIndex = function (keyframe) {
- const that = this;
- const renderableKeyframeNodes = that.renderableKeyframeNodes;
+ const renderableKeyframeNodes = this.renderableKeyframeNodes;
scratchBinarySearchKeyframeNode.keyframe = keyframe;
const index = binarySearch(
renderableKeyframeNodes,
@@ -963,15 +1066,13 @@ SpatialNode.prototype.findRenderableKeyframeIndex = function (keyframe) {
/**
* Computes the most suitable keyframes for rendering, balancing between temporal and visual quality.
- * @ignore
+ *
* @param {Number} keyframeLocation
*/
SpatialNode.prototype.computeSurroundingRenderableKeyframeNodes = function (
keyframeLocation
) {
- const that = this;
-
- let spatialNode = that;
+ let spatialNode = this;
const startLevel = spatialNode.level;
const targetKeyframePrev = Math.floor(keyframeLocation);
@@ -1058,12 +1159,12 @@ SpatialNode.prototype.computeSurroundingRenderableKeyframeNodes = function (
spatialNode = spatialNode.parent;
}
- that.renderableKeyframeNodePrevious = bestKeyframeNodePrev;
- that.renderableKeyframeNodeNext = bestKeyframeNodeNext;
+ this.renderableKeyframeNodePrevious = bestKeyframeNodePrev;
+ this.renderableKeyframeNodeNext = bestKeyframeNodeNext;
if (defined(bestKeyframeNodePrev) && defined(bestKeyframeNodeNext)) {
const bestKeyframePrev = bestKeyframeNodePrev.keyframe;
const bestKeyframeNext = bestKeyframeNodeNext.keyframe;
- that.renderableKeyframeNodeLerp =
+ this.renderableKeyframeNodeLerp =
bestKeyframePrev === bestKeyframeNext
? 0.0
: CesiumMath.clamp(
@@ -1076,30 +1177,25 @@ SpatialNode.prototype.computeSurroundingRenderableKeyframeNodes = function (
};
/**
- * @ignore
* @param {Number} frameNumber
* @returns {Boolean}
*/
SpatialNode.prototype.isVisited = function (frameNumber) {
- const that = this;
- return that.visitedFrameNumber === frameNumber;
+ return this.visitedFrameNumber === frameNumber;
};
/**
- * @ignore
* @param {Number} keyframe
*/
SpatialNode.prototype.createKeyframeNode = function (keyframe) {
- const that = this;
- let index = that.findKeyframeIndex(keyframe);
+ let index = this.findKeyframeIndex(keyframe);
if (index < 0) {
index = ~index; // convert to insertion index
- const keyframeNode = new KeyframeNode(that, keyframe);
- that.keyframeNodes.splice(index, 0, keyframeNode);
+ const keyframeNode = new KeyframeNode(this, keyframe);
+ this.keyframeNodes.splice(index, 0, keyframeNode);
}
};
/**
- * @ignore
* @param {KeyframeNode} keyframeNode
* @param {Megatexture} megatexture
*/
@@ -1107,15 +1203,13 @@ SpatialNode.prototype.destroyKeyframeNode = function (
keyframeNode,
megatextures
) {
- const that = this;
-
const keyframe = keyframeNode.keyframe;
- const keyframeIndex = that.findKeyframeIndex(keyframe);
+ const keyframeIndex = this.findKeyframeIndex(keyframe);
if (keyframeIndex < 0) {
throw new DeveloperError("Keyframe node does not exist.");
}
- const keyframeNodes = that.keyframeNodes;
+ const keyframeNodes = this.keyframeNodes;
keyframeNodes.splice(keyframeIndex, 1);
if (keyframeNode.megatextureIndex !== -1) {
@@ -1127,14 +1221,14 @@ SpatialNode.prototype.destroyKeyframeNode = function (
megatextureArray[i].remove(keyframeNode.megatextureIndex);
}
- const renderableKeyframeNodeIndex = that.findRenderableKeyframeIndex(
+ const renderableKeyframeNodeIndex = this.findRenderableKeyframeIndex(
keyframe
);
if (renderableKeyframeNodeIndex < 0) {
throw new DeveloperError("Renderable keyframe node does not exist.");
}
- const renderableKeyframeNodes = that.renderableKeyframeNodes;
+ const renderableKeyframeNodes = this.renderableKeyframeNodes;
renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 1);
}
@@ -1146,6 +1240,10 @@ SpatialNode.prototype.destroyKeyframeNode = function (
keyframeNode.highPriorityFrameNumber = -1;
};
+/**
+ * @param {KeyframeNode} keyframeNode
+ * @param {Megatexture[]} megatextures
+ */
SpatialNode.prototype.addKeyframeNodeToMegatextures = function (
keyframeNode,
megatextures
@@ -1177,8 +1275,8 @@ SpatialNode.prototype.addKeyframeNodeToMegatextures = function (
renderableKeyframeNodeIndex = ~renderableKeyframeNodeIndex;
renderableKeyframeNodes.splice(renderableKeyframeNodeIndex, 0, keyframeNode);
};
+
/**
- * @ignore
* @param {KeyframeNode} keyframeNode
* @param {Megatexture} megatexture
*/
@@ -1186,8 +1284,6 @@ SpatialNode.prototype.addKeyframeNodeToMegatexture = function (
keyframeNode,
megatexture
) {
- const that = this;
-
if (
keyframeNode.state !== VoxelTraversal.LoadState.RECEIVED ||
keyframeNode.megatextureIndex !== -1 ||
@@ -1202,8 +1298,8 @@ SpatialNode.prototype.addKeyframeNodeToMegatexture = function (
keyframeNode.metadatas[megatexture.metadataName] = undefined; // data is in megatexture so no need to hold onto it
keyframeNode.state = VoxelTraversal.LoadState.LOADED;
- const renderableKeyframeNodes = that.renderableKeyframeNodes;
- let renderableKeyframeNodeIndex = that.findRenderableKeyframeIndex(
+ const renderableKeyframeNodes = this.renderableKeyframeNodes;
+ let renderableKeyframeNodeIndex = this.findRenderableKeyframeIndex(
keyframeNode.keyframe
);
if (renderableKeyframeNodeIndex >= 0) {
@@ -1214,30 +1310,30 @@ SpatialNode.prototype.addKeyframeNodeToMegatexture = function (
};
/**
- * @ignore
* @param {Number} frameNumber
*/
SpatialNode.prototype.isRenderable = function (frameNumber) {
- const that = this;
-
- const previousNode = that.renderableKeyframeNodePrevious;
- const nextNode = that.renderableKeyframeNodeNext;
- const level = that.level;
+ const previousNode = this.renderableKeyframeNodePrevious;
+ const nextNode = this.renderableKeyframeNodeNext;
+ const level = this.level;
return (
defined(previousNode) &&
defined(nextNode) &&
(previousNode.spatialNode.level === level ||
nextNode.spatialNode.level === level) &&
- that.visitedFrameNumber === frameNumber
+ this.visitedFrameNumber === frameNumber
);
};
/**
- * @ignore
+ * @alias KeyframeNode
* @constructor
+ *
* @param {SpatialNode} spatialNode
* @param {Number} keyframe
+ *
+ * @private
*/
function KeyframeNode(spatialNode, keyframe) {
this.spatialNode = spatialNode;
@@ -1250,15 +1346,14 @@ function KeyframeNode(spatialNode, keyframe) {
}
/**
- * @ignore
* @param {KeyframeNode} a
* @param {KeyframeNode} b
*/
KeyframeNode.priorityComparator = function (a, b) {
return a.priority - b.priority;
};
+
/**
- * @ignore
* @param {KeyframeNode} a
* @param {KeyframeNode} b
*/
@@ -1302,13 +1397,15 @@ const GpuOctreeFlag = {
};
/**
- * @ignore
+ * @function
+ *
* @param {VoxelTraversal} that
+ * @param {FrameState} frameState
*/
-function generateOctree(that) {
- const keyframeLocation = that.keyframeLocation;
- const useLeafNodes = that.useLeafNodeTexture;
- const frameNumber = that.frameNumber;
+function generateOctree(that, frameState) {
+ const keyframeLocation = that._keyframeLocation;
+ const useLeafNodes = that._useLeafNodeTexture;
+ const frameNumber = frameState.frameNumber;
let internalNodeCount = 0;
let leafNodeCount = 0;
@@ -1523,7 +1620,6 @@ function generateOctree(that) {
}
/**
- * @private
* @param {Number} tileCount
* @param {Cartesian3} dimensions
* @param {MetadataType[]} types
@@ -1555,13 +1651,16 @@ VoxelTraversal.getApproximateTextureMemoryByteLength = function (
};
/**
- * @ignore
+ * @alias Megatexture
* @constructor
+ *
* @param {Context} context
* @param {Cartesian3} dimensions
* @param {Number} channelCount
* @param {MetadataComponentType} componentType
* @param {Number} [textureMemoryByteLength]
+ *
+ * @private
*/
function Megatexture(
context,
@@ -1642,32 +1741,32 @@ function Megatexture(
/**
* @type {Number}
- * @private
+ * @readonly
*/
this.channelCount = channelCount;
/**
* @type {MetadataComponentType}
- * @private
+ * @readonly
*/
this.componentType = componentType;
/**
* @type {Cartesian3}
- * @private
+ * @readonly
*/
this.voxelCountPerTile = Cartesian3.clone(dimensions, new Cartesian3());
/**
* @type {Number}
- * @private
+ * @readonly
*/
this.maximumTileCount =
regionCountPerMegatextureX * regionCountPerMegatextureY;
/**
* @type {Cartesian2}
- * @private
+ * @readonly
*/
this.regionCountPerMegatexture = new Cartesian2(
regionCountPerMegatextureX,
@@ -1676,7 +1775,7 @@ function Megatexture(
/**
* @type {Cartesian2}
- * @private
+ * @readonly
*/
this.voxelCountPerRegion = new Cartesian2(
voxelCountPerRegionX,
@@ -1685,7 +1784,7 @@ function Megatexture(
/**
* @type {Cartesian2}
- * @private
+ * @readonly
*/
this.sliceCountPerRegion = new Cartesian2(
sliceCountPerRegionX,
@@ -1694,7 +1793,7 @@ function Megatexture(
/**
* @type {Cartesian2}
- * @private
+ * @readonly
*/
this.voxelSizeUv = new Cartesian2(
1.0 / textureDimension,
@@ -1703,7 +1802,7 @@ function Megatexture(
/**
* @type {Cartesian2}
- * @private
+ * @readonly
*/
this.sliceSizeUv = new Cartesian2(
dimensions.x / textureDimension,
@@ -1712,7 +1811,7 @@ function Megatexture(
/**
* @type {Cartesian2}
- * @private
+ * @readonly
*/
this.regionSizeUv = new Cartesian2(
voxelCountPerRegionX / textureDimension,
@@ -1721,7 +1820,7 @@ function Megatexture(
/**
* @type {Texture}
- * @private
+ * @readonly
*/
this.texture = new Texture({
context: context,
@@ -1739,13 +1838,17 @@ function Megatexture(
});
const ArrayType = MetadataComponentType.toTypedArrayType(componentType);
+
+ /**
+ * @type {Array}
+ */
this.tileVoxelDataTemp = new ArrayType(
voxelCountPerRegionX * voxelCountPerRegionY * channelCount
);
/**
* @type {MegatextureNode[]}
- * @private
+ * @readonly
*/
this.nodes = new Array(this.maximumTileCount);
for (let tileIndex = 0; tileIndex < this.maximumTileCount; tileIndex++) {
@@ -1762,89 +1865,88 @@ function Megatexture(
/**
* @type {MegatextureNode}
- * @private
+ * @readonly
*/
this.occupiedList = undefined;
/**
* @type {MegatextureNode}
- * @private
+ * @readonly
*/
this.emptyList = this.nodes[0];
/**
* @type {Number}
- * @private
+ * @readonly
*/
this.occupiedCount = 0;
}
/**
- * @ignore
+ * @alias MegatextureNode
* @constructor
+ *
* @param {Number} index
+ *
+ * @private
*/
function MegatextureNode(index) {
+ /**
+ * @type {Number}
+ */
this.index = index;
/**
- * @ignore
* @type {MegatextureNode}
*/
this.nextNode = undefined;
/**
- * @ignore
* @type {MegatextureNode}
*/
this.previousNode = undefined;
}
/**
- * @ignore
* @param {Array} data
* @returns {Number}
*/
Megatexture.prototype.add = function (data) {
- const that = this;
-
- if (that.isFull()) {
+ if (this.isFull()) {
throw new DeveloperError("Trying to add when there are no empty spots");
}
// remove head of empty list
- const node = that.emptyList;
- that.emptyList = that.emptyList.nextNode;
- if (defined(that.emptyList)) {
- that.emptyList.previousNode = undefined;
+ const node = this.emptyList;
+ this.emptyList = this.emptyList.nextNode;
+ if (defined(this.emptyList)) {
+ this.emptyList.previousNode = undefined;
}
// make head of occupied list
- node.nextNode = that.occupiedList;
+ node.nextNode = this.occupiedList;
if (defined(node.nextNode)) {
node.nextNode.previousNode = node;
}
- that.occupiedList = node;
+ this.occupiedList = node;
const index = node.index;
- that.writeDataToTexture(index, data);
+ this.writeDataToTexture(index, data);
- that.occupiedCount++;
+ this.occupiedCount++;
return index;
};
/**
- * @ignore
* @param {Number} index
*/
Megatexture.prototype.remove = function (index) {
- const that = this;
- if (index < 0 || index >= that.maximumTileCount) {
+ if (index < 0 || index >= this.maximumTileCount) {
throw new DeveloperError("Megatexture index out of bounds");
}
// remove from list
- const node = that.nodes[index];
+ const node = this.nodes[index];
if (defined(node.previousNode)) {
node.previousNode.nextNode = node.nextNode;
}
@@ -1853,26 +1955,23 @@ Megatexture.prototype.remove = function (index) {
}
// make head of empty list
- node.nextNode = that.emptyList;
+ node.nextNode = this.emptyList;
if (defined(node.nextNode)) {
node.nextNode.previousNode = node;
}
node.previousNode = undefined;
- that.emptyList = node;
- that.occupiedCount--;
+ this.emptyList = node;
+ this.occupiedCount--;
};
/**
- * @ignore
* @returns {Boolean}
*/
Megatexture.prototype.isFull = function () {
- const that = this;
- return that.emptyList === undefined;
+ return this.emptyList === undefined;
};
/**
- * @ignore
* @param {Number} tileCount
* @param {Cartesian3} dimensions
* @param {Number} channelCount number of channels in the metadata. Must be 1 to 4.
@@ -1925,18 +2024,16 @@ Megatexture.getApproximateTextureMemoryByteLength = function (
};
/**
- * @ignore
* @param {Number} index
* @param {Float32Array|Uint16Array|Uint8Array} data
*/
Megatexture.prototype.writeDataToTexture = function (index, data) {
- const that = this;
- const texture = that.texture;
- const channelCount = that.channelCount;
- const regionDimensionsPerMegatexture = that.regionCountPerMegatexture;
- const voxelDimensionsPerRegion = that.voxelCountPerRegion;
- const voxelDimensionsPerTile = that.voxelCountPerTile;
- const sliceDimensionsPerRegion = that.sliceCountPerRegion;
+ const texture = this.texture;
+ const channelCount = this.channelCount;
+ const regionDimensionsPerMegatexture = this.regionCountPerMegatexture;
+ const voxelDimensionsPerRegion = this.voxelCountPerRegion;
+ const voxelDimensionsPerTile = this.voxelCountPerTile;
+ const sliceDimensionsPerRegion = this.sliceCountPerRegion;
let tileData = data;
@@ -1947,8 +2044,8 @@ Megatexture.prototype.writeDataToTexture = function (index, data) {
for (let i = 0; i < elementCount / channelCount; i++) {
for (let channelIndex = 0; channelIndex < channelCount; channelIndex++) {
const dataIndex = i * channelCount + channelIndex;
- const minimumValue = that.minimumValues[channelIndex];
- const maximumValue = that.maximumValues[channelIndex];
+ const minimumValue = this.minimumValues[channelIndex];
+ const maximumValue = this.maximumValues[channelIndex];
// TODO extrema are unnormalized, but we are normalizing to [0, 1] here. what do we want to do? will the user expect to get normalized samples and extrema in the style function?
tileData[dataIndex] =
(data[dataIndex] - minimumValue) / (maximumValue - minimumValue);
@@ -1961,7 +2058,7 @@ Megatexture.prototype.writeDataToTexture = function (index, data) {
}
}
- const tileVoxelData = that.tileVoxelDataTemp;
+ const tileVoxelData = this.tileVoxelDataTemp;
for (let z = 0; z < voxelDimensionsPerTile.z; z++) {
const sliceVoxelOffsetX =
(z % sliceDimensionsPerRegion.x) * voxelDimensionsPerTile.x;
@@ -2007,14 +2104,38 @@ Megatexture.prototype.writeDataToTexture = function (index, data) {
texture.copyFrom(copyOptions);
};
+/**
+ * Returns true if this object was destroyed; otherwise, false.
+ *
+ * If this object was destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception.
+ *
+ * @returns {Boolean} true
if this object was destroyed; otherwise, false
.
+ *
+ * @see Megatexture#destroy
+ */
Megatexture.prototype.isDestroyed = function () {
return false;
};
+/**
+ * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
+ * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
+ *
+ * Once an object is destroyed, it should not be used; calling any function other than
+ * isDestroyed
will result in a {@link DeveloperError} exception. Therefore,
+ * assign the return value (undefined
) to the object as done in the example.
+ *
+ * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
+ *
+ * @see Megatexture#isDestroyed
+ *
+ * @example
+ * megatexture = megatexture && megatexture.destroy();
+ */
Megatexture.prototype.destroy = function () {
- const that = this;
- that.texture = that.texture && that.texture.destroy();
- return destroyObject(that);
+ this.texture = this.texture && this.texture.destroy();
+ return destroyObject(this);
};
export default VoxelTraversal;
diff --git a/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js b/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
index 5eaaf1f26ba..3d62e19ad39 100644
--- a/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
+++ b/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
@@ -90,7 +90,7 @@ describe(
// need to call update until the promise is ready
return pollToPromise(function () {
provider.update(scene.frameState);
- return provider._doneLoading();
+ return provider.doneLoading();
})
.then(function () {
return requestTilePromise;
diff --git a/Specs/Scene/GltfVoxelProviderSpec.js b/Specs/Scene/GltfVoxelProviderSpec.js
index ef9f736648f..adbf9367169 100644
--- a/Specs/Scene/GltfVoxelProviderSpec.js
+++ b/Specs/Scene/GltfVoxelProviderSpec.js
@@ -88,7 +88,7 @@ describe(
// need to call update until the promise is ready
return pollToPromise(function () {
provider.update(scene.frameState);
- return provider._doneLoading();
+ return provider.doneLoading();
})
.then(function () {
return requestTilePromise;
From 52385b5aaba714a86d9377fbdc5e0740c46412e0 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Mon, 4 Apr 2022 20:21:37 -0400
Subject: [PATCH 008/679] getting non bounded ellipsoid working
---
Apps/Sandcastle/gallery/Voxels.html | 115 ++-
Source/Core/OrientedBoundingBox.js | 19 +
Source/Scene/VoxelBoxShape.js | 14 +-
Source/Scene/VoxelCylinderShape.js | 14 +-
Source/Scene/VoxelEllipsoidShape.js | 1181 ++++++------------------
Source/Scene/VoxelPrimitive.js | 68 +-
Source/Shaders/VoxelFS.glsl | 181 ++--
Specs/Scene/VoxelEllipsoidShapeSpec.js | 418 +++++----
8 files changed, 749 insertions(+), 1261 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index a55ffc38e8a..db7b3216cd5 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -35,7 +35,13 @@
function startup(Cesium) {
"use strict";
//Sandcastle_Begin
- const viewer = new Cesium.Viewer("cesiumContainer");
+ const viewer = new Cesium.Viewer("cesiumContainer", {
+ imageryProvider: new Cesium.TileMapServiceImageryProvider({
+ url: Cesium.buildModuleUrl("Assets/Textures/NaturalEarthII"),
+ }),
+ baseLayerPicker: false,
+ geocoder: false,
+ });
viewer.extend(Cesium.viewerVoxelInspectorMixin);
viewer.scene.debugShowFramesPerSecond = true;
let voxelPrimitive;
@@ -46,21 +52,30 @@
break;
}
case Cesium.VoxelShapeType.ELLIPSOID: {
- const west = -Cesium.Math.PI + 0.2;
- const east = +Cesium.Math.PI;
- const south = -Cesium.Math.PI_OVER_TWO;
- const north = +Cesium.Math.PI_OVER_TWO;
- const minimumHeight = -10.0;
- const maximumHeight = +10.0;
- this.minBounds = new Cesium.Cartesian3(
+ const minBounds = Cesium.VoxelShapeType.getMinBounds(
+ Cesium.VoxelShapeType.ELLIPSOID
+ );
+ const maxBounds = Cesium.VoxelShapeType.getMaxBounds(
+ Cesium.VoxelShapeType.ELLIPSOID
+ );
+ const west = minBounds.x;
+ const east = maxBounds.x;
+ const south = minBounds.y;
+ const north = maxBounds.y;
+ const minimumHeight = 0.0;
+ const maximumHeight = 2000000.0;
+
+ this.minBounds = Cesium.Cartesian3.fromElements(
west,
south,
- minimumHeight
+ minimumHeight,
+ new Cesium.Cartesian3()
);
- this.maxBounds = new Cesium.Cartesian3(
+ this.maxBounds = Cesium.Cartesian3.fromElements(
east,
north,
- maximumHeight
+ maximumHeight,
+ new Cesium.Cartesian3()
);
break;
}
@@ -70,7 +85,7 @@
}
this.shape = shape;
- this.dimensions = new Cesium.Cartesian3(4, 4, 4);
+ this.dimensions = new Cesium.Cartesian3(8, 8, 8);
// this.names = ["color", "shininess"];
// this.types = [Cesium.MetadataType.VEC4, Cesium.MetadataType.SCALAR];
// this.componentTypes = [
@@ -87,7 +102,7 @@
ProceduralSingleTileVoxelProvider.prototype.requestData = function (
options
) {
- const tileLevel = options.level;
+ const tileLevel = options.tileLevel;
const tileX = options.tileX;
const tileY = options.tileY;
const tileZ = options.tileZ;
@@ -114,7 +129,7 @@
const lerperY = y / (dimensions.y - 1);
const lerperZ = z / (dimensions.z - 1);
- const h = hue + lerperX * 0.1;
+ const h = hue + lerperX * 0.5 - lerperY * 0.3 + lerperZ * 0.2;
const s = 1.0 - lerperY * 0.2;
const v = 0.5 + 2.0 * (lerperZ - 0.5) * 0.2;
const color = Cesium.Color.fromHsl(h, s, v);
@@ -122,17 +137,17 @@
const index =
z * dimensions.y * dimensions.x + y * dimensions.x + x;
- if (tileLevel === 0) {
- dataColor[index * channelCount + 0] = Math.random();
- dataColor[index * channelCount + 1] = Math.random();
- dataColor[index * channelCount + 2] = Math.random();
- dataColor[index * channelCount + 3] = 1.0;
- } else {
- dataColor[index * channelCount + 0] = color.red;
- dataColor[index * channelCount + 1] = color.green;
- dataColor[index * channelCount + 2] = color.blue;
- dataColor[index * channelCount + 3] = 0.75;
- }
+ // if (tileLevel === 0) {
+ // dataColor[index * channelCount + 0] = Math.random();
+ // dataColor[index * channelCount + 1] = Math.random();
+ // dataColor[index * channelCount + 2] = Math.random();
+ // dataColor[index * channelCount + 3] = 1.0;
+ // } else {
+ dataColor[index * channelCount + 0] = color.red;
+ dataColor[index * channelCount + 1] = color.green;
+ dataColor[index * channelCount + 2] = color.blue;
+ dataColor[index * channelCount + 3] = 0.75;
+ // }
}
}
}
@@ -336,6 +351,11 @@
customShader: customShader,
modelMatrix: Cesium.Matrix4.fromScale(
Cesium.Ellipsoid.WGS84.radii,
+ // Cesium.Cartesian3.fromElements(
+ // Cesium.Ellipsoid.WGS84.radii.x,
+ // Cesium.Ellipsoid.WGS84.radii.y,
+ // Cesium.Ellipsoid.WGS84.radii.z * 2.0
+ // ),
new Cesium.Matrix4()
),
})
@@ -351,6 +371,8 @@
.catch(function (error) {
console.log(error);
});
+
+ return voxelPrimitive;
}
const customShaderColor = new Cesium.CustomShader({
@@ -376,7 +398,25 @@
const provider = new ProceduralSingleTileVoxelProvider(
Cesium.VoxelShapeType.BOX
);
- createPrimitive(provider, customShaderColor);
+ const primitive = createPrimitive(provider, customShaderColor);
+ primitive.readyPromise.then(function () {
+ viewer.camera.flyToBoundingSphere(primitive.boundingSphere, {
+ duration: 0.0,
+ });
+ // viewer.camera.position = new Cesium.Cartesian3(
+ // 20210315.04663869,
+ // 22200569.010404103,
+ // 16940652.047713447
+ // );
+ viewer.camera.flyTo({
+ destination: new Cesium.Cartesian3(
+ 22489095.52149246,
+ 15457085.667961657,
+ 20334893.228182133
+ ),
+ duration: 0.0,
+ });
+ });
},
},
{
@@ -385,7 +425,7 @@
const provider = new ProceduralMultiTileVoxelProvider(
Cesium.VoxelShapeType.BOX
);
- createPrimitive(provider, customShaderColor);
+ const primitive = createPrimitive(provider, customShaderColor);
},
},
{
@@ -395,7 +435,7 @@
gltf:
"../../SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/voxelBox.gltf",
});
- createPrimitive(provider, customShaderAlpha);
+ const primitive = createPrimitive(provider, customShaderAlpha);
},
},
{
@@ -405,7 +445,7 @@
url:
"../../SampleData/Cesium3DTiles/Voxel/VoxelBox3DTiles/tileset.json",
});
- createPrimitive(provider, customShaderAlpha);
+ const primitive = createPrimitive(provider, customShaderAlpha);
},
},
{
@@ -414,7 +454,7 @@
const provider = new ProceduralSingleTileVoxelProvider(
Cesium.VoxelShapeType.ELLIPSOID
);
- createPrimitive(provider, customShaderColor);
+ const primitive = createPrimitive(provider, customShaderColor);
},
},
{
@@ -423,7 +463,7 @@
const provider = new ProceduralMultiTileVoxelProvider(
Cesium.VoxelShapeType.ELLIPSOID
);
- createPrimitive(provider, customShaderColor);
+ const primitive = createPrimitive(provider, customShaderColor);
},
},
{
@@ -433,7 +473,7 @@
gltf:
"../../SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/voxelEllipsoid.gltf",
});
- createPrimitive(provider, customShaderAlpha);
+ const primitive = createPrimitive(provider, customShaderAlpha);
},
},
{
@@ -443,7 +483,7 @@
url:
"../../SampleData/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json",
});
- createPrimitive(provider, customShaderAlpha);
+ const primitive = createPrimitive(provider, customShaderAlpha);
},
},
{
@@ -452,7 +492,7 @@
const provider = new ProceduralSingleTileVoxelProvider(
Cesium.VoxelShapeType.CYLINDER
);
- createPrimitive(provider, customShaderColor);
+ const primitive = createPrimitive(provider, customShaderColor);
},
},
{
@@ -461,7 +501,7 @@
const provider = new ProceduralMultiTileVoxelProvider(
Cesium.VoxelShapeType.CYLINDER
);
- createPrimitive(provider, customShaderColor);
+ const primitive = createPrimitive(provider, customShaderColor);
},
},
{
@@ -471,7 +511,7 @@
gltf:
"../../SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/voxelCylinder.gltf",
});
- createPrimitive(provider, customShaderAlpha);
+ const primitive = createPrimitive(provider, customShaderAlpha);
},
},
{
@@ -481,7 +521,7 @@
url:
"../../SampleData/Cesium3DTiles/Voxel/VoxelCylinder3DTiles/tileset.json",
});
- createPrimitive(provider, customShaderAlpha);
+ const primitive = createPrimitive(provider, customShaderAlpha);
},
},
]);
@@ -493,6 +533,7 @@
const mousePosition = movement.position;
const pickedPrimitive = scene.pick(mousePosition);
console.log(pickedPrimitive);
+ console.log(camera.position);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
//Sandcastle_End
diff --git a/Source/Core/OrientedBoundingBox.js b/Source/Core/OrientedBoundingBox.js
index 50d5d679e4d..849402b023a 100644
--- a/Source/Core/OrientedBoundingBox.js
+++ b/Source/Core/OrientedBoundingBox.js
@@ -373,6 +373,25 @@ OrientedBoundingBox.fromRectangle = function (
maximumHeight = defaultValue(maximumHeight, 0.0);
ellipsoid = defaultValue(ellipsoid, Ellipsoid.WGS84);
+ if (rectangle.equals(Rectangle.MAX_VALUE)) {
+ if (!defined(result)) {
+ result = new OrientedBoundingBox();
+ }
+ result.halfAxes = Matrix3.fromScale(
+ Cartesian3.add(
+ ellipsoid.radii,
+ Cartesian3.fromElements(
+ maximumHeight,
+ maximumHeight,
+ maximumHeight,
+ scratchScale
+ ),
+ scratchScale
+ )
+ );
+ return result;
+ }
+
let minX, maxX, minY, maxY, minZ, maxZ, plane;
if (rectangle.width <= CesiumMath.PI) {
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index 0f7cbec3b08..e133f03881b 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -233,7 +233,7 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
*
- * @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
+ * @param {Cartesian3} dimensions The voxel grid dimensions for a tile.
* @returns {Number} The step size.
*/
VoxelBoxShape.prototype.computeApproximateStepSize = function (dimensions) {
@@ -267,12 +267,12 @@ VoxelBoxShape.DefaultMaxBounds = new Cartesian3(+1.0, +1.0, +1.0);
*
* @function
*
- * @param {Cartesian3} minimumX The minimumX.
- * @param {Cartesian3} maximumX The maximumX.
- * @param {Cartesian3} minimumY The minimumY.
- * @param {Cartesian3} maximumY The maximumY.
- * @param {Cartesian3} minimumZ The minimumZ.
- * @param {Cartesian3} maximumZ The maximumZ.
+ * @param {Number} minimumX The minimumX.
+ * @param {Number} maximumX The maximumX.
+ * @param {Number} minimumY The minimumY.
+ * @param {Number} maximumY The maximumY.
+ * @param {Number} minimumZ The minimumZ.
+ * @param {Number} maximumZ The maximumZ.
* @param {Matrix4} matrix The matrix to transform the points.
* @param {OrientedBoundingBox} result The object onto which to store the result.
* @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index bf9f0a748ce..520e6fc76d6 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -380,12 +380,16 @@ const scratchScaleRatio = new Cartesian3();
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
*
- * @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
+ * @param {Cartesian3} dimensions The voxel grid dimensions for a tile.
* @returns {Number} The step size.
*/
VoxelCylinderShape.prototype.computeApproximateStepSize = function (
- voxelDimensions
+ dimensions
) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("dimensions", dimensions);
+ //>>includeEnd('debug');
+
const rootTransform = this.shapeTransform;
const minRadius = this._minimumRadius;
@@ -395,9 +399,9 @@ VoxelCylinderShape.prototype.computeApproximateStepSize = function (
const minAngle = this._minimumAngle;
const maxAngle = this._maximumAngle;
- const lerpRadius = 1.0 - 1.0 / voxelDimensions.x;
- const lerpHeight = 1.0 - 1.0 / voxelDimensions.y;
- const lerpAngle = 1.0 - 1.0 / voxelDimensions.z;
+ const lerpRadius = 1.0 - 1.0 / dimensions.x;
+ const lerpHeight = 1.0 - 1.0 / dimensions.y;
+ const lerpAngle = 1.0 - 1.0 / dimensions.z;
// Compare the size of an outermost cylinder voxel to the total cylinder
const voxelMinimumRadius = CesiumMath.lerp(minRadius, maxRadius, lerpRadius);
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index ec374131da2..cbe85683d76 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -1,20 +1,15 @@
import BoundingSphere from "../Core/BoundingSphere.js";
-import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Check from "../Core/Check.js";
-import defaultValue from "../Core/defaultValue.js";
-import defined from "../Core/defined.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import CesiumMath from "../Core/Math.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
-import Ray from "../Core/Ray.js";
import Rectangle from "../Core/Rectangle.js";
-import VoxelShapeType from "./VoxelShapeType.js";
/**
- * A {@link VoxelShape} for an ellipsoid.
+ * An ellipsoid {@link VoxelShape}.
*
* @alias VoxelEllipsoidShape
* @constructor
@@ -26,394 +21,214 @@ import VoxelShapeType from "./VoxelShapeType.js";
*
* @private
*/
-function VoxelEllipsoidShape(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- this._ellipsoid = Ellipsoid.clone(
- defaultValue(options.ellipsoid, Ellipsoid.WGS84)
- );
- this._rectangle = Rectangle.clone(
- defaultValue(options.rectangle, Rectangle.MAX_VALUE)
- );
- this._minimumHeight = defaultValue(options.minimumHeight, 0.0);
- this._maximumHeight = defaultValue(options.maximumHeight, 1.0);
- this._translation = Cartesian3.clone(
- defaultValue(options.translation, Cartesian3.ZERO)
- );
- this._scale = Cartesian3.clone(defaultValue(options.scale, Cartesian3.ONE));
- this._rotation = Matrix3.clone(
- defaultValue(options.rotation, Matrix3.IDENTITY)
- );
+function VoxelEllipsoidShape() {
+ /**
+ * An oriented bounding box containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {OrientedBoundingBox}
+ * @readonly
+ */
+ this.orientedBoundingBox = new OrientedBoundingBox();
- this._firstUpdate = true;
- this._ellipsoidOld = Ellipsoid.clone(this._ellipsoid);
- this._rectangleOld = Rectangle.clone(this._rectangle);
- this._minimumHeightOld = this._minimumHeight;
- this._maximumHeightOld = this._maximumHeight;
- this._translationOld = Cartesian3.clone(this._translation);
- this._scaleOld = Cartesian3.clone(this._scale);
- this._rotationOld = Matrix3.clone(this._rotation);
-
- this._boundTransform = Matrix4.clone(Matrix4.IDENTITY);
- this._shapeTransform = Matrix4.clone(Matrix4.IDENTITY);
- this._orientedBoundingBox = new OrientedBoundingBox();
- this._boundingSphere = new BoundingSphere();
-
- this._ellipsoidHeightDifferenceUv = 1.0;
- this._ellipsoidOuterRadiiLocal = new Cartesian3();
- this._ellipsoidLongitudeBounds = new Cartesian3();
- this._ellipsoidLatitudeBounds = new Cartesian3();
-
- this._type = VoxelShapeType.ELLIPSOID;
-
- this._phiMin = undefined;
- this._phiMax = undefined;
- this._thetaMin = undefined;
- this._thetaMax = undefined;
- setThetaAndPhiExtrema(this);
-}
+ /**
+ * A bounding sphere containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {BoundingSphere}
+ * @readonly
+ */
+ this.boundingSphere = new BoundingSphere();
-function setThetaAndPhiExtrema(that) {
- const rectangle = that.rectangle;
- // Converting from cartographic lat, lon, height to spherical theta, phi, and radius for math
-
- // phi is the angle with the x axis in the counter clockwise direction. It is like
- // longitude, but when it gets halfway around the globe, instead of going to negative
- // pi it continues to positive 2 pi. [0, 2pi]
- that._phiMin = rectangle.west;
- if (rectangle.west < 0) {
- // from [-pi, pi] to [0, 2pi]
- that._phiMin = CesiumMath.TWO_PI + rectangle.west;
- }
- that._phiMax = that._phiMin + rectangle.width;
- if (that._phiMax > CesiumMath.TWO_PI) {
- that._phiMax -= CesiumMath.TWO_PI;
- }
+ /**
+ * A transformation matrix containing the bounded shape.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ * @readonly
+ */
+ this.boundTransform = new Matrix4();
- // theta is angle from z axis to point. It is like latitude except zero is the north pole
- // and it increases as it points more and more south.
- // From [-pi/2, pi/2] centered on equator to [0, pi] from z axis. This swaps min/max
- that._thetaMin = CesiumMath.PI_OVER_TWO - rectangle.north;
- that._thetaMax = that._thetaMin + rectangle.height;
-}
+ /**
+ * A transformation matrix containing the shape, ignoring the bounds.
+ * The update function must be called before accessing this value.
+ * @type {Matrix4}
+ * @readonly
+ */
+ this.shapeTransform = new Matrix4();
-Object.defineProperties(VoxelEllipsoidShape.prototype, {
/**
- * Gets or sets the ellipsoid.
- * @memberof VoxelEllipsoidShape.prototype
- * @type {Ellipsoid}
+ * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
+ * The update function must be called before accessing this value.
+ * @type {Boolean}
+ * @readonly
*/
- ellipsoid: {
- get: function () {
- return this._ellipsoid;
- },
- set: function (value) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("ellipsoid", value);
- //>>includeEnd('debug');
-
- this._ellipsoid = Ellipsoid.clone(value, this._translation);
- },
- },
+ this.isVisible = false;
+
/**
- * Gets or sets the rectangle relative to the ellipsoid.
- * @memberof VoxelEllipsoidShape.prototype
* @type {Rectangle}
*/
- rectangle: {
- get: function () {
- return this._rectangle;
- },
- set: function (value) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("rectangle", value);
- //>>includeEnd('debug');
-
- this._rectangle = Rectangle.clone(value, this._rectangle);
- setThetaAndPhiExtrema(this);
- },
- },
+ this._rectangle = new Rectangle();
+
/**
- * Gets or sets the minimum height relative to the ellipsoid surface.
- * @memberof VoxelEllipsoidShape.prototype
* @type {Number}
+ * @private
*/
- minimumHeight: {
- get: function () {
- return this._minimumHeight;
- },
- set: function (value) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("minimumHeight", value);
- //>>includeEnd('debug');
-
- this._minimumHeight = value;
- },
- },
+ this._minimumHeight = VoxelEllipsoidShape.DefaultMinBounds.z;
+
/**
- * Gets or sets the maximum height relative to the ellipsoid surface.
- * @memberof VoxelEllipsoidShape.prototype
* @type {Number}
+ * @private
*/
- maximumHeight: {
- get: function () {
- return this._maximumHeight;
- },
- set: function (value) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("maximumHeight", value);
- //>>includeEnd('debug');
-
- this._maximumHeight = value;
- },
- },
+ this._maximumHeight = VoxelEllipsoidShape.DefaultMaxBounds.z;
+
/**
- * Gets or sets the translation.
- * @memberof VoxelEllipsoidShape.prototype
- * @type {Cartesian3}
+ * @type {Ellipsoid}
+ * @private
*/
- translation: {
- get: function () {
- return this._translation;
- },
- set: function (value) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("translation", value);
- //>>includeEnd('debug');
-
- this._translation = Cartesian3.clone(value, this._translation);
- },
- },
+ this._ellipsoid = new Ellipsoid();
+
/**
- * Gets or sets the scale.
- * @memberof VoxelEllipsoidShape.prototype
* @type {Cartesian3}
*/
- scale: {
- get: function () {
- return this._scale;
- },
- set: function (value) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("scale", value);
- //>>includeEnd('debug');
-
- this._scale = Cartesian3.clone(value, this._scale);
- },
- },
- /**
- * Gets or sets the rotation.
- * @memberof VoxelEllipsoidShape.prototype
- * @type {Matrix3}
- */
- rotation: {
- get: function () {
- return this._rotation;
- },
- set: function (value) {
- //>>includeStart('debug', pragmas.debug);
- Check.defined("rotation", value);
- //>>includeEnd('debug');
-
- this._rotation = Matrix3.clone(value, this._rotation);
- },
- },
- /**
- * Gets the bounding sphere.
- * @memberof VoxelEllipsoidShape.prototype
- * @type {BoundingSphere}
- * @readonly
- */
- boundingSphere: {
- get: function () {
- if (isDirty(this)) {
- computeTransforms(this);
- }
- return this._boundingSphere;
- },
- },
+ this._translation = new Cartesian3();
+
/**
- * Gets the oriented bounding box.
- * @memberof VoxelEllipsoidShape.prototype
- * @type {OrientedBoundingBox}
- * @readonly
+ * @type {Matrix3
*/
- orientedBoundingBox: {
- get: function () {
- if (isDirty(this)) {
- computeTransforms(this);
- }
- return this._orientedBoundingBox;
- },
- },
-});
-
-VoxelEllipsoidShape.clone = function (shape, result) {
- if (!defined(shape)) {
- return undefined;
- }
-
- if (!defined(result)) {
- result = new VoxelEllipsoidShape();
- }
-
- result._minimumHeight = shape._minimumHeight;
- result._maximumHeight = shape._maximumHeight;
- result._ellipsoid = shape._ellipsoid;
- result._rectangle = shape._rectangle;
-
- result._translation = Cartesian3.clone(shape._translation);
- result._scale = Cartesian3.clone(shape._scale);
- result._rotation = Matrix3.clone(shape._rotation);
-
- result._firstUpdate = shape._firstUpdate;
-
- result._minimumHeightOld = shape._minimumHeightOld;
- result._maximumHeightOld = shape._maximumHeightOld;
- result._ellipsoidOld = shape._ellipsoidOld;
- result._rectangleOld = shape._rectangleOld;
- result._translationOld = Cartesian3.clone(shape._translationOld);
- result._scaleOld = Cartesian3.clone(shape._scaleOld);
- result._rotationOld = Matrix3.clone(shape._rotationOld);
-
- result._boundTransform = Matrix4.clone(shape._boundTransform);
- result._shapeTransform = Matrix4.clone(shape._shapeTransform);
- result._orientedBoundingBox = OrientedBoundingBox.clone(
- shape._orientedBoundingBox
- );
- result._boundingSphere = BoundingSphere.clone(shape._boundingSphere);
-
- result._type = shape._type;
+ this._rotation = new Matrix3();
+}
- return result;
-};
+/**
+ * @type {Cartesian3}
+ * @private
+ */
+VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
+ -CesiumMath.PI,
+ -CesiumMath.PI_OVER_TWO,
+ 0.0
+);
-VoxelEllipsoidShape.prototype.clone = function (result) {
- return VoxelEllipsoidShape.clone(this, result);
-};
+/**
+ * @type {Cartesian3}
+ * @private
+ */
+VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
+ +CesiumMath.PI,
+ +CesiumMath.PI_OVER_TWO,
+ 1.0
+);
const scratchScale = new Cartesian3();
-const scratchOrientedBoundingBox = new OrientedBoundingBox();
-const scratchRectangle = new Rectangle();
+const scratchFullScale = new Cartesian3();
+const scratchRotationScale = new Matrix3();
-function isDirty(that) {
- const dirtyEllipsoid = !that._ellipsoid.equals(that._ellipsoidOld);
- const dirtyRectangle = !Rectangle.equals(that._rectangle, that._rectangleOld);
- const dirtyMinimumHeight = that._minimumHeight !== that._minimumHeightOld;
- const dirtyMaximumHeight = that._maximumHeight !== that._maximumHeightOld;
- const dirtyTranslation = !Cartesian3.equals(
- that._translation,
- that._translationOld
- );
- const dirtyScale = !Cartesian3.equals(that._scale, that._scaleOld);
- const dirtyRotation = !Matrix3.equals(that._rotation, that._rotationOld);
-
- return (
- that._firstUpdate ||
- dirtyEllipsoid ||
- dirtyRectangle ||
- dirtyMinimumHeight ||
- dirtyMaximumHeight ||
- dirtyTranslation ||
- dirtyScale ||
- dirtyRotation
+/**
+ * Update the shape's state.
+ *
+ * @param {Matrix4} modelMatrix The model matrix.
+ * @param {Cartesian3} minBounds The minimum bounds.
+ * @param {Cartesian3} maxBounds The maximum bounds.
+ */
+VoxelEllipsoidShape.prototype.update = function (
+ modelMatrix,
+ minBounds,
+ maxBounds
+) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("modelMatrix", modelMatrix);
+ Check.typeOf.object("minBounds", minBounds);
+ Check.typeOf.object("maxBounds", maxBounds);
+ //>>includeEnd('debug');
+
+ // Don't let the scale be 0, it will screw up a bunch of math
+ const scaleEps = CesiumMath.EPSILON8;
+ const scale = Matrix4.getScale(modelMatrix, scratchScale);
+ if (Math.abs(scale.x) < scaleEps) {
+ scale.x = CesiumMath.signNotZero(scale.x) * scaleEps;
+ }
+ if (Math.abs(scale.y) < scaleEps) {
+ scale.y = CesiumMath.signNotZero(scale.y) * scaleEps;
+ }
+ if (Math.abs(scale.z) < scaleEps) {
+ scale.z = CesiumMath.signNotZero(scale.z) * scaleEps;
+ }
+
+ this._rectangle = Rectangle.fromRadians(
+ minBounds.x,
+ minBounds.y,
+ maxBounds.x,
+ maxBounds.y
);
-}
-function computeTransforms(that) {
- const ellipsoid = that._ellipsoid;
- const minimumHeight = that._minimumHeight;
- const maximumHeight = that._maximumHeight;
- const rectangle = that._rectangle;
-
- const radii = ellipsoid.radii;
- const scaleX = 2.0 * (radii.x + maximumHeight);
- const scaleY = 2.0 * (radii.y + maximumHeight);
- const scaleZ = 2.0 * (radii.z + maximumHeight);
- const scale = Cartesian3.fromElements(scaleX, scaleY, scaleZ, scratchScale);
-
- that._shapeTransform = Matrix4.fromScale(scale, that._shapeTransform);
-
- if (rectangle.equals(Rectangle.MAX_VALUE)) {
- that._boundTransform = Matrix4.clone(
- that._shapeTransform,
- that._boundTransform
- );
- } else {
- // Convert the obb to a model matrix
- const obb = OrientedBoundingBox.fromRectangle(
- rectangle,
- minimumHeight,
- maximumHeight,
- ellipsoid,
- scratchOrientedBoundingBox
- );
- that._boundTransform = OrientedBoundingBox.computeTransformation(
- obb,
- that._boundTransform
- );
+ const minHeight = minBounds.z;
+ const maxHeight = maxBounds.z;
+
+ // Exit early if the bounds make the shape invisible.
+ // Note that west may be greater than east when crossing the 180th meridian.
+ const rectangle = this._rectangle;
+ if (rectangle.south > rectangle.north || minHeight > maxHeight) {
+ this.isVisible = false;
+ return;
}
- that._orientedBoundingBox = OrientedBoundingBox.fromTransformation(
- that._boundTransform,
- that._orientedBoundingBox
+ this._translation = Matrix4.getTranslation(modelMatrix, this._translation);
+ this._rotation = Matrix4.getRotation(modelMatrix, this._rotation);
+ this._ellipsoid = Ellipsoid.fromCartesian3(scale, this._ellipsoid);
+
+ const fullScale = Cartesian3.add(
+ scale,
+ Cartesian3.fromElements(maxHeight, maxHeight, maxHeight, scratchFullScale),
+ scratchFullScale
);
- that._boundingSphere = BoundingSphere.fromTransformation(
- that._boundTransform,
- that._boundingSphere
+ const rotationScale = Matrix3.setScale(
+ this._rotation,
+ fullScale,
+ scratchRotationScale
);
-
- const boundedWest = rectangle.west;
- const boundedEast = rectangle.east;
- const boundedSouth = rectangle.south;
- const boundedNorth = rectangle.north;
- const longitudeRange = rectangle.width;
- const latitudeRange = rectangle.height;
-
- const ellipsoidRadii = ellipsoid.radii;
- const ellipsoidMaximumRadius = ellipsoid.maximumRadius;
- that._ellipsoidHeightDifferenceUv =
- 1.0 -
- (ellipsoidMaximumRadius + minimumHeight) /
- (ellipsoidMaximumRadius + maximumHeight);
- that._ellipsoidOuterRadiiLocal = Cartesian3.divideByScalar(
- ellipsoidRadii,
- ellipsoidMaximumRadius,
- that._ellipsoidOuterRadiiLocal
+ this.shapeTransform = Matrix4.fromRotationTranslation(
+ rotationScale,
+ this._translation,
+ this.shapeTransform
);
- that._ellipsoidLongitudeBounds = Cartesian3.fromElements(
- boundedWest,
- boundedEast,
- longitudeRange,
- that._ellipsoidLongitudeBounds
+
+ this.orientedBoundingBox = getEllipsoidChunkObb(
+ rectangle.west,
+ rectangle.east,
+ rectangle.south,
+ rectangle.north,
+ minHeight,
+ maxHeight,
+ this._ellipsoid,
+ this._translation,
+ this._rotation,
+ this.orientedBoundingBox
);
- that._ellipsoidLatitudeBounds = Cartesian3.fromElements(
- boundedSouth,
- boundedNorth,
- latitudeRange,
- that._ellipsoidLatitudeBounds
+
+ this.boundTransform = Matrix4.fromRotationTranslation(
+ this.orientedBoundingBox.halfAxes,
+ this.orientedBoundingBox.center,
+ this.boundTransform
);
-}
-VoxelEllipsoidShape.prototype.update = function () {
- if (isDirty(this)) {
- computeTransforms(this);
- this._firstUpdate = false;
- this._ellipsoidOld = Ellipsoid.clone(this._ellipsoid, this._ellipsoidOld);
- this._rectangleOld = Rectangle.clone(this._rectangle, this._rectangleOld);
- this._minimumHeightOld = this._minimumHeight;
- this._maximumHeightOld = this._maximumHeight;
- this._translationOld = Cartesian3.clone(
- this._translation,
- this._translationOld
- );
- this._scaleOld = Cartesian3.clone(this._scale, this._scaleOld);
- this._rotationOld = Matrix3.clone(this._rotation, this._rotationOld);
- return true;
- }
+ this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
+ this.orientedBoundingBox,
+ this.boundingSphere
+ );
- return false;
+ this.isVisible = true;
};
+const scratchRectangle = new Rectangle();
+
+/**
+ * Computes an oriented bounding box for a specified tile.
+ * The update function must be called before calling this function.
+ *
+ * @param {Number} tileLevel The tile's level.
+ * @param {Number} tileX The tile's x coordinate.
+ * @param {Number} tileY The tile's y coordinate.
+ * @param {Number} tileZ The tile's z coordinate.
+ * @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
+ * @returns {OrientedBoundingBox} The oriented bounding box.
+ */
VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
tileLevel,
tileX,
@@ -421,47 +236,71 @@ VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
tileZ,
result
) {
- const ellipsoid = this._ellipsoid;
- const rectangle = this._rectangle;
- const minimumHeight = this._minimumHeight;
- const maximumHeight = this._maximumHeight;
-
- const sizeAtLevel = 1.0 / Math.pow(2, tileLevel);
- const tileMinimumHeight = CesiumMath.lerp(
- minimumHeight,
- maximumHeight,
- tileZ * sizeAtLevel
- );
- const tileMaximumHeight = CesiumMath.lerp(
- minimumHeight,
- maximumHeight,
- (tileZ + 1) * sizeAtLevel
- );
-
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.number("tileLevel", tileLevel);
+ Check.typeOf.number("tileX", tileX);
+ Check.typeOf.number("tileY", tileY);
+ Check.typeOf.number("tileZ", tileZ);
+ Check.typeOf.object("result", result);
+ //>>includeEnd('debug');
+
+ const sizeAtLevel = 1.0 / Math.pow(2.0, tileLevel);
const westLerp = tileX * sizeAtLevel;
const eastLerp = (tileX + 1) * sizeAtLevel;
const southLerp = tileY * sizeAtLevel;
const northLerp = (tileY + 1) * sizeAtLevel;
- const tileRectangle = Rectangle.subsection(
- rectangle,
+ const minHeightLerp = tileZ * sizeAtLevel;
+ const maxHeightLerp = (tileZ + 1) * sizeAtLevel;
+
+ const rectangle = Rectangle.subsection(
+ this._rectangle,
westLerp,
southLerp,
eastLerp,
northLerp,
scratchRectangle
);
- return OrientedBoundingBox.fromRectangle(
- tileRectangle,
- tileMinimumHeight,
- tileMaximumHeight,
- ellipsoid,
+
+ const minHeight = CesiumMath.lerp(
+ this._minimumHeight,
+ this._maximumHeight,
+ minHeightLerp
+ );
+
+ const maxHeight = CesiumMath.lerp(
+ this._minimumHeight,
+ this._maximumHeight,
+ maxHeightLerp
+ );
+
+ return getEllipsoidChunkObb(
+ rectangle.west,
+ rectangle.east,
+ rectangle.south,
+ rectangle.north,
+ minHeight,
+ maxHeight,
+ this._ellipsoid,
+ this._translation,
+ this._rotation,
result
);
};
+/**
+ * Computes an approximate step size for raymarching the root tile of a voxel grid.
+ * The update function must be called before calling this function.
+ *
+ * @param {Cartesian3} dimensions The voxel grid dimensions for a tile.
+ * @returns {Number} The step size.
+ */
VoxelEllipsoidShape.prototype.computeApproximateStepSize = function (
- voxelDimensions
+ dimensions
) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.object("dimensions", dimensions);
+ //>>includeEnd('debug');
+
const ellipsoid = this._ellipsoid;
const ellipsoidMaximumRadius = ellipsoid.maximumRadius;
const minimumHeight = this._minimumHeight;
@@ -469,539 +308,16 @@ VoxelEllipsoidShape.prototype.computeApproximateStepSize = function (
const shellToEllipsoidRatio =
(maximumHeight - minimumHeight) / (ellipsoidMaximumRadius + maximumHeight);
- const stepSize = (0.5 * shellToEllipsoidRatio) / voxelDimensions.z;
+ const stepSize = (0.5 * shellToEllipsoidRatio) / dimensions.z;
return stepSize;
};
-const scratchSphericalMinima = new Cartesian3();
-const scratchSphericalMaxima = new Cartesian3();
-VoxelEllipsoidShape.prototype.localPointInsideShape = function (
- point,
- clippingMinimum,
- clippingMaximum
-) {
- const pointUv = Cartesian3.multiplyByScalar(point, 2.0, new Cartesian3());
- const pointEllipsoidSpace = Cartesian3.multiplyComponents(
- pointUv,
- this._ellipsoidOuterRadiiLocal,
- new Cartesian3()
- ); // now we work as if the ellipsoid is a sphere
-
- getSphericalExtremaFromClippingExtrema(
- this,
- clippingMinimum,
- clippingMaximum,
- scratchSphericalMinima,
- scratchSphericalMaxima
- );
- return pointIsWithinClippingParameters(
- pointEllipsoidSpace,
- scratchSphericalMinima.z,
- scratchSphericalMaxima.z,
- scratchSphericalMinima.y,
- scratchSphericalMaxima.y,
- scratchSphericalMinima.x,
- scratchSphericalMaxima.x,
- this._ellipsoidLongitudeBounds
- );
-};
-
-VoxelEllipsoidShape.prototype.transformFromLocalToShapeSpace = function (
- localCartesian,
- result
-) {
- const pos3D = new Cartesian3();
- pos3D.x = localCartesian.x * 2.0;
- pos3D.y = localCartesian.y * 2.0;
- pos3D.z = localCartesian.z * 2.0;
- const ellipsoidRadiiLocal = this._ellipsoidOuterRadiiLocal;
- Cartesian3.multiplyComponents(pos3D, ellipsoidRadiiLocal, pos3D);
-
- const cartographic = Ellipsoid.fromCartesian3(
- Cartesian3.fromElements(1.0, 1.0, 1.0)
- ).cartesianToCartographic(pos3D);
- let longitudeMin = this._ellipsoidLongitudeBounds.x;
- const longitudeMax = this._ellipsoidLongitudeBounds.y;
- const longitudeWidth = this._ellipsoidLongitudeBounds.z;
-
- const latitudeMin = this._ellipsoidLatitudeBounds.x;
- const latitudeWidth = this._ellipsoidLatitudeBounds.z;
-
- if (longitudeMin > longitudeMax) {
- longitudeMin -= CesiumMath.TWO_PI;
- if (cartographic.longitude > longitudeMax) {
- cartographic.longitude -= CesiumMath.TWO_PI;
- }
- }
-
- // normalize to be within min and max of ellipsoid slice
- const distMin = -this._ellipsoidHeightDifferenceUv;
- cartographic.longitude =
- (cartographic.longitude - longitudeMin) / longitudeWidth;
- cartographic.latitude = (cartographic.latitude - latitudeMin) / latitudeWidth;
- cartographic.height = (cartographic.height - distMin) / -distMin;
- result.x = cartographic.longitude;
- result.y = cartographic.latitude;
- result.z = cartographic.height;
- return result;
-};
-
-const scratchIntersectionTs = new Cartesian2();
-VoxelEllipsoidShape.prototype.intersectRay = function (
- ray,
- clippingMinimum,
- clippingMaximum
-) {
- if (!defined(clippingMinimum)) {
- clippingMinimum = Cartesian3.ZERO;
- }
- if (!defined(clippingMaximum)) {
- clippingMinimum = Cartesian3.fromElements(1.0, 1.0, 1.0);
- }
-
- const epsilon = CesiumMath.EPSILON6;
-
- getSphericalExtremaFromClippingExtrema(
- this,
- clippingMinimum,
- clippingMaximum,
- scratchSphericalMinima,
- scratchSphericalMaxima
- );
- const phiMinClipping = scratchSphericalMinima.x;
- const phiMaxClipping = scratchSphericalMaxima.x;
- const thetaMinClipping = scratchSphericalMinima.y;
- const thetaMaxClipping = scratchSphericalMaxima.y;
- const radiusMinClipping = scratchSphericalMinima.z;
- const radiusMaxClipping = scratchSphericalMaxima.z;
-
- // convert to ellipsoid space. This simplifies analytical solutions since in ellipsoid space we are working with a sphere.
- const ellipsoidRadiiLocal = this._ellipsoidOuterRadiiLocal;
- const origin = Cartesian3.multiplyComponents(
- ray.origin,
- ellipsoidRadiiLocal,
- new Cartesian3()
- );
- let direction = Cartesian3.multiplyComponents(
- ray.direction,
- ellipsoidRadiiLocal,
- new Cartesian3()
- );
- direction = Cartesian3.normalize(direction, direction);
- const rayEllipsoidSpace = new Ray(origin, direction);
-
- const a = Cartesian3.dot(direction, direction);
- const b = Cartesian3.dot(origin, direction);
- const originDot = Cartesian3.dot(origin, origin);
-
- let returnT = Number.MAX_VALUE;
-
- const tsOuter = findTsAtRadus(
- radiusMaxClipping,
- a,
- b,
- originDot,
- scratchIntersectionTs
- );
-
- const ellipsoidLongitudeBounds = this._ellipsoidLongitudeBounds;
- if (
- tsOuter.x >= 0.0 &&
- tIsWithinClippingPlanes(
- tsOuter.x,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = tsOuter.x;
- }
- const tsInner = findTsAtRadus(
- radiusMinClipping,
- a,
- b,
- originDot,
- scratchIntersectionTs
- );
- if (
- tsInner.y >= 0.0 &&
- tIsWithinClippingPlanes(
- tsInner.y,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tsInner.y);
- }
-
- const tPhiMinClipping = findTAtPhi(phiMinClipping, rayEllipsoidSpace);
- if (
- tPhiMinClipping >= 0 &&
- tIsWithinClippingPlanes(
- tPhiMinClipping,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tPhiMinClipping);
- }
- const tPhiMaxClipping = findTAtPhi(phiMaxClipping, rayEllipsoidSpace);
- if (
- tPhiMinClipping >= 0 &&
- tIsWithinClippingPlanes(
- tPhiMaxClipping,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tPhiMaxClipping);
- }
-
- if (phiMinClipping < phiMaxClipping) {
- const tPhiMinAbsolute = findTAtPhi(this._phiMin, rayEllipsoidSpace);
- if (
- tPhiMinClipping >= 0 &&
- tIsWithinClippingPlanes(
- tPhiMinAbsolute,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tPhiMinAbsolute);
- }
- const tPhiMaxAbsolute = findTAtPhi(this._phiMax, rayEllipsoidSpace);
- if (
- tPhiMinAbsolute >= 0 &&
- tIsWithinClippingPlanes(
- tPhiMaxAbsolute,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tPhiMaxAbsolute);
- }
- }
-
- const tsThetaMin = findTAtTheta(
- thetaMinClipping,
- rayEllipsoidSpace,
- scratchIntersectionTs
- );
- if (
- tsThetaMin.x >= 0.0 &&
- tIsWithinClippingPlanes(
- tsThetaMin.x,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tsThetaMin.x);
- }
- if (
- tsThetaMin.y >= 0.0 &&
- tIsWithinClippingPlanes(
- tsThetaMin.y,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tsThetaMin.y);
- }
- const tsThetaMax = findTAtTheta(
- thetaMaxClipping,
- rayEllipsoidSpace,
- scratchIntersectionTs
- );
- if (
- tsThetaMax.x >= 0.0 &&
- tIsWithinClippingPlanes(
- tsThetaMax.x,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tsThetaMax.x);
- }
- if (
- tsThetaMax.y >= 0.0 &&
- tIsWithinClippingPlanes(
- tsThetaMax.y,
- rayEllipsoidSpace,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- )
- ) {
- returnT = Math.min(returnT, tsThetaMax.y);
- }
- if (returnT === Number.MAX_VALUE) {
- return -1.0;
- }
-
- const intersection = Ray.getPoint(rayEllipsoidSpace, returnT);
- // transfor point to non ellipsoid space
- Cartesian3.divideComponents(intersection, ellipsoidRadiiLocal, intersection);
- // get t for non ellipsoid space
- let t;
- if (ray.direction.x !== 0) {
- t = (intersection.x - ray.origin.x) / ray.direction.x;
- } else if (ray.direction.y !== 0) {
- t = (intersection.y - ray.origin.y) / ray.direction.y;
- } else if (ray.direction.z !== 0) {
- t = (intersection.z - ray.origin.z) / ray.direction.z;
- }
- return t + epsilon;
-};
-
-function pointIsWithinClippingParameters(
- point,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
-) {
- const radius = Math.sqrt(Cartesian3.dot(point, point));
- let phi = Math.atan2(point.y, point.x);
- if (phi < 0.0) {
- phi += CesiumMath.TWO_PI;
- }
- let theta = Math.atan(
- Math.abs(Math.sqrt(point.x * point.x + point.y * point.y) / point.z)
- );
- if (point.z < 0) {
- theta = CesiumMath.PI - theta;
- }
-
- let absolutePhiMin = ellipsoidLongitudeBounds.x;
- if (absolutePhiMin < 0.0) {
- absolutePhiMin += CesiumMath.TWO_PI;
- }
- let absolutePhiMax = ellipsoidLongitudeBounds.y;
- if (absolutePhiMax < 0.0) {
- absolutePhiMax += CesiumMath.TWO_PI;
- }
- let withinAbsolutePhiBounds = phi >= absolutePhiMin && phi <= absolutePhiMax;
- if (absolutePhiMin >= absolutePhiMax) {
- withinAbsolutePhiBounds = phi >= absolutePhiMin || phi <= absolutePhiMax;
- }
- let withinPhiRange = phi >= phiMinClipping && phi <= phiMaxClipping;
- if (phiMinClipping >= phiMaxClipping) {
- // wrap around zero
- withinPhiRange = phi >= phiMinClipping || phi <= phiMaxClipping;
- }
- return (
- radius >= radiusMinClipping &&
- radius <= radiusMaxClipping &&
- withinPhiRange &&
- withinAbsolutePhiBounds &&
- theta >= thetaMinClipping &&
- theta <= thetaMaxClipping
- );
-}
-
-/**
- * Takes the clipping minima and maxima and converts them to spherical minima and maxima
- * (phi in radians, theta in radians, radius) for the ellipsoid
- * @param {VoxelEllipsoidShape} that
- * @param {Cartesian3} clippingMinimum The minimum values for shape parameters that the shader renders. Shape space [0, 1].
- * @param {Cartesian3} clippingMaximum The minimum values for shape parameters that the shader renders. Shape space [0, 1].
- * @param {Cartesian3} sphericalMinimum Result parameter. The minimum values in unit sphere space. (phi [0, 2pi], theta [0, pi], radius [0, inf]).
- * @param {Cartesian3} sphericalMaximum Result parameter. The maximum values in unit sphere space. (phi [0, 2pi], theta [0, pi], radius [0, inf]).
- * @private
- */
-function getSphericalExtremaFromClippingExtrema(
- that,
- clippingMinimum,
- clippingMaximum,
- sphericalMinimum,
- sphericalMaximum
-) {
- const rectangle = that._rectangle;
- const phiWidth = rectangle.width;
- let phiMin = that._phiMin + phiWidth * clippingMinimum.x;
- if (phiMin >= CesiumMath.TWO_PI) {
- // wrap around zero
- phiMin -= CesiumMath.TWO_PI;
- }
- sphericalMinimum.x = phiMin;
- let phiMax = that._phiMin + phiWidth * clippingMaximum.x;
- if (phiMax >= CesiumMath.TWO_PI) {
- // wrap around zero
- phiMax -= CesiumMath.TWO_PI;
- }
- sphericalMaximum.x = phiMax;
-
- const thetaWidth = rectangle.height;
- sphericalMinimum.y = that._thetaMin + thetaWidth * (1 - clippingMaximum.y);
- sphericalMaximum.y = that._thetaMin + thetaWidth * (1 - clippingMinimum.y);
-
- const radiusWidth = that._ellipsoidHeightDifferenceUv;
- sphericalMinimum.z = 1.0 + (clippingMinimum.z - 1.0) * radiusWidth;
- sphericalMaximum.z = 1.0 + (clippingMaximum.z - 1.0) * radiusWidth;
-}
-
-/**
- * Get the ray parameter t of the intersection with a sphere of given radius
- * @param {Number} radius The radius of the sphere you are intersecting with
- * @param {Number} a The first coefficient of the quadratic equation that we are solving. This should be ray direction dot ray direction.
- * @param {Number} b The second coefficient of the quadratic equation that we are solving. This should be ray direction dot ray origin.
- * @param {Number} c The third coefficient of the quadratic equation that we are solving. This should be ray origin dot ray origin.
- * @param {Cartesian2} tsAtRadius Return parameter. The first intersection will be stored in x while the second will be stored in y.
- * @returns {Cartesian2} The return parameter tsAtRadius with the ts for the intersections in order first to second in x and y.
- * @private
- */
-function findTsAtRadus(radius, a, b, c, tsAtRadius) {
- // no 2 in quadratic formula because it cancels out by taking 2 out of b
- c -= radius * radius;
- let discrim = b * b - a * c;
- tsAtRadius.x = -1.0;
- tsAtRadius.y = -1.0;
- if (discrim >= 0.0) {
- discrim = Math.sqrt(discrim);
- tsAtRadius.x = (-b - discrim) / a;
- tsAtRadius.y = (-b + discrim) / a;
- }
- return tsAtRadius;
-}
-
-const epsilon = CesiumMath.EPSILON6;
-function tIsWithinClippingPlanes(
- t,
- ray,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
-) {
- const intersectionPoint = Ray.getPoint(ray, t + epsilon);
- return pointIsWithinClippingParameters(
- intersectionPoint,
- radiusMinClipping,
- radiusMaxClipping,
- thetaMinClipping,
- thetaMaxClipping,
- phiMinClipping,
- phiMaxClipping,
- ellipsoidLongitudeBounds
- );
-}
-
-/**
- * Finds the ray parameter t for the intersection with a plane created by a constant phi and a ray
- * @param {Number} phi The angle that your want to intersect with. [0, 2pi]
- * @param {Ray} ray The ray you are intersecting
- * @returns {Number} The ray parameter that of the intersection of the ray and the angle phi
- * @private
- */
-function findTAtPhi(phi, ray) {
- // tan(phi) = y/x, solve for t
- const tanPhi = Math.tan(phi);
- const origin = ray.origin;
- const numerator = tanPhi * origin.x - origin.y;
- const direction = ray.direction;
- const denominator = direction.y - tanPhi * direction.x;
- return numerator / denominator;
-}
-
-/**
- * Finds the ray parameters t for the intersections with a ray and the cone created by a constant theta angle
- * @param {Number} theta The theta angle that you want to intersect with. [0, pi]
- * @param {Ray} ray The ray you are intersecting
- * @param {Cartesian2} returnTs Return parameter. The first intersection will be stored in x while the second will be stored in y.
- * @returns {Cartesian2} The return parameter tsAtRadius with the ts for the intersections in order first to second in x and y.
- * @private
- */
-function findTAtTheta(theta, ray, returnTs) {
- const d = ray.direction;
- const o = ray.origin;
- if (theta === CesiumMath.PI_OVER_TWO) {
- // intersect with z = 0
- const intersectionT = -o.z / d.z;
- returnTs.x = intersectionT;
- returnTs.y = intersectionT;
- } else {
- // tan(theta) = sqrt(x^2 + y^2) / z, solve for t
- const tanTheta = Math.tan(theta);
- const tan2Theta = tanTheta * tanTheta;
- const a = d.x * d.x + d.y * d.y - tan2Theta * d.z * d.z;
- const b = o.x * d.x + o.y * d.y - o.z * d.z * tan2Theta;
- const c = o.x * o.x + o.y * o.y - o.z * o.z * tan2Theta;
- let discrim = b * b - a * c;
- if (discrim >= 0.0) {
- discrim = Math.sqrt(discrim);
- returnTs.x = (-b - discrim) / a;
- returnTs.y = (-b + discrim) / a;
- }
- }
- return returnTs;
-}
-
/**
+ * Defines the minimum bounds of the shape. Corresponds to minimum longitude, latitude, height.
+ *
* @type {Cartesian3}
- * @private
+ * @constant
+ * @readonly
*/
VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
-CesiumMath.PI,
@@ -1010,8 +326,11 @@ VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
);
/**
+ * Defines the maximum bounds of the shape. Corresponds to maximum longitude, latitude, height.
+ *
* @type {Cartesian3}
- * @private
+ * @constant
+ * @readonly
*/
VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
+CesiumMath.PI,
@@ -1019,4 +338,50 @@ VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
1.0
);
+/**
+ * Computes an {@link OrientedBoundingBox} for a subregion of the shape.
+ *
+ * @function
+ *
+ * @param {Number} west The minimumX.
+ * @param {Number} east The maximumX.
+ * @param {Number} south The minimumY.
+ * @param {Number} north The maximumY.
+ * @param {Number} minHeight The minimumZ.
+ * @param {Number} maxHeight The maximumZ.
+ * @param {Ellipsoid} ellipsoid The ellipsoid.
+ * @param {Matrix4} matrix The matrix to transform the points.
+ * @param {OrientedBoundingBox} result The object onto which to store the result.
+ * @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
+ *
+ * @private
+ */
+function getEllipsoidChunkObb(
+ west,
+ east,
+ south,
+ north,
+ minHeight,
+ maxHeight,
+ ellipsoid,
+ translation,
+ rotation,
+ result
+) {
+ result = OrientedBoundingBox.fromRectangle(
+ Rectangle.fromRadians(west, south, east, north, scratchRectangle),
+ minHeight,
+ maxHeight,
+ ellipsoid,
+ result
+ );
+ result.center = Cartesian3.add(result.center, translation, result.center);
+ result.halfAxes = Matrix3.multiply(
+ result.halfAxes,
+ rotation,
+ result.halfAxes
+ );
+ return result;
+}
+
export default VoxelEllipsoidShape;
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 17af662bfb9..c54287f6bd7 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -414,7 +414,7 @@ function VoxelPrimitive(options) {
cameraPositionUv: new Cartesian3(),
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
stepSize: 1.0,
- ellipsoidHeightDifferenceUv: 1.0,
+ ellipsoidInverseHeightDifferenceUv: 1.0,
ellipsoidOuterRadiiLocal: new Cartesian3(),
ellipsoidInverseRadiiSquaredLocal: new Cartesian3(),
minBounds: new Cartesian3(),
@@ -1053,6 +1053,7 @@ const scratchTotalDimensions = new Cartesian3();
const scratchIntersect = new Cartesian4();
const scratchNdcAabb = new Cartesian4();
const scratchScale = new Cartesian3();
+const scratchEllipsoidRadii = new Cartesian3();
const scratchLocalScale = new Cartesian3();
const scratchInverseLocalScale = new Cartesian3();
const scratchRotation = new Matrix3();
@@ -1331,16 +1332,35 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Set other uniforms when the shape is dirty
if (shapeType === VoxelShapeType.ELLIPSOID) {
- uniforms.ellipsoidHeightDifferenceUv = shape._ellipsoidHeightDifferenceUv;
- uniforms.ellipsoidOuterRadiiLocal = Cartesian3.clone(
- shape._ellipsoidOuterRadiiLocal,
+ const radii = Matrix4.getScale(
+ compoundModelMatrix,
+ scratchEllipsoidRadii
+ );
+ const minHeight = minBounds.z;
+ const maxHeight = maxBounds.z;
+ // const minRadius = Cartesian3.minimumComponent(radii);
+ const maxRadius = Cartesian3.maximumComponent(radii);
+ uniforms.ellipsoidOuterRadiiLocal = Cartesian3.fromElements(
+ (radii.x + maxHeight) / (maxRadius + maxHeight),
+ (radii.y + maxHeight) / (maxRadius + maxHeight),
+ (radii.z + maxHeight) / (maxRadius + maxHeight),
uniforms.ellipsoidOuterRadiiLocal
);
- uniforms.ellipsoidInverseRadiiSquaredLocal = Cartesian3.multiplyComponents(
- shape._ellipsoidOuterRadiiLocal,
- shape._ellipsoidOuterRadiiLocal,
+ uniforms.ellipsoidInverseRadiiSquaredLocal = Cartesian3.divideComponents(
+ Cartesian3.ONE,
+ Cartesian3.multiplyComponents(
+ uniforms.ellipsoidOuterRadiiLocal,
+ uniforms.ellipsoidOuterRadiiLocal,
+ uniforms.ellipsoidInverseRadiiSquaredLocal
+ ),
uniforms.ellipsoidInverseRadiiSquaredLocal
);
+
+ // TODO: not sure if this is accurate. It could just as well be
+ // (minRadius + minHeight) / (minRadius + maxHeight). Better approach might
+ // be to get height relative to inner ellipsoid.
+ uniforms.ellipsoidInverseHeightDifferenceUv =
+ (maxRadius + maxHeight) / (maxRadius + minHeight);
}
// Math that's only valid if the shape is visible.
@@ -1937,13 +1957,33 @@ function buildDrawCommands(that, context) {
const isDefaultMaxX = maxBounds.x === defaultMaxBounds.x;
const isDefaultMaxY = maxBounds.y === defaultMaxBounds.y;
const isDefaultMaxZ = maxBounds.z === defaultMaxBounds.z;
- const useBounds =
- !isDefaultMinX ||
- !isDefaultMinY ||
- !isDefaultMinZ ||
- !isDefaultMaxX ||
- !isDefaultMaxY ||
- !isDefaultMaxZ;
+
+ let useBounds = false;
+ if (shapeType === VoxelShapeType.BOX) {
+ useBounds =
+ !isDefaultMinX ||
+ !isDefaultMinY ||
+ !isDefaultMinZ ||
+ !isDefaultMaxX ||
+ !isDefaultMaxY ||
+ !isDefaultMaxZ;
+ } else if (shapeType === VoxelShapeType.CYLINDER) {
+ useBounds =
+ !isDefaultMinX ||
+ !isDefaultMinY ||
+ !isDefaultMinZ ||
+ !isDefaultMaxX ||
+ !isDefaultMaxY ||
+ !isDefaultMaxZ;
+ } else if (shapeType === shapeType.ELLIPSOID) {
+ useBounds =
+ !isDefaultMinX ||
+ !isDefaultMinY ||
+ !isDefaultMinZ ||
+ !isDefaultMaxY ||
+ !isDefaultMaxZ;
+ }
+
if (useBounds) {
shaderBuilder.addDefine("BOUNDS", undefined, ShaderDestination.FRAGMENT);
}
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index ee2650d6b04..f0a3fcccb2e 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -180,7 +180,7 @@ uniform vec3 u_maxClippingBounds;
#endif
#if defined(SHAPE_ELLIPSOID)
-uniform float u_ellipsoidHeightDifferenceUv;
+uniform float u_ellipsoidInverseHeightDifferenceUv;
uniform vec3 u_ellipsoidOuterRadiiLocal; // [0,1]
uniform vec3 u_ellipsoidInverseRadiiSquaredLocal;
#endif
@@ -225,6 +225,9 @@ int intMin(int a, int b) {
int intMax(int a, int b) {
return a >= b ? a : b;
}
+int intClamp(int v, int minVal, int maxVal) {
+ return intMin(intMax(v, minVal), maxVal);
+}
float safeMod(float a, float m) {
return mod(mod(a, m) + m, m);
}
@@ -640,7 +643,7 @@ vec2 intersectUnitSphere(Ray ray)
}
#endif
-#if defined(SHAPE_ELLIPSOID) && defined(BOUNDS_2_MIN)
+#if defined(SHAPE_ELLIPSOID)
vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
{
vec3 o = ray.pos;
@@ -708,54 +711,58 @@ vec2 intersectUncappedCone(Ray ray, float angle, float direction)
}
#endif
-#if defined(SHAPE_ELLIPSOID) && defined(BOUNDS)
-vec2 intersectClippedEllipsoid(Ray ray, vec3 minBounds, vec3 maxBounds)
+#if defined(SHAPE_ELLIPSOID)
+vec2 intersectEllipsoidShape(Ray ray)
{
- float lonMin = minBounds.x + 0.5 * czm_pi; // [-pi,+pi]
- float lonMax = maxBounds.x + 0.5 * czm_pi; // [-pi,+pi]
- float latMin = minBounds.y; // [-halfPi,+halfPi]
- float latMax = maxBounds.y; // [-halfPi,+halfPi]
- float heightMin = minBounds.z; // [-inf,+inf]
- float heightMax = maxBounds.z; // [-inf,+inf]
-
- vec2 outerIntersect = intersectUnitSphere(ray);
- if (outerIntersect == vec2(NoHit, NoHit)) {
- return vec2(NoHit, NoHit);
- }
-
- float intersections[SHAPE_INTERSECTION_COUNT];
- intersections[BOUNDS_2_MAX_IDX * 2 + 0] = outerIntersect.x;
- intersections[BOUNDS_2_MAX_IDX * 2 + 1] = outerIntersect.y;
-
- #if defined(BOUNDS_2_MIN)
- float innerScale = heightMin;
- Ray innerRay = Ray(ray.pos / innerScale, ray.dir / innerScale);
- vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- intersections[BOUNDS_2_MIN_IDX * 2 + 0] = innerIntersect.x;
- intersections[BOUNDS_2_MIN_IDX * 2 + 1] = innerIntersect.y;
- #endif
+ #if !defined(BOUNDS)
+ return intersectUnitSphereUnnormalizedDirection(ray);
+ #else
+ float lonMin = u_minBounds.x; // [-pi,+pi]
+ float lonMax = u_maxBounds.x; // [-pi,+pi]
+ float latMin = u_minBounds.y; // [-halfPi,+halfPi]
+ float latMax = u_maxBounds.y; // [-halfPi,+halfPi]
+ float heightMin = u_minBounds.z; // [-inf,+inf]
+ float heightMax = u_maxBounds.z; // [-inf,+inf]
- #if defined(BOUNDS_1_MIN)
- vec2 botConeIntersect = intersectUncappedCone(ray, abs(latMin), sign(latMin));
- intersections[BOUNDS_1_MIN_IDX * 2 + 0] = botConeIntersect.x;
- intersections[BOUNDS_1_MIN_IDX * 2 + 1] = botConeIntersect.y;
- #endif
-
- #if defined(BOUNDS_1_MAX)
- vec2 topConeIntersect = intersectUncappedCone(ray, abs(latMax), sign(latMax));
- intersections[BOUNDS_1_MAX_IDX * 2 + 0] = topConeIntersect.x;
- intersections[BOUNDS_1_MAX_IDX * 2 + 1] = topConeIntersect.y;
- #endif
-
- #if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
- vec3 planeNormal1 = -vec3(cos(lonMin), sin(lonMin), 0.0);
- vec3 planeNormal2 = vec3(cos(lonMax), sin(lonMax), 0.0);
- vec2 wedgeIntersect = intersectWedge(ray, planeNormal1, planeNormal2);
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = wedgeIntersect.x;
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = wedgeIntersect.y;
+ vec2 outerIntersect = intersectUnitSphere(ray);
+ if (outerIntersect == vec2(NoHit, NoHit)) {
+ return vec2(NoHit, NoHit);
+ }
+
+ float intersections[SHAPE_INTERSECTION_COUNT];
+ intersections[BOUNDS_2_MAX_IDX * 2 + 0] = outerIntersect.x;
+ intersections[BOUNDS_2_MAX_IDX * 2 + 1] = outerIntersect.y;
+
+ #if defined(BOUNDS_2_MIN)
+ float innerScale = heightMin;
+ Ray innerRay = Ray(ray.pos / innerScale, ray.dir / innerScale);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ intersections[BOUNDS_2_MIN_IDX * 2 + 0] = innerIntersect.x;
+ intersections[BOUNDS_2_MIN_IDX * 2 + 1] = innerIntersect.y;
+ #endif
+
+ #if defined(BOUNDS_1_MIN)
+ vec2 botConeIntersect = intersectUncappedCone(ray, abs(latMin), sign(latMin));
+ intersections[BOUNDS_1_MIN_IDX * 2 + 0] = botConeIntersect.x;
+ intersections[BOUNDS_1_MIN_IDX * 2 + 1] = botConeIntersect.y;
+ #endif
+
+ #if defined(BOUNDS_1_MAX)
+ vec2 topConeIntersect = intersectUncappedCone(ray, abs(latMax), sign(latMax));
+ intersections[BOUNDS_1_MAX_IDX * 2 + 0] = topConeIntersect.x;
+ intersections[BOUNDS_1_MAX_IDX * 2 + 1] = topConeIntersect.y;
+ #endif
+
+ #if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
+ vec3 planeNormal1 = -vec3(cos(lonMin), sin(lonMin), 0.0);
+ vec3 planeNormal2 = vec3(cos(lonMax), sin(lonMax), 0.0);
+ vec2 wedgeIntersect = intersectWedge(ray, planeNormal1, planeNormal2);
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = wedgeIntersect.x;
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = wedgeIntersect.y;
+ #endif
+
+ return resolveIntersections(intersections);
#endif
-
- return resolveIntersections(intersections);
}
#endif
@@ -853,8 +860,8 @@ vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
- // 1) Convert positionUv [0,1] to unit space [-1, +1] in ellipsoid scale space.
- // 2) Convert to non-ellipsoid space. Max ellipsoid axis has value 1, anything shorter is < 1.
+ // 1) Convert positionUv [0,1] to unit ellipsoid space [-1,+1].
+ // 2) Convert from unit ellipsoid space [-1,+1] to local space. Max ellipsoid axis has value 1, anything shorter is < 1.
// 3) Convert 3d position to 2D point relative to ellipse (since radii.x and radii.y are assumed to be equal for WGS84).
// 4) Find closest distance. if distance > 1, it's outside the outer shell, if distance < u_ellipsoidMinimumHeightUv, it's inside the inner shell.
// 5) Compute geodetic surface normal.
@@ -864,32 +871,13 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
vec3 pos3D = posLocal * u_ellipsoidOuterRadiiLocal; // 2
vec2 pos2D = vec2(length(pos3D.xy), pos3D.z); // 3
float dist = ellipseDistanceIterative(pos2D, u_ellipsoidOuterRadiiLocal.xz); // 4
- vec3 normal = normalize(pos3D * u_ellipsoidInverseRadiiSquaredLocal); // 5
- float longitude = atan(normal.y, normal.x); // 6
- float latitude = asin(normal.z); // 6
+ dist = 1.0 + dist * u_ellipsoidInverseHeightDifferenceUv; // same as delerp(dist, -u_ellipsoidHeightDifferenceUv, 0);
- #if defined(BOUNDS)
- float longitudeMin = u_minBounds.x;
- float longitudeMax = u_maxBounds.x;
- float latitudeMin = u_minBounds.x;
- float latitudeMax = u_minBounds.y;
- if (longitudeMin > longitudeMax) {
- longitudeMin -= czm_twoPi;
- if (longitude > longitudeMax) {
- longitude -= czm_twoPi;
- }
- }
- float shapeX = (longitude - longitudeMin) * u_boundsLengthInverse.x; // [0, 1]
- float shapeY = (latitude - latitudeMin) * u_boundsLengthInverse.y; // [0, 1]
- #else
- float shapeX = (longitude / czm_pi) * 0.5 + 0.5;
- float shapeY = (latitude / czm_piOverTwo) * 0.5 + 0.5;
- #endif
+ vec3 normal = normalize(pos3D * u_ellipsoidInverseRadiiSquaredLocal); // 5
+ float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi; // 6
+ float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi; // 6
- float distMax = 0.0;
- float distMin = -u_ellipsoidHeightDifferenceUv;
- float shapeZ = (dist - distMin) / (distMax - distMin);
- return vec3(shapeX, shapeY, shapeZ);
+ return vec3(longitude, latitude, dist);
}
#endif
@@ -922,51 +910,6 @@ vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
return positionShape;
}
-#if defined(SHAPE_ELLIPSOID)
-vec3 geodeticSurfaceNormalCartographic(float longitude, float latitude) {
- float cosLatitude = cos(latitude);
- float x = cosLatitude * cos(longitude);
- float y = cosLatitude * sin(longitude);
- float z = sin(latitude);
- return normalize(vec3(x, y, z));
-}
-vec3 cartographicToCartesianUv(float longitude, float latitude, float height) {
- vec3 normal = geodeticSurfaceNormalCartographic(longitude, latitude);
- vec3 k = normal * u_ellipsoidOuterRadiiLocal * u_ellipsoidOuterRadiiLocal;
- k /= sqrt(dot(normal, k));
- vec3 final = normal * height + k;
- return final * 0.5 + 0.5;
-}
-#endif
-
-vec3 transformFromShapeSpaceToUv(in vec3 positionUvShapeSpace) {
- #if defined(SHAPE_CYLINDER)
- float dist = positionUvShapeSpace.x;
- float angle = czm_twoPi * safeMod(positionUvShapeSpace.y, 1.0);
- float slice = positionUvShapeSpace.z;
- float x = 0.5 + 0.5 * dist * cos(angle);
- float y = 0.5 + 0.5 * dist * sin(angle);
- float z = slice;
- return vec3(x, y, z);
- #elif defined(SHAPE_ELLIPSOID)
- #if defined(BOUNDS)
- float longitudeMin = u_minBounds.x;
- float longitudeMax = u_maxBounds.x;
- float latitudeMin = u_minBounds.y;
- float latitudeMax = u_maxBounds.y;
- float longitude = mix(longitudeMin, longitudeMax, positionUvShapeSpace.x);
- float latitude = mix(latitudeMin, latitudeMax, positionUvShapeSpace.y);
- float height = mix(-u_ellipsoidHeightDifferenceUv, 0.0, positionUvShapeSpace.z);
- #else
- float longitude = positionUvShapeSpace.x * czm_twoPi - czm_pi;
- float latitude = positionUvShapeSpace.y * czm_pi - czm_piOverTwo;
- float height = positionUvShapeSpace.z;
- #endif
- return cartographicToCartesianUv(longitude, latitude, height);
- #else
- return positionUvShapeSpace;
- #endif
-}
// --------------------------------------------------------
// Megatexture
@@ -1037,7 +980,7 @@ Properties getPropertiesFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 vox
// Slice location
float slice = voxelCoord.z - 0.5;
int sliceIndex = int(floor(slice));
- int sliceIndex0 = intMax(sliceIndex, 0);
+ int sliceIndex0 = intClamp(sliceIndex, 0, voxelDims.z - 1);
vec2 sliceUvOffset0 = index1DTo2DTexcoord(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
// Voxel location
diff --git a/Specs/Scene/VoxelEllipsoidShapeSpec.js b/Specs/Scene/VoxelEllipsoidShapeSpec.js
index e80c0ecd79b..57c55b57f31 100644
--- a/Specs/Scene/VoxelEllipsoidShapeSpec.js
+++ b/Specs/Scene/VoxelEllipsoidShapeSpec.js
@@ -1,179 +1,255 @@
import {
+ BoundingSphere,
Cartesian3,
Math as CesiumMath,
- Ellipsoid,
OrientedBoundingBox,
+ Matrix3,
Matrix4,
- Ray,
- Rectangle,
+ Quaternion,
VoxelEllipsoidShape,
- VoxelShapeType,
} from "../../Source/Cesium.js";
-describe(
- "Scene/VoxelEllipsoidShape",
- function () {
- const PI_OVER_TWO = CesiumMath.PI_OVER_TWO;
- const west = -PI_OVER_TWO;
- const east = PI_OVER_TWO;
- const south = -PI_OVER_TWO;
- const north = PI_OVER_TWO;
- const rectangle = new Rectangle(west, south, east, north);
- const minimumHeight = 0.0;
- const maximumHeight = 1000000.0;
- let ellipsoid;
- const scratchCartesian3 = new Cartesian3();
- beforeEach(function () {
- ellipsoid = new VoxelEllipsoidShape({
- rectangle: rectangle,
- minimumHeight: minimumHeight,
- maximumHeight: maximumHeight,
- });
- ellipsoid.update(); // compute transforms
- });
-
- it("constructs with arguments", function () {
- expect(ellipsoid.ellipsoid.equals(Ellipsoid.WGS84)).toBe(true);
- expect(ellipsoid.rectangle.equals(rectangle)).toBe(true);
- expect(ellipsoid.minimumHeight).toBe(minimumHeight);
- expect(ellipsoid.maximumHeight).toBe(maximumHeight);
- expect(ellipsoid._type).toBe(VoxelShapeType.ELLIPSOID);
- });
-
- it("updates bounding shapes upon changes to ellipsoid", function () {
- const oldObb = ellipsoid.orientedBoundingBox.clone();
- const oldSphere = ellipsoid.boundingSphere.clone();
- ellipsoid.ellipsoid = Ellipsoid.MOON;
- expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
- expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
- });
-
- it("updates bounding shapes upon changes to rectangle", function () {
- const oldObb = ellipsoid.orientedBoundingBox.clone();
- const oldSphere = ellipsoid.boundingSphere.clone();
- ellipsoid.rectangle = new Rectangle(
- west + CesiumMath.EPSILON7,
- south,
- east,
- north
- );
- expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
- expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
- });
-
- it("updates bounding shapes upon changes to minimum height", function () {
- const oldObb = ellipsoid.orientedBoundingBox.clone();
- const oldSphere = ellipsoid.boundingSphere.clone();
- ellipsoid.minimumHeight += 1;
- expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(true);
- expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(true);
- });
-
- it("updates bounding shapes upon changes to maximum height", function () {
- const oldObb = ellipsoid.orientedBoundingBox.clone();
- const oldSphere = ellipsoid.boundingSphere.clone();
- ellipsoid.maximumHeight += 1;
- expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
- expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
- });
-
- it("computes shape transform", function () {
- const radii = Ellipsoid.WGS84._radii;
- const scaleX = 2.0 * (radii.x + maximumHeight);
- const scaleY = 2.0 * (radii.y + maximumHeight);
- const scaleZ = 2.0 * (radii.z + maximumHeight);
- const scale = Cartesian3.fromElements(scaleX, scaleY, scaleZ);
- const shapeTransform = Matrix4.fromScale(scale, new Matrix4());
- expect(shapeTransform.equals(ellipsoid._shapeTransform)).toBe(true);
- });
-
- it("can clone itself", function () {
- const ellipsoidClone = ellipsoid.clone();
- expect(ellipsoidClone).not.toBe(ellipsoid);
- expect(ellipsoid.ellipsoid.equals(ellipsoidClone.ellipsoid)).toBe(true);
- expect(ellipsoid.rectangle.equals(ellipsoidClone.rectangle)).toBe(true);
- expect(ellipsoid.minimumHeight).toBe(ellipsoidClone.minimumHeight);
- expect(ellipsoid.maximumHeight).toBe(ellipsoidClone.maximumHeight);
- });
-
- it("computes bounding volume for root tile", function () {
- const result = new OrientedBoundingBox();
- ellipsoid.computeOrientedBoundingBoxForTile(0, 0, 0, 0, result);
- expect(result.equals(ellipsoid.orientedBoundingBox)).toBe(true);
- });
-
- it("indicates when a point in local space is outside the shape", function () {
- const clippingMinimum = Cartesian3.ZERO;
- const clippingMaximum = Cartesian3.fromElements(1.0, 1.0, 1.0);
- expect(
- ellipsoid.localPointInsideShape(
- Cartesian3.ZERO,
- clippingMinimum,
- clippingMaximum
- )
- ).toBe(false);
- expect(
- ellipsoid.localPointInsideShape(
- Cartesian3.fromElements(0.49, 0.0, 0.0),
- clippingMinimum,
- clippingMaximum
- )
- ).toBe(true);
- });
-
- it("transforms from local to shape space", function () {
- const point = Cartesian3.fromElements(0.5, 0.0, 0.0);
- expect(
- ellipsoid
- .transformFromLocalToShapeSpace(point, scratchCartesian3)
- .equals(Cartesian3.fromElements(0.5, 0.5, 1.0))
- ).toBe(true);
- });
-
- it("intersects ray with outer shell", function () {
- const origin = Cartesian3.fromElements(2.0, 0.0, 0.0);
- const direction = Cartesian3.fromElements(-1.0, 0.0, 0.0);
- const ray = new Ray(origin, direction);
- const minClipping = Cartesian3.ZERO;
- const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
- const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
- expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
- });
-
- it("intersects ray with inner shell", function () {
- const origin = Cartesian3.ZERO;
- const direction = Cartesian3.fromElements(1.0, 0.0, 0.0);
- const ray = new Ray(origin, direction);
- const minClipping = Cartesian3.ZERO;
- const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
- const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
- expect(t).toEqualEpsilon(
- 1.0 - ellipsoid._ellipsoidHeightDifferenceUv,
- CesiumMath.EPSILON4
- );
- });
-
- it("intersects ray with longitude face", function () {
- ellipsoid.rectangle = new Rectangle(west, south, 0.0, north);
- const origin = Cartesian3.fromElements(0.99, 1.0, 0.0);
- const direction = Cartesian3.fromElements(0.0, -1.0, 0.0);
- const ray = new Ray(origin, direction);
- const minClipping = Cartesian3.fromElements(-1.0, -1.0, -1.0);
- const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
- const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
- expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
- });
-
- it("intersects ray with latitude face", function () {
- ellipsoid.rectangle = new Rectangle(west, south, east, 0.0);
- const origin = Cartesian3.fromElements(0.99, 0.0, 1.0);
- const direction = Cartesian3.fromElements(0.0, 0.0, -1.0);
- const ray = new Ray(origin, direction);
- const minClipping = Cartesian3.fromElements(-1.0, -1.0, -1.0);
- const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
- const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
- expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
- });
- },
- "WebGL"
-);
+describe("Scene/VoxelEllipsoidShape", function () {
+ it("constructs", function () {
+ const shape = new VoxelEllipsoidShape();
+ expect(shape.isVisible).toEqual(false);
+ });
+
+ it("update works with model matrix", function () {
+ const shape = new VoxelEllipsoidShape();
+ const translation = new Cartesian3(1.0, 2.0, 3.0);
+ const scale = new Cartesian3(2.0, 2.0, 4.0);
+ const angle = CesiumMath.PI_OVER_FOUR;
+ const rotation = Quaternion.fromAxisAngle(Cartesian3.UNIT_Z, angle);
+
+ const modelMatrix = Matrix4.fromTranslationQuaternionRotationScale(
+ translation,
+ rotation,
+ scale
+ );
+
+ const minBounds = VoxelEllipsoidShape.DefaultMinBounds;
+ const maxBounds = VoxelEllipsoidShape.DefaultMaxBounds;
+ const maxHeight = maxBounds.z;
+
+ const expectedOrientedBoundingBox = new OrientedBoundingBox(
+ translation,
+ Matrix3.fromColumnMajorArray([
+ (scale.x + maxHeight) * Math.cos(angle),
+ (scale.x + maxHeight) * Math.sin(angle),
+ 0.0,
+ (scale.y + maxHeight) * Math.cos(angle + CesiumMath.PI_OVER_TWO),
+ (scale.y + maxHeight) * Math.sin(angle + CesiumMath.PI_OVER_TWO),
+ 0.0,
+ 0.0,
+ 0.0,
+ scale.z + maxHeight,
+ ])
+ );
+
+ const expectedBoundingSphere = BoundingSphere.fromOrientedBoundingBox(
+ expectedOrientedBoundingBox,
+ new BoundingSphere()
+ );
+
+ shape.update(modelMatrix, minBounds, maxBounds);
+
+ expect(shape.orientedBoundingBox.center).toEqual(
+ expectedOrientedBoundingBox.center
+ );
+ expect(shape.orientedBoundingBox.halfAxes).toEqualEpsilon(
+ expectedOrientedBoundingBox.halfAxes,
+ CesiumMath.EPSILON12
+ );
+ expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
+
+ expect(
+ Matrix4.getTranslation(shape.boundTransform, new Cartesian3())
+ ).toEqualEpsilon(expectedOrientedBoundingBox.center, CesiumMath.EPSILON12);
+
+ expect(
+ Matrix4.getMatrix3(shape.boundTransform, new Matrix3())
+ ).toEqualEpsilon(
+ expectedOrientedBoundingBox.halfAxes,
+ CesiumMath.EPSILON12
+ );
+
+ expect(
+ Matrix4.getTranslation(shape.shapeTransform, new Cartesian3())
+ ).toEqualEpsilon(expectedOrientedBoundingBox.center, CesiumMath.EPSILON12);
+
+ expect(
+ Matrix4.getMatrix3(shape.shapeTransform, new Matrix3())
+ ).toEqualEpsilon(
+ expectedOrientedBoundingBox.halfAxes,
+ CesiumMath.EPSILON12
+ );
+
+ // expect(shape.boundTransform).toEqual(modelMatrix);
+ // expect(shape.shapeTransform).toEqual(modelMatrix);
+ // expect(shape.isVisible).toBeTrue();
+ });
+
+ // const PI_OVER_TWO = CesiumMath.PI_OVER_TWO;
+ // const west = -PI_OVER_TWO;
+ // const east = PI_OVER_TWO;
+ // const south = -PI_OVER_TWO;
+ // const north = PI_OVER_TWO;
+ // const rectangle = new Rectangle(west, south, east, north);
+ // const minimumHeight = 0.0;
+ // const maximumHeight = 1000000.0;
+ // let ellipsoid;
+ // const scratchCartesian3 = new Cartesian3();
+ // beforeEach(function () {
+ // ellipsoid = new VoxelEllipsoidShape({
+ // rectangle: rectangle,
+ // minimumHeight: minimumHeight,
+ // maximumHeight: maximumHeight,
+ // });
+ // ellipsoid.update(); // compute transforms
+ // });
+
+ // it("constructs with arguments", function () {
+ // expect(ellipsoid.ellipsoid.equals(Ellipsoid.WGS84)).toBe(true);
+ // expect(ellipsoid.rectangle.equals(rectangle)).toBe(true);
+ // expect(ellipsoid.minimumHeight).toBe(minimumHeight);
+ // expect(ellipsoid.maximumHeight).toBe(maximumHeight);
+ // expect(ellipsoid._type).toBe(VoxelShapeType.ELLIPSOID);
+ // });
+
+ // it("updates bounding shapes upon changes to ellipsoid", function () {
+ // const oldObb = ellipsoid.orientedBoundingBox.clone();
+ // const oldSphere = ellipsoid.boundingSphere.clone();
+ // ellipsoid.ellipsoid = Ellipsoid.MOON;
+ // expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
+ // expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
+ // });
+
+ // it("updates bounding shapes upon changes to rectangle", function () {
+ // const oldObb = ellipsoid.orientedBoundingBox.clone();
+ // const oldSphere = ellipsoid.boundingSphere.clone();
+ // ellipsoid.rectangle = new Rectangle(
+ // west + CesiumMath.EPSILON7,
+ // south,
+ // east,
+ // north
+ // );
+ // expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
+ // expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
+ // });
+
+ // it("updates bounding shapes upon changes to minimum height", function () {
+ // const oldObb = ellipsoid.orientedBoundingBox.clone();
+ // const oldSphere = ellipsoid.boundingSphere.clone();
+ // ellipsoid.minimumHeight += 1;
+ // expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(true);
+ // expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(true);
+ // });
+
+ // it("updates bounding shapes upon changes to maximum height", function () {
+ // const oldObb = ellipsoid.orientedBoundingBox.clone();
+ // const oldSphere = ellipsoid.boundingSphere.clone();
+ // ellipsoid.maximumHeight += 1;
+ // expect(oldObb.equals(ellipsoid.orientedBoundingBox)).toBe(false);
+ // expect(oldSphere.equals(ellipsoid.boundingSphere)).toBe(false);
+ // });
+
+ // it("computes shape transform", function () {
+ // const radii = Ellipsoid.WGS84._radii;
+ // const scaleX = 2.0 * (radii.x + maximumHeight);
+ // const scaleY = 2.0 * (radii.y + maximumHeight);
+ // const scaleZ = 2.0 * (radii.z + maximumHeight);
+ // const scale = Cartesian3.fromElements(scaleX, scaleY, scaleZ);
+ // const shapeTransform = Matrix4.fromScale(scale, new Matrix4());
+ // expect(shapeTransform.equals(ellipsoid._shapeTransform)).toBe(true);
+ // });
+
+ // it("can clone itself", function () {
+ // const ellipsoidClone = ellipsoid.clone();
+ // expect(ellipsoidClone).not.toBe(ellipsoid);
+ // expect(ellipsoid.ellipsoid.equals(ellipsoidClone.ellipsoid)).toBe(true);
+ // expect(ellipsoid.rectangle.equals(ellipsoidClone.rectangle)).toBe(true);
+ // expect(ellipsoid.minimumHeight).toBe(ellipsoidClone.minimumHeight);
+ // expect(ellipsoid.maximumHeight).toBe(ellipsoidClone.maximumHeight);
+ // });
+
+ // it("computes bounding volume for root tile", function () {
+ // const result = new OrientedBoundingBox();
+ // ellipsoid.computeOrientedBoundingBoxForTile(0, 0, 0, 0, result);
+ // expect(result.equals(ellipsoid.orientedBoundingBox)).toBe(true);
+ // });
+
+ // it("indicates when a point in local space is outside the shape", function () {
+ // const clippingMinimum = Cartesian3.ZERO;
+ // const clippingMaximum = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ // expect(
+ // ellipsoid.localPointInsideShape(
+ // Cartesian3.ZERO,
+ // clippingMinimum,
+ // clippingMaximum
+ // )
+ // ).toBe(false);
+ // expect(
+ // ellipsoid.localPointInsideShape(
+ // Cartesian3.fromElements(0.49, 0.0, 0.0),
+ // clippingMinimum,
+ // clippingMaximum
+ // )
+ // ).toBe(true);
+ // });
+
+ // it("transforms from local to shape space", function () {
+ // const point = Cartesian3.fromElements(0.5, 0.0, 0.0);
+ // expect(
+ // ellipsoid
+ // .transformFromLocalToShapeSpace(point, scratchCartesian3)
+ // .equals(Cartesian3.fromElements(0.5, 0.5, 1.0))
+ // ).toBe(true);
+ // });
+
+ // it("intersects ray with outer shell", function () {
+ // const origin = Cartesian3.fromElements(2.0, 0.0, 0.0);
+ // const direction = Cartesian3.fromElements(-1.0, 0.0, 0.0);
+ // const ray = new Ray(origin, direction);
+ // const minClipping = Cartesian3.ZERO;
+ // const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ // const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ // expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
+ // });
+
+ // it("intersects ray with inner shell", function () {
+ // const origin = Cartesian3.ZERO;
+ // const direction = Cartesian3.fromElements(1.0, 0.0, 0.0);
+ // const ray = new Ray(origin, direction);
+ // const minClipping = Cartesian3.ZERO;
+ // const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ // const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ // expect(t).toEqualEpsilon(
+ // 1.0 - ellipsoid._ellipsoidHeightDifferenceUv,
+ // CesiumMath.EPSILON4
+ // );
+ // });
+
+ // it("intersects ray with longitude face", function () {
+ // ellipsoid.rectangle = new Rectangle(west, south, 0.0, north);
+ // const origin = Cartesian3.fromElements(0.99, 1.0, 0.0);
+ // const direction = Cartesian3.fromElements(0.0, -1.0, 0.0);
+ // const ray = new Ray(origin, direction);
+ // const minClipping = Cartesian3.fromElements(-1.0, -1.0, -1.0);
+ // const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ // const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ // expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
+ // });
+
+ // it("intersects ray with latitude face", function () {
+ // ellipsoid.rectangle = new Rectangle(west, south, east, 0.0);
+ // const origin = Cartesian3.fromElements(0.99, 0.0, 1.0);
+ // const direction = Cartesian3.fromElements(0.0, 0.0, -1.0);
+ // const ray = new Ray(origin, direction);
+ // const minClipping = Cartesian3.fromElements(-1.0, -1.0, -1.0);
+ // const maxClipping = Cartesian3.fromElements(1.0, 1.0, 1.0);
+ // const t = ellipsoid.intersectRay(ray, minClipping, maxClipping);
+ // expect(t).toEqualEpsilon(1.0, CesiumMath.EPSILON4);
+ // });
+ // }),
+});
From 1dfe3830e14dbe69596075e9a29546bff7222a29 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Tue, 5 Apr 2022 11:36:36 -0400
Subject: [PATCH 009/679] preparing for ellipsoid bounds
---
Apps/Sandcastle/gallery/Voxels.html | 2 +
Source/Scene/VoxelPrimitive.js | 47 ++++++++++++++-----
Source/Shaders/VoxelFS.glsl | 73 ++++++++++++++++++-----------
3 files changed, 83 insertions(+), 39 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index db7b3216cd5..689327164ad 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -60,6 +60,8 @@
);
const west = minBounds.x;
const east = maxBounds.x;
+ // const south = -0.5;
+ // const north = +0.5;
const south = minBounds.y;
const north = maxBounds.y;
const minimumHeight = 0.0;
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index c54287f6bd7..bbb35a227fd 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1289,12 +1289,31 @@ VoxelPrimitive.prototype.update = function (frameState) {
uniforms.maxBoundsUv
);
} else if (shapeType === VoxelShapeType.ELLIPSOID) {
- uniforms.minBoundsUv = Cartesian3.clone(
- minBounds,
+ const minLongitudeUv =
+ (minBounds.x - defaultMinBounds.x) /
+ (defaultMaxBounds.x - defaultMinBounds.x);
+ const maxLongitudeUv =
+ (maxBounds.x - defaultMinBounds.x) /
+ (defaultMaxBounds.x - defaultMinBounds.x);
+ const minLatitudeUv =
+ (minBounds.y - defaultMinBounds.y) /
+ (defaultMaxBounds.y - defaultMinBounds.y);
+ const maxLatitudeUv =
+ (maxBounds.y - defaultMinBounds.y) /
+ (defaultMaxBounds.y - defaultMinBounds.y);
+ const minHeightUv = 0.0; // don't know what to do with these yet
+ const maxHeightUv = 0.0; // don't know what to do with these yet
+
+ uniforms.minBoundsUv = Cartesian3.fromElements(
+ minLongitudeUv,
+ minLatitudeUv,
+ minHeightUv,
uniforms.minBoundsUv
);
- uniforms.maxBoundsUv = Cartesian3.clone(
- maxBounds,
+ uniforms.maxBoundsUv = Cartesian3.fromElements(
+ maxLongitudeUv,
+ maxLatitudeUv,
+ maxHeightUv,
uniforms.maxBoundsUv
);
} else if (shapeType === VoxelShapeType.CYLINDER) {
@@ -1962,26 +1981,32 @@ function buildDrawCommands(that, context) {
if (shapeType === VoxelShapeType.BOX) {
useBounds =
!isDefaultMinX ||
- !isDefaultMinY ||
- !isDefaultMinZ ||
!isDefaultMaxX ||
+ !isDefaultMinY ||
!isDefaultMaxY ||
+ !isDefaultMinZ ||
!isDefaultMaxZ;
} else if (shapeType === VoxelShapeType.CYLINDER) {
useBounds =
!isDefaultMinX ||
- !isDefaultMinY ||
- !isDefaultMinZ ||
!isDefaultMaxX ||
+ !isDefaultMinY ||
!isDefaultMaxY ||
+ !isDefaultMinZ ||
!isDefaultMaxZ;
- } else if (shapeType === shapeType.ELLIPSOID) {
+ } else if (shapeType === VoxelShapeType.ELLIPSOID) {
+ const radii = Matrix4.getScale(that._compoundModelMatrix, scratchScale);
+ const hasInnerEllipsoid = !(
+ radii.x === radii.y &&
+ radii.y === radii.z &&
+ minBounds.z === -radii.x
+ );
useBounds =
!isDefaultMinX ||
+ !isDefaultMaxX ||
!isDefaultMinY ||
- !isDefaultMinZ ||
!isDefaultMaxY ||
- !isDefaultMaxZ;
+ hasInnerEllipsoid;
}
if (useBounds) {
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index f0a3fcccb2e..254aa9a7686 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -266,7 +266,7 @@ struct Ray
const float NoHit = -czm_infinity;
const float InfHit = czm_infinity;
-#if (defined(SHAPE_CYLINDER) && defined(BOUNDS)) || (defined(SHAPE_ELLIPSOID) && defined(BOUNDS))
+#if (defined(SHAPE_CYLINDER) || defined(SHAPE_ELLIPSOID)) && defined(BOUNDS)
vec2 resolveIntersections(vec2 intersections[SHAPE_INTERSECTION_COUNT])
{
// TODO: completely skip shape if both of its Ts are below 0.0?
@@ -442,9 +442,9 @@ vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
bool h = smax >= 0.0;
// if () return vec2(tmin, tmax);
- // else if () return vec2(NoHitNeg, tmin);
- // else if () return vec2(NoHitNeg, tmax);
- // else if () return vec2(tmax, NoHitPos);
+ // else if () return vec2(-InfHit, tmin);
+ // else if () return vec2(-InfHit, tmax);
+ // else if () return vec2(tmax, +InfHit);
// else return vec2(NoHit, NoHit);
if (e != g && f == h) return vec2(tmin, tmax);
@@ -705,8 +705,8 @@ vec2 intersectUncappedCone(Ray ray, float angle, float direction)
return vec2(NoHit, NoHit);
}
- else if (h1 < 0.0) return vec2(tmax, NoHitPos);
- else if (h2 < 0.0) return vec2(NoHitNeg, tmin);
+ else if (h1 < 0.0) return vec2(tmax, +InfHit);
+ else if (h2 < 0.0) return vec2(-InfHit, tmin);
else return vec2(tmin, tmax);
}
#endif
@@ -724,44 +724,46 @@ vec2 intersectEllipsoidShape(Ray ray)
float heightMin = u_minBounds.z; // [-inf,+inf]
float heightMax = u_maxBounds.z; // [-inf,+inf]
- vec2 outerIntersect = intersectUnitSphere(ray);
+ vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
+ return outerIntersect;
if (outerIntersect == vec2(NoHit, NoHit)) {
return vec2(NoHit, NoHit);
}
- float intersections[SHAPE_INTERSECTION_COUNT];
- intersections[BOUNDS_2_MAX_IDX * 2 + 0] = outerIntersect.x;
- intersections[BOUNDS_2_MAX_IDX * 2 + 1] = outerIntersect.y;
+ vec2 intersections[SHAPE_INTERSECTION_COUNT];
+ intersections[0] = vec2(float(0), outerIntersect.x);
+ intersections[1] = vec2(float(1), outerIntersect.y);
#if defined(BOUNDS_2_MIN)
float innerScale = heightMin;
Ray innerRay = Ray(ray.pos / innerScale, ray.dir / innerScale);
vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- intersections[BOUNDS_2_MIN_IDX * 2 + 0] = innerIntersect.x;
- intersections[BOUNDS_2_MIN_IDX * 2 + 1] = innerIntersect.y;
+ intersections[2] = vec2(float(2), innerIntersect.x);
+ intersections[3] = vec2(float(3), innerIntersect.y);
#endif
#if defined(BOUNDS_1_MIN)
vec2 botConeIntersect = intersectUncappedCone(ray, abs(latMin), sign(latMin));
- intersections[BOUNDS_1_MIN_IDX * 2 + 0] = botConeIntersect.x;
- intersections[BOUNDS_1_MIN_IDX * 2 + 1] = botConeIntersect.y;
+ intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
+ intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
#endif
#if defined(BOUNDS_1_MAX)
vec2 topConeIntersect = intersectUncappedCone(ray, abs(latMax), sign(latMax));
- intersections[BOUNDS_1_MAX_IDX * 2 + 0] = topConeIntersect.x;
- intersections[BOUNDS_1_MAX_IDX * 2 + 1] = topConeIntersect.y;
+ intersections[BOUNDS_1_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 0), topConeIntersect.x);
+ intersections[BOUNDS_1_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 1), topConeIntersect.y);
#endif
#if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
vec3 planeNormal1 = -vec3(cos(lonMin), sin(lonMin), 0.0);
vec3 planeNormal2 = vec3(cos(lonMax), sin(lonMax), 0.0);
vec2 wedgeIntersect = intersectWedge(ray, planeNormal1, planeNormal2);
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = wedgeIntersect.x;
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = wedgeIntersect.y;
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
#endif
- return resolveIntersections(intersections);
+ return resolveIntersections(intersections);
+ // return vec2(0.0);
#endif
}
#endif
@@ -854,7 +856,11 @@ float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 viewPosUv, vec3 viewDir
#if defined(SHAPE_BOX)
vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
- return positionUv;
+ vec3 positionShape = positionUv;
+ #if defined(BOUNDS)
+ positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
+ #endif
+ return positionShape;
}
#endif
@@ -877,6 +883,15 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi; // 6
float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi; // 6
+ #if defined(BOUNDS)
+ float minLongitude = u_minBoundsUv.x;
+ float maxLongitude = u_maxBoundsUv.x;
+ float minLatitude = u_minBoundsUv.y;
+ float maxLatitude = u_minBoundsUv.y;
+ longitude = (longitude - minLongitude) / (maxLongitude - minLongitude);
+ latitude = (latitude - minLatitude) / (maxLatitude - minLatitude);
+ #endif
+
return vec3(longitude, latitude, dist);
}
#endif
@@ -887,7 +902,16 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
float radius = length(positionLocal.xy); // [0,1]
float height = positionUv.z; // [0,1]
float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
- return vec3(radius, height, angle);
+ vec3 positionShape = vec3(radius, height, angle);
+
+ #if defined(BOUNDS)
+ positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
+ // TODO: This breaks down when minBounds == maxBounds. To fix it, this
+ // function would have to know if ray is intersecting the front or back of the shape
+ // and set the shape space position to 1 (front) or 0 (back) accordingly.
+ #endif
+
+ return positionShape;
}
#endif
@@ -900,13 +924,6 @@ vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
vec3 positionShape = transformFromUvToCylinderSpace(positionUv);
#endif
- #if defined(BOUNDS)
- positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
- // TODO: This breaks down when minBounds == maxBounds. To fix it, this
- // function would have to know if ray is intersecting the front or back of the shape
- // and set the shape space position to 1 (front) or 0 (back) accordingly.
- #endif
-
return positionShape;
}
From 94521adc27fd336461de7897f8bc11286693f75e Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Tue, 5 Apr 2022 12:00:18 -0400
Subject: [PATCH 010/679] disable cull and occlude if depth testing is off
---
Source/Scene/VoxelEllipsoidShape.js | 4 +-
Source/Scene/VoxelPrimitive.js | 2 +
.../Widgets/VoxelInspector/VoxelInspector.js | 48 ++++++++++++++-----
3 files changed, 40 insertions(+), 14 deletions(-)
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index cbe85683d76..41a5f5cd728 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -322,7 +322,7 @@ VoxelEllipsoidShape.prototype.computeApproximateStepSize = function (
VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
-CesiumMath.PI,
-CesiumMath.PI_OVER_TWO,
- 0.0
+ 0.0 // should be -1?
);
/**
@@ -335,7 +335,7 @@ VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
+CesiumMath.PI,
+CesiumMath.PI_OVER_TWO,
- 1.0
+ 1.0 // should be 0?
);
/**
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index bbb35a227fd..fb5b2834158 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -2482,6 +2482,8 @@ function buildDrawCommands(that, context) {
pass: Pass.VOXELS,
executeInClosestFrustum: true,
owner: this,
+ cull: depthTest, // don't cull or occlude if depth testing is off
+ occlude: depthTest, // don't cull or occlude if depth testing is off
});
// Create the pick draw command
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
index c1eed5c1631..2c1e327db37 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspector.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -1,3 +1,4 @@
+import Cartesian3 from "../../Core/Cartesian3.js";
import CesiumMath from "../../Core/Math.js";
import Check from "../../Core/Check.js";
import defaultValue from "../../Core/defaultValue.js";
@@ -127,6 +128,29 @@ function VoxelInspector(container, scene) {
"boundsVisibleToggle"
);
+ const boxMinBounds = VoxelShapeType.getMinBounds(VoxelShapeType.BOX);
+ const boxMaxBounds = VoxelShapeType.getMaxBounds(VoxelShapeType.BOX);
+
+ const ellipsoidMinBounds = Cartesian3.fromElements(
+ VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID).x,
+ VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID).y,
+ -6356752.3142451793, // The deepest height for WGS84
+ new Cartesian3()
+ );
+ const ellipsoidMaxBounds = Cartesian3.fromElements(
+ VoxelShapeType.getMaxBounds(VoxelShapeType.ELLIPSOID).x,
+ VoxelShapeType.getMaxBounds(VoxelShapeType.ELLIPSOID).y,
+ +10000000.0,
+ new Cartesian3()
+ );
+
+ const cylinderMinBounds = VoxelShapeType.getMinBounds(
+ VoxelShapeType.CYLINDER
+ );
+ const cylinderMaxBounds = VoxelShapeType.getMaxBounds(
+ VoxelShapeType.CYLINDER
+ );
+
makeCoordinateRange(
"Max X",
"Min X",
@@ -140,8 +164,8 @@ function VoxelInspector(container, scene) {
"boundsBoxMinY",
"boundsBoxMaxZ",
"boundsBoxMinZ",
- VoxelShapeType.getMinBounds(VoxelShapeType.BOX),
- VoxelShapeType.getMaxBounds(VoxelShapeType.BOX),
+ boxMinBounds,
+ boxMaxBounds,
"shapeIsBox",
boundsPanelContents
);
@@ -159,8 +183,8 @@ function VoxelInspector(container, scene) {
"boundsEllipsoidMinLatitude",
"boundsEllipsoidMaxHeight",
"boundsEllipsoidMinHeight",
- VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID),
- VoxelShapeType.getMaxBounds(VoxelShapeType.ELLIPSOID),
+ ellipsoidMinBounds,
+ ellipsoidMaxBounds,
"shapeIsEllipsoid",
boundsPanelContents
);
@@ -178,8 +202,8 @@ function VoxelInspector(container, scene) {
"boundsCylinderMinHeight",
"boundsCylinderMaxAngle",
"boundsCylinderMinAngle",
- VoxelShapeType.getMinBounds(VoxelShapeType.CYLINDER),
- VoxelShapeType.getMaxBounds(VoxelShapeType.CYLINDER),
+ cylinderMinBounds,
+ cylinderMaxBounds,
"shapeIsCylinder",
boundsPanelContents
);
@@ -205,8 +229,8 @@ function VoxelInspector(container, scene) {
"clippingBoxMinY",
"clippingBoxMaxZ",
"clippingBoxMinZ",
- VoxelShapeType.getMinBounds(VoxelShapeType.BOX),
- VoxelShapeType.getMaxBounds(VoxelShapeType.BOX),
+ boxMinBounds,
+ boxMaxBounds,
"shapeIsBox",
clippingPanelContents
);
@@ -224,8 +248,8 @@ function VoxelInspector(container, scene) {
"clippingEllipsoidMinLatitude",
"clippingEllipsoidMaxHeight",
"clippingEllipsoidMinHeight",
- VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID),
- VoxelShapeType.getMaxBounds(VoxelShapeType.ELLIPSOID),
+ ellipsoidMinBounds,
+ ellipsoidMaxBounds,
"shapeIsEllipsoid",
clippingPanelContents
);
@@ -243,8 +267,8 @@ function VoxelInspector(container, scene) {
"clippingCylinderMinHeight",
"clippingCylinderMaxAngle",
"clippingCylinderMinAngle",
- VoxelShapeType.getMinBounds(VoxelShapeType.CYLINDER),
- VoxelShapeType.getMaxBounds(VoxelShapeType.CYLINDER),
+ cylinderMinBounds,
+ cylinderMaxBounds,
"shapeIsCylinder",
clippingPanelContents
);
From d5597ba0e3902f7c0dc469eac1ee831f6112d7ea Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Tue, 5 Apr 2022 18:25:29 -0400
Subject: [PATCH 011/679] fixed cone intersection
---
Apps/Sandcastle/gallery/Voxels.html | 21 +++++-
Source/Shaders/VoxelFS.glsl | 110 ++++++++++++++--------------
Source/Widgets/Viewer/Viewer.js | 2 +-
3 files changed, 72 insertions(+), 61 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index 689327164ad..46f75d97863 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -61,10 +61,10 @@
const west = minBounds.x;
const east = maxBounds.x;
// const south = -0.5;
- // const north = +0.5;
+ const north = Cesium.Math.PI_OVER_TWO - 0.1;
const south = minBounds.y;
- const north = maxBounds.y;
- const minimumHeight = 0.0;
+ // const north = maxBounds.y;
+ const minimumHeight = 1000000.0;
const maximumHeight = 2000000.0;
this.minBounds = Cesium.Cartesian3.fromElements(
@@ -394,6 +394,21 @@
});
Sandcastle.addToolbarMenu([
+ {
+ text: "Ellipsoid - Procedural Tile",
+ onselect: function () {
+ const provider = new ProceduralSingleTileVoxelProvider(
+ Cesium.VoxelShapeType.ELLIPSOID
+ );
+ const primitive = createPrimitive(provider, customShaderColor);
+ primitive.readyPromise.then(function () {
+ console.log(primitive.boundingSphere.center);
+ viewer.camera.flyToBoundingSphere(primitive.boundingSphere, {
+ duration: 0.0,
+ });
+ });
+ },
+ },
{
text: "Box - Procedural Tile",
onselect: function () {
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 254aa9a7686..bc8933bc47b 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -136,6 +136,8 @@ void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
#define STEP_COUNT_MAX 1000 // Harcoded value because GLSL doesn't like variable length loops
#define OCTREE_MAX_LEVELS 32 // Harcoded value because GLSL doesn't like variable length loops
#define ALPHA_ACCUM_MAX 0.98 // Must be > 0.0 and <= 1.0
+#define NO_HIT -czm_infinity
+#define INF_HIT czm_infinity
#if defined(MEGATEXTURE_2D)
uniform ivec2 u_megatextureSliceDimensions; // number of slices per tile, in two dimensions
@@ -202,6 +204,11 @@ struct SampleData {
#endif
};
+struct Ray {
+ vec3 pos;
+ vec3 dir;
+};
+
// --------------------------------------------------------
// Misc math
// --------------------------------------------------------
@@ -256,21 +263,11 @@ vec2 index1DTo2DTexcoord(int index, ivec2 dimensions, vec2 uvScale)
// --------------------------------------------------------
// Intersection tests, shape coordinate conversions, etc
// --------------------------------------------------------
-
-struct Ray
-{
- vec3 pos;
- vec3 dir;
-};
-
-const float NoHit = -czm_infinity;
-const float InfHit = czm_infinity;
-
#if (defined(SHAPE_CYLINDER) || defined(SHAPE_ELLIPSOID)) && defined(BOUNDS)
vec2 resolveIntersections(vec2 intersections[SHAPE_INTERSECTION_COUNT])
{
// TODO: completely skip shape if both of its Ts are below 0.0?
- vec2 entryExitT = vec2(NoHit, NoHit);
+ vec2 entryExitT = vec2(NO_HIT, NO_HIT);
// Sort the intersections from min T to max T with bubble sort.
// Note: If this sorting function changes, some of the intersection test may
@@ -352,7 +349,7 @@ vec2 intersectUnitCube(Ray ray)
float tMax = min(min(m1.x, m1.y), m1.z);
if (tMin >= tMax) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
return vec2(tMin, tMax);
@@ -368,7 +365,7 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
float t = -o.z / d.z;
vec2 planePos = o.xy + d.xy * t;
if (any(greaterThan(abs(planePos), vec2(1.0)))) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
return vec2(t, t);
@@ -442,15 +439,15 @@ vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
bool h = smax >= 0.0;
// if () return vec2(tmin, tmax);
- // else if () return vec2(-InfHit, tmin);
- // else if () return vec2(-InfHit, tmax);
- // else if () return vec2(tmax, +InfHit);
- // else return vec2(NoHit, NoHit);
+ // else if () return vec2(-INF_HIT, tmin);
+ // else if () return vec2(-INF_HIT, tmax);
+ // else if () return vec2(tmax, +INF_HIT);
+ // else return vec2(NO_HIT, NO_HIT);
if (e != g && f == h) return vec2(tmin, tmax);
- else if (e == g && f == h) return vec2(-InfHit, tmin);
- else if (e != g && f != h) return vec2(tmax, +InfHit);
- else return vec2(NoHit, NoHit);
+ else if (e == g && f == h) return vec2(-INF_HIT, tmin);
+ else if (e != g && f != h) return vec2(tmax, +INF_HIT);
+ else return vec2(NO_HIT, NO_HIT);
}
#endif
@@ -466,7 +463,7 @@ vec2 intersectUnitCylinder(Ray ray)
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
det = sqrt(det);
@@ -481,13 +478,13 @@ vec2 intersectUnitCylinder(Ray ray)
if (abs(z1) >= 1.0)
{
float tCap = (sign(z1) - o.z) / d.z;
- t1 = abs(b + a * tCap) < det ? tCap : NoHit;
+ t1 = abs(b + a * tCap) < det ? tCap : NO_HIT;
}
if (abs(z2) >= 1.0)
{
float tCap = (sign(z2) - o.z) / d.z;
- t2 = abs(b + a * tCap) < det ? tCap : NoHit;
+ t2 = abs(b + a * tCap) < det ? tCap : NO_HIT;
}
return vec2(t1, t2);
@@ -504,7 +501,7 @@ vec2 intersectUnitCircle(Ray ray) {
float distSqr = dot(zPlanePos, zPlanePos);
if (distSqr > 1.0) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
return vec2(t, t);
@@ -523,7 +520,7 @@ vec2 intersectInfiniteUnitCylinder(Ray ray)
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
det = sqrt(det);
@@ -566,8 +563,8 @@ vec2 intersectCylinderShape(Ray ray)
outerIntersect = intersectUnitCylinder(outerRay);
}
- if (outerIntersect == vec2(NoHit, NoHit)) {
- return vec2(NoHit, NoHit);
+ if (outerIntersect == vec2(NO_HIT, NO_HIT)) {
+ return vec2(NO_HIT, NO_HIT);
}
vec2 intersections[SHAPE_INTERSECTION_COUNT];
@@ -630,7 +627,7 @@ vec2 intersectUnitSphere(Ray ray)
float det = b * b - c;
if (det < 0.0) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
det = sqrt(det);
@@ -655,7 +652,7 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
det = sqrt(det);
@@ -670,26 +667,27 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
#if defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_1_MIN) || defined(BOUNDS_1_MAX))
// TODO: can angle and direction be folded into the same parameter
-vec2 intersectUncappedCone(Ray ray, float angle, float direction)
+vec2 intersectUncappedCone(Ray ray, float angle)
{
vec3 o = ray.pos;
vec3 d = ray.dir;
- float s = direction;
- float h = max(0.01, angle); // float fix
-
+ float s = sign(angle);
+ float h = cos(abs(angle));
float hh = h * h;
- float ds = d[2] * s;
- float os = o[2] * s;
+
+ float ds = d.z * s;
+ float os = o.z * s;
+ float dd = dot(d, d);
float od = dot(o, d);
float oo = dot(o, o);
-
- float a = ds * ds - hh;
+
+ float a = ds * ds - dd * hh;
float b = ds * os - od * hh;
float c = os * os - oo * hh;
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
det = sqrt(det);
@@ -698,15 +696,15 @@ vec2 intersectUncappedCone(Ray ray, float angle, float direction)
float tmin = min(t1, t2);
float tmax = max(t1, t2);
- float h1 = (o[2] + tmin * d[2]) * s;
- float h2 = (o[2] + tmax * d[2]) * s;
+ float h1 = (o.z + tmin * d.z) * s;
+ float h2 = (o.z + tmax * d.z) * s;
if (h1 < 0.0 && h2 < 0.0) {
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
- else if (h1 < 0.0) return vec2(tmax, +InfHit);
- else if (h2 < 0.0) return vec2(-InfHit, tmin);
+ else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
+ else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
else return vec2(tmin, tmax);
}
#endif
@@ -725,9 +723,8 @@ vec2 intersectEllipsoidShape(Ray ray)
float heightMax = u_maxBounds.z; // [-inf,+inf]
vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- return outerIntersect;
- if (outerIntersect == vec2(NoHit, NoHit)) {
- return vec2(NoHit, NoHit);
+ if (outerIntersect == vec2(NO_HIT, NO_HIT)) {
+ return vec2(NO_HIT, NO_HIT);
}
vec2 intersections[SHAPE_INTERSECTION_COUNT];
@@ -735,29 +732,28 @@ vec2 intersectEllipsoidShape(Ray ray)
intersections[1] = vec2(float(1), outerIntersect.y);
#if defined(BOUNDS_2_MIN)
- float innerScale = heightMin;
- Ray innerRay = Ray(ray.pos / innerScale, ray.dir / innerScale);
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseHeightDifferenceUv, ray.dir * u_ellipsoidInverseHeightDifferenceUv);
vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
intersections[2] = vec2(float(2), innerIntersect.x);
intersections[3] = vec2(float(3), innerIntersect.y);
#endif
#if defined(BOUNDS_1_MIN)
- vec2 botConeIntersect = intersectUncappedCone(ray, abs(latMin), sign(latMin));
+ float halfAngleMin = sign(latMin) * (czm_piOverTwo - abs(latMin));
+ vec2 botConeIntersect = intersectUncappedCone(ray, halfAngleMin);
intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
#endif
#if defined(BOUNDS_1_MAX)
- vec2 topConeIntersect = intersectUncappedCone(ray, abs(latMax), sign(latMax));
+ float halfAngleMax = sign(latMax) * (czm_piOverTwo - abs(latMax));
+ vec2 topConeIntersect = intersectUncappedCone(ray, halfAngleMax);
intersections[BOUNDS_1_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 0), topConeIntersect.x);
intersections[BOUNDS_1_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 1), topConeIntersect.y);
#endif
#if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
- vec3 planeNormal1 = -vec3(cos(lonMin), sin(lonMin), 0.0);
- vec3 planeNormal2 = vec3(cos(lonMax), sin(lonMax), 0.0);
- vec2 wedgeIntersect = intersectWedge(ray, planeNormal1, planeNormal2);
+ vec2 wedgeIntersect = intersectWedge(ray, lonMin, lonMax);
intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
#endif
@@ -829,9 +825,9 @@ vec2 intersectShape(vec3 positionUv, vec3 directionUv) {
if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
// Intersection is invalid when start and end are behind the ray.
- return vec2(NoHit, NoHit);
+ return vec2(NO_HIT, NO_HIT);
}
-
+
// Set start to 0 when ray is inside the shape.
entryExitT.x = max(entryExitT.x, 0.0);
@@ -1230,7 +1226,7 @@ void main()
vec2 entryExitT = intersectShape(viewPosUv, viewDirUv);
// Exit early if the shape was completely missed.
- if (entryExitT == vec2(NoHit, NoHit)) {
+ if (entryExitT == vec2(NO_HIT, NO_HIT)) {
discard;
}
@@ -1352,7 +1348,7 @@ void main()
vec2 entryExitT = intersectShape(positionUv, viewDirUv);
// Stop raymarching if it doesn't hit anything
- if (entryExitT == vec2(NoHit, NoHit)) {
+ if (entryExitT == vec2(NO_HIT, NO_HIT)) {
break;
}
diff --git a/Source/Widgets/Viewer/Viewer.js b/Source/Widgets/Viewer/Viewer.js
index 5319e2eb7df..38795b77ed9 100644
--- a/Source/Widgets/Viewer/Viewer.js
+++ b/Source/Widgets/Viewer/Viewer.js
@@ -2027,7 +2027,7 @@ Viewer.prototype._onDataSourceRemoved = function (
* target will be the range. The heading will be determined from the offset. If the heading cannot be
* determined from the offset, the heading will be north.
*
- * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
+ * @param {Entity|Entity[]|EntityCollection|DataSource|ImageryLayer|Cesium3DTileset|TimeDynamicPointCloud|Promise.} target The entity, array of entities, entity collection, data source, Cesium3DTileset, point cloud, or imagery layer to view. You can also pass a promise that resolves to one of the previously mentioned types.
* @param {HeadingPitchRange} [offset] The offset from the center of the entity in the local east-north-up reference frame.
* @returns {Promise.} A Promise that resolves to true if the zoom was successful or false if the target is not currently visualized in the scene or the zoom was cancelled.
*/
From d3b992430150fe8999b830c2af75cd6347007c7d Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Tue, 5 Apr 2022 18:32:08 -0400
Subject: [PATCH 012/679] rearranged ellipsoid before cylinder
---
Source/Shaders/VoxelFS.glsl | 376 ++++++++++++++++++------------------
1 file changed, 187 insertions(+), 189 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index bc8933bc47b..30c942a9da1 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -263,7 +263,7 @@ vec2 index1DTo2DTexcoord(int index, ivec2 dimensions, vec2 uvScale)
// --------------------------------------------------------
// Intersection tests, shape coordinate conversions, etc
// --------------------------------------------------------
-#if (defined(SHAPE_CYLINDER) || defined(SHAPE_ELLIPSOID)) && defined(BOUNDS)
+#if (defined(SHAPE_ELLIPSOID) || defined(SHAPE_CYLINDER)) && defined(BOUNDS)
vec2 resolveIntersections(vec2 intersections[SHAPE_INTERSECTION_COUNT])
{
// TODO: completely skip shape if both of its Ts are below 0.0?
@@ -410,7 +410,7 @@ vec2 intersectBoxShape(Ray ray)
}
#endif
-#if (defined(SHAPE_CYLINDER) && (defined(BOUNDS_2_MIN) || defined(BOUNDS_2_MAX))) || (defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)))
+#if ((defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)) || defined(SHAPE_CYLINDER) && (defined(BOUNDS_2_MIN) || defined(BOUNDS_2_MAX))))
vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
{
vec2 o = ray.pos.xy;
@@ -451,6 +451,154 @@ vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
}
#endif
+#if defined(SHAPE_ELLIPSOID)
+vec2 intersectUnitSphere(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float b = dot(d, o);
+ float c = dot(o, o) - 1.0;
+ float det = b * b - c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT, NO_HIT);
+ }
+
+ det = sqrt(det);
+ float t1 = -b - det;
+ float t2 = -b + det;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float a = dot(d, d);
+ float b = dot(d, o);
+ float c = dot(o, o) - 1.0;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT, NO_HIT);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_1_MIN) || defined(BOUNDS_1_MAX))
+// TODO: can angle and direction be folded into the same parameter
+vec2 intersectUncappedCone(Ray ray, float angle)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ float s = sign(angle);
+ float h = cos(abs(angle));
+ float hh = h * h;
+
+ float ds = d.z * s;
+ float os = o.z * s;
+ float dd = dot(d, d);
+ float od = dot(o, d);
+ float oo = dot(o, o);
+
+ float a = ds * ds - dd * hh;
+ float b = ds * os - od * hh;
+ float c = os * os - oo * hh;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT, NO_HIT);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ float h1 = (o.z + tmin * d.z) * s;
+ float h2 = (o.z + tmax * d.z) * s;
+
+ if (h1 < 0.0 && h2 < 0.0) {
+ return vec2(NO_HIT, NO_HIT);
+ }
+
+ else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
+ else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
+ else return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+vec2 intersectEllipsoidShape(Ray ray)
+{
+ #if !defined(BOUNDS)
+ return intersectUnitSphereUnnormalizedDirection(ray);
+ #else
+ float lonMin = u_minBounds.x; // [-pi,+pi]
+ float lonMax = u_maxBounds.x; // [-pi,+pi]
+ float latMin = u_minBounds.y; // [-halfPi,+halfPi]
+ float latMax = u_maxBounds.y; // [-halfPi,+halfPi]
+ float heightMin = u_minBounds.z; // [-inf,+inf]
+ float heightMax = u_maxBounds.z; // [-inf,+inf]
+
+ vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
+ if (outerIntersect == vec2(NO_HIT, NO_HIT)) {
+ return vec2(NO_HIT, NO_HIT);
+ }
+
+ vec2 intersections[SHAPE_INTERSECTION_COUNT];
+ intersections[0] = vec2(float(0), outerIntersect.x);
+ intersections[1] = vec2(float(1), outerIntersect.y);
+
+ #if defined(BOUNDS_2_MIN)
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseHeightDifferenceUv, ray.dir * u_ellipsoidInverseHeightDifferenceUv);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ intersections[2] = vec2(float(2), innerIntersect.x);
+ intersections[3] = vec2(float(3), innerIntersect.y);
+ #endif
+
+ #if defined(BOUNDS_1_MIN)
+ float halfAngleMin = sign(latMin) * (czm_piOverTwo - abs(latMin));
+ vec2 botConeIntersect = intersectUncappedCone(ray, halfAngleMin);
+ intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
+ intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
+ #endif
+
+ #if defined(BOUNDS_1_MAX)
+ float halfAngleMax = sign(latMax) * (czm_piOverTwo - abs(latMax));
+ vec2 topConeIntersect = intersectUncappedCone(ray, halfAngleMax);
+ intersections[BOUNDS_1_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 0), topConeIntersect.x);
+ intersections[BOUNDS_1_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 1), topConeIntersect.y);
+ #endif
+
+ #if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
+ vec2 wedgeIntersect = intersectWedge(ray, lonMin, lonMax);
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
+ intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
+ #endif
+
+ return resolveIntersections(intersections);
+ // return vec2(0.0);
+ #endif
+}
+#endif
+
#if defined(SHAPE_CYLINDER)
vec2 intersectUnitCylinder(Ray ray)
{
@@ -616,151 +764,54 @@ vec2 intersectCylinderShape(Ray ray)
}
#endif
-#if defined(SHAPE_ELLIPSOID)
-vec2 intersectUnitSphere(Ray ray)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
-
- float b = dot(d, o);
- float c = dot(o, o) - 1.0;
- float det = b * b - c;
-
- if (det < 0.0) {
- return vec2(NO_HIT, NO_HIT);
- }
-
- det = sqrt(det);
- float t1 = -b - det;
- float t2 = -b + det;
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
-
- return vec2(tmin, tmax);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID)
-vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
-
- float a = dot(d, d);
- float b = dot(d, o);
- float c = dot(o, o) - 1.0;
- float det = b * b - a * c;
-
- if (det < 0.0) {
- return vec2(NO_HIT, NO_HIT);
- }
-
- det = sqrt(det);
- float t1 = (-b - det) / a;
- float t2 = (-b + det) / a;
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
-
- return vec2(tmin, tmax);
-}
-#endif
+vec2 intersectShape(vec3 positionUv, vec3 directionUv) {
+ // Do a ray-shape intersection to find the exact starting and ending points.
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ Ray ray = Ray(positionUv * 2.0 - 1.0, directionUv * 2.0);
-#if defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_1_MIN) || defined(BOUNDS_1_MAX))
-// TODO: can angle and direction be folded into the same parameter
-vec2 intersectUncappedCone(Ray ray, float angle)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
- float s = sign(angle);
- float h = cos(abs(angle));
- float hh = h * h;
-
- float ds = d.z * s;
- float os = o.z * s;
- float dd = dot(d, d);
- float od = dot(o, d);
- float oo = dot(o, o);
+ #if defined(SHAPE_BOX)
+ vec2 entryExitT = intersectBoxShape(ray);
+ #elif defined(SHAPE_ELLIPSOID)
+ vec2 entryExitT = intersectEllipsoidShape(ray);
+ #elif defined(SHAPE_CYLINDER)
+ vec2 entryExitT = intersectCylinderShape(ray);
+ #endif
- float a = ds * ds - dd * hh;
- float b = ds * os - od * hh;
- float c = os * os - oo * hh;
- float det = b * b - a * c;
-
- if (det < 0.0) {
+ if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
+ // Intersection is invalid when start and end are behind the ray.
return vec2(NO_HIT, NO_HIT);
}
- det = sqrt(det);
- float t1 = (-b - det) / a;
- float t2 = (-b + det) / a;
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
+ // Set start to 0 when ray is inside the shape.
+ entryExitT.x = max(entryExitT.x, 0.0);
- float h1 = (o.z + tmin * d.z) * s;
- float h2 = (o.z + tmax * d.z) * s;
-
- if (h1 < 0.0 && h2 < 0.0) {
- return vec2(NO_HIT, NO_HIT);
- }
+ return entryExitT;
+}
- else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
- else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
- else return vec2(tmin, tmax);
+#if defined(DEPTH_TEST)
+float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 viewPosUv, vec3 viewDirUv) {
+ float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenUv));
+ if (logDepthOrDepth != 0.0) {
+ // Calculate how far the ray must travel before it hits the depth buffer.
+ vec4 eyeCoordinateDepth = czm_windowToEyeCoordinates(fragCoord, logDepthOrDepth);
+ eyeCoordinateDepth /= eyeCoordinateDepth.w;
+ vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
+ return dot(viewDirUv, depthPositionUv - viewPosUv);
+ } else {
+ // There's no depth at this position so set it to some really far value.
+ return czm_infinity;
+ }
}
#endif
-#if defined(SHAPE_ELLIPSOID)
-vec2 intersectEllipsoidShape(Ray ray)
-{
- #if !defined(BOUNDS)
- return intersectUnitSphereUnnormalizedDirection(ray);
- #else
- float lonMin = u_minBounds.x; // [-pi,+pi]
- float lonMax = u_maxBounds.x; // [-pi,+pi]
- float latMin = u_minBounds.y; // [-halfPi,+halfPi]
- float latMax = u_maxBounds.y; // [-halfPi,+halfPi]
- float heightMin = u_minBounds.z; // [-inf,+inf]
- float heightMax = u_maxBounds.z; // [-inf,+inf]
-
- vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- if (outerIntersect == vec2(NO_HIT, NO_HIT)) {
- return vec2(NO_HIT, NO_HIT);
- }
-
- vec2 intersections[SHAPE_INTERSECTION_COUNT];
- intersections[0] = vec2(float(0), outerIntersect.x);
- intersections[1] = vec2(float(1), outerIntersect.y);
-
- #if defined(BOUNDS_2_MIN)
- Ray innerRay = Ray(ray.pos * u_ellipsoidInverseHeightDifferenceUv, ray.dir * u_ellipsoidInverseHeightDifferenceUv);
- vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- intersections[2] = vec2(float(2), innerIntersect.x);
- intersections[3] = vec2(float(3), innerIntersect.y);
- #endif
-
- #if defined(BOUNDS_1_MIN)
- float halfAngleMin = sign(latMin) * (czm_piOverTwo - abs(latMin));
- vec2 botConeIntersect = intersectUncappedCone(ray, halfAngleMin);
- intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
- intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
- #endif
-
- #if defined(BOUNDS_1_MAX)
- float halfAngleMax = sign(latMax) * (czm_piOverTwo - abs(latMax));
- vec2 topConeIntersect = intersectUncappedCone(ray, halfAngleMax);
- intersections[BOUNDS_1_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 0), topConeIntersect.x);
- intersections[BOUNDS_1_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 1), topConeIntersect.y);
- #endif
-
- #if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
- vec2 wedgeIntersect = intersectWedge(ray, lonMin, lonMax);
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
- #endif
-
- return resolveIntersections(intersections);
- // return vec2(0.0);
+#if defined(SHAPE_BOX)
+vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
+ vec3 positionShape = positionUv;
+ #if defined(BOUNDS)
+ positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
#endif
+ return positionShape;
}
#endif
@@ -768,7 +819,6 @@ vec2 intersectEllipsoidShape(Ray ray)
// robust iterative solution without trig functions
// https://github.com/0xfaded/ellipse_demo/issues/1
// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
-
float ellipseDistanceIterative (vec2 p, in vec2 ab) {
float px = abs(p[0]);
float py = abs(p[1]);
@@ -809,57 +859,6 @@ float ellipseDistanceIterative (vec2 p, in vec2 ab) {
}
#endif
-vec2 intersectShape(vec3 positionUv, vec3 directionUv) {
- // Do a ray-shape intersection to find the exact starting and ending points.
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- Ray ray = Ray(positionUv * 2.0 - 1.0, directionUv * 2.0);
-
- #if defined(SHAPE_BOX)
- vec2 entryExitT = intersectBoxShape(ray);
- #elif defined(SHAPE_CYLINDER)
- vec2 entryExitT = intersectCylinderShape(ray);
- #elif defined(SHAPE_ELLIPSOID)
- vec2 entryExitT = intersectEllipsoidShape(ray);
- #endif
-
- if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
- // Intersection is invalid when start and end are behind the ray.
- return vec2(NO_HIT, NO_HIT);
- }
-
- // Set start to 0 when ray is inside the shape.
- entryExitT.x = max(entryExitT.x, 0.0);
-
- return entryExitT;
-}
-
-#if defined(DEPTH_TEST)
-float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 viewPosUv, vec3 viewDirUv) {
- float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenUv));
- if (logDepthOrDepth != 0.0) {
- // Calculate how far the ray must travel before it hits the depth buffer.
- vec4 eyeCoordinateDepth = czm_windowToEyeCoordinates(fragCoord, logDepthOrDepth);
- eyeCoordinateDepth /= eyeCoordinateDepth.w;
- vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
- return dot(viewDirUv, depthPositionUv - viewPosUv);
- } else {
- // There's no depth at this position so set it to some really far value.
- return czm_infinity;
- }
-}
-#endif
-
-#if defined(SHAPE_BOX)
-vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
- vec3 positionShape = positionUv;
- #if defined(BOUNDS)
- positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
- #endif
- return positionShape;
-}
-#endif
-
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
// 1) Convert positionUv [0,1] to unit ellipsoid space [-1,+1].
@@ -923,7 +922,6 @@ vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
return positionShape;
}
-
// --------------------------------------------------------
// Megatexture
// --------------------------------------------------------
From 1b76f5961437c4b46d0e7fc960f32312c237859e Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 6 Apr 2022 12:16:26 -0400
Subject: [PATCH 013/679] better overturned cone and also better uv to
ellipsoid space
---
Source/Scene/VoxelPrimitive.js | 53 +++++++++++++---------
Source/Shaders/VoxelFS.glsl | 82 +++++++++++++++++++---------------
2 files changed, 78 insertions(+), 57 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index fb5b2834158..a97a02bd85e 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -415,8 +415,10 @@ function VoxelPrimitive(options) {
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
stepSize: 1.0,
ellipsoidInverseHeightDifferenceUv: 1.0,
- ellipsoidOuterRadiiLocal: new Cartesian3(),
- ellipsoidInverseRadiiSquaredLocal: new Cartesian3(),
+ ellipsoidInverseInnerScaleUv: 1.0,
+ ellipsoidRadiiUv: new Cartesian3(),
+ ellipsoidInnerRadiiUv: new Cartesian3(),
+ ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
minBounds: new Cartesian3(),
maxBounds: new Cartesian3(),
minBoundsUv: new Cartesian3(),
@@ -1357,29 +1359,40 @@ VoxelPrimitive.prototype.update = function (frameState) {
);
const minHeight = minBounds.z;
const maxHeight = maxBounds.z;
- // const minRadius = Cartesian3.minimumComponent(radii);
- const maxRadius = Cartesian3.maximumComponent(radii);
- uniforms.ellipsoidOuterRadiiLocal = Cartesian3.fromElements(
- (radii.x + maxHeight) / (maxRadius + maxHeight),
- (radii.y + maxHeight) / (maxRadius + maxHeight),
- (radii.z + maxHeight) / (maxRadius + maxHeight),
- uniforms.ellipsoidOuterRadiiLocal
+ // The farthest distance a point can be from the center of the ellipsoid.
+ const maxExtent = Cartesian3.maximumComponent(radii) + maxHeight;
+ // The percent of space that is between the inner and outer ellipsoid
+ const thickness = (maxHeight - minHeight) / maxExtent;
+ // The percent of space that is taken up by the inner ellipsoid.
+ const innerScale = 1.0 - thickness;
+
+ // The ellipsoid radii scaled to [0,1]. The max ellipsoid radius will be 1.0 and others will be less.
+ uniforms.ellipsoidRadiiUv = Cartesian3.fromElements(
+ (radii.x + maxHeight) / maxExtent,
+ (radii.y + maxHeight) / maxExtent,
+ (radii.z + maxHeight) / maxExtent,
+ uniforms.ellipsoidRadiiUv
);
- uniforms.ellipsoidInverseRadiiSquaredLocal = Cartesian3.divideComponents(
+
+ // The inner ellipsoid radii scaled to [0,innerScale]. The max inner ellipsoid radius will be innerScale and others will be less.
+ uniforms.ellipsoidInnerRadiiUv = Cartesian3.multiplyByScalar(
+ uniforms.ellipsoidRadiiUv,
+ innerScale,
+ uniforms.ellipsoidInnerRadiiUv
+ );
+
+ // Used to compute geodetic surface normal.
+ uniforms.ellipsoidInverseRadiiSquaredUv = Cartesian3.divideComponents(
Cartesian3.ONE,
Cartesian3.multiplyComponents(
- uniforms.ellipsoidOuterRadiiLocal,
- uniforms.ellipsoidOuterRadiiLocal,
- uniforms.ellipsoidInverseRadiiSquaredLocal
+ uniforms.ellipsoidRadiiUv,
+ uniforms.ellipsoidRadiiUv,
+ uniforms.ellipsoidInverseRadiiSquaredUv
),
- uniforms.ellipsoidInverseRadiiSquaredLocal
+ uniforms.ellipsoidInverseRadiiSquaredUv
);
-
- // TODO: not sure if this is accurate. It could just as well be
- // (minRadius + minHeight) / (minRadius + maxHeight). Better approach might
- // be to get height relative to inner ellipsoid.
- uniforms.ellipsoidInverseHeightDifferenceUv =
- (maxRadius + maxHeight) / (maxRadius + minHeight);
+ uniforms.ellipsoidInverseHeightDifferenceUv = 1.0 / thickness;
+ uniforms.ellipsoidInverseInnerScaleUv = 1.0 / innerScale;
}
// Math that's only valid if the shape is visible.
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 30c942a9da1..37c3a0db361 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -183,8 +183,10 @@ uniform vec3 u_maxClippingBounds;
#if defined(SHAPE_ELLIPSOID)
uniform float u_ellipsoidInverseHeightDifferenceUv;
-uniform vec3 u_ellipsoidOuterRadiiLocal; // [0,1]
-uniform vec3 u_ellipsoidInverseRadiiSquaredLocal;
+uniform float u_ellipsoidInverseInnerScaleUv;
+uniform vec3 u_ellipsoidRadiiUv; // [0,1]
+uniform vec3 u_ellipsoidInnerRadiiUv; // [0,1]
+uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
#endif
#if defined(PICKING)
@@ -501,12 +503,14 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
#endif
#if defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_1_MIN) || defined(BOUNDS_1_MAX))
-// TODO: can angle and direction be folded into the same parameter
-vec2 intersectUncappedCone(Ray ray, float angle)
+vec2 intersectUncappedCone(Ray ray, float angle, float side)
{
+ if (angle < 0.0) {
+ side *= -1.0;
+ }
vec3 o = ray.pos;
vec3 d = ray.dir;
- float s = sign(angle);
+ float s = sign(side);
float h = cos(abs(angle));
float hh = h * h;
@@ -522,7 +526,11 @@ vec2 intersectUncappedCone(Ray ray, float angle)
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NO_HIT, NO_HIT);
+ if (angle > 0.0) {
+ return vec2(NO_HIT, NO_HIT);
+ } else {
+ return vec2(-INF_HIT, +INF_HIT);
+ }
}
det = sqrt(det);
@@ -533,14 +541,19 @@ vec2 intersectUncappedCone(Ray ray, float angle)
float h1 = (o.z + tmin * d.z) * s;
float h2 = (o.z + tmax * d.z) * s;
-
- if (h1 < 0.0 && h2 < 0.0) {
- return vec2(NO_HIT, NO_HIT);
- }
- else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
- else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
- else return vec2(tmin, tmax);
+ if (angle > 0.0) {
+ if (h1 < 0.0 && h2 < 0.0) return vec2(NO_HIT, NO_HIT);
+ else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
+ else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
+ else return vec2(tmin, tmax);
+ } else {
+ if (h1 < 0.0 && h2 < 0.0) return vec2(-INF_HIT, +INF_HIT);
+ else if (h1 < 0.0) return vec2(-INF_HIT, tmax);
+ else if (h2 < 0.0) return vec2(tmin, +INF_HIT);
+ else if (tmin < 0.0) return vec2(tmax, +INF_HIT);
+ else return vec2(-INF_HIT, tmin);
+ }
}
#endif
@@ -567,7 +580,7 @@ vec2 intersectEllipsoidShape(Ray ray)
intersections[1] = vec2(float(1), outerIntersect.y);
#if defined(BOUNDS_2_MIN)
- Ray innerRay = Ray(ray.pos * u_ellipsoidInverseHeightDifferenceUv, ray.dir * u_ellipsoidInverseHeightDifferenceUv);
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
intersections[2] = vec2(float(2), innerIntersect.x);
intersections[3] = vec2(float(3), innerIntersect.y);
@@ -575,14 +588,14 @@ vec2 intersectEllipsoidShape(Ray ray)
#if defined(BOUNDS_1_MIN)
float halfAngleMin = sign(latMin) * (czm_piOverTwo - abs(latMin));
- vec2 botConeIntersect = intersectUncappedCone(ray, halfAngleMin);
+ vec2 botConeIntersect = intersectUncappedCone(ray, halfAngleMin, -1.0);
intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
#endif
#if defined(BOUNDS_1_MAX)
float halfAngleMax = sign(latMax) * (czm_piOverTwo - abs(latMax));
- vec2 topConeIntersect = intersectUncappedCone(ray, halfAngleMax);
+ vec2 topConeIntersect = intersectUncappedCone(ray, halfAngleMax, +1.0);
intersections[BOUNDS_1_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 0), topConeIntersect.x);
intersections[BOUNDS_1_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 1), topConeIntersect.y);
#endif
@@ -861,33 +874,28 @@ float ellipseDistanceIterative (vec2 p, in vec2 ab) {
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
- // 1) Convert positionUv [0,1] to unit ellipsoid space [-1,+1].
- // 2) Convert from unit ellipsoid space [-1,+1] to local space. Max ellipsoid axis has value 1, anything shorter is < 1.
- // 3) Convert 3d position to 2D point relative to ellipse (since radii.x and radii.y are assumed to be equal for WGS84).
- // 4) Find closest distance. if distance > 1, it's outside the outer shell, if distance < u_ellipsoidMinimumHeightUv, it's inside the inner shell.
- // 5) Compute geodetic surface normal.
- // 6) Compute longitude and latitude from geodetic surface normal.
-
- vec3 posLocal = positionUv * 2.0 - 1.0; // 1
- vec3 pos3D = posLocal * u_ellipsoidOuterRadiiLocal; // 2
- vec2 pos2D = vec2(length(pos3D.xy), pos3D.z); // 3
- float dist = ellipseDistanceIterative(pos2D, u_ellipsoidOuterRadiiLocal.xz); // 4
- dist = 1.0 + dist * u_ellipsoidInverseHeightDifferenceUv; // same as delerp(dist, -u_ellipsoidHeightDifferenceUv, 0);
-
- vec3 normal = normalize(pos3D * u_ellipsoidInverseRadiiSquaredLocal); // 5
- float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi; // 6
- float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi; // 6
+ // 1) Convert positionUv [0,1] to local space [-1,+1] to normalized cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height). A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
+ // 2) Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84). This is an optimization to do math with ellipses instead of ellipsoids.
+ // 3) Compute height from inner ellipse.
+ // 4) Compute geodetic surface normal.
+ // 5) Compute longitude from geodetic surface normal.
+ // 6) Compute latitude from geodetic surface normal.
+ vec3 pos3D = (positionUv * 2.0 - 1.0) * u_ellipsoidRadiiUv; // 1
+ vec2 pos2D = vec2(length(pos3D.xy), pos3D.z); // 2
+ float height = ellipseDistanceIterative(pos2D, u_ellipsoidInnerRadiiUv.xz); // 3
+ vec3 geodeticSurfaceNormal = normalize(pos3D * u_ellipsoidInverseRadiiSquaredUv); // 4
+ float longitude = (atan(geodeticSurfaceNormal.y, geodeticSurfaceNormal.x) + czm_pi) / czm_twoPi; // 5
+ float latitude = (asin(geodeticSurfaceNormal.z) + czm_piOverTwo) / czm_pi; // 6
#if defined(BOUNDS)
+ height *= u_ellipsoidInverseHeightDifferenceUv;
float minLongitude = u_minBoundsUv.x;
- float maxLongitude = u_maxBoundsUv.x;
float minLatitude = u_minBoundsUv.y;
- float maxLatitude = u_minBoundsUv.y;
- longitude = (longitude - minLongitude) / (maxLongitude - minLongitude);
- latitude = (latitude - minLatitude) / (maxLatitude - minLatitude);
+ longitude = (longitude - minLongitude) * u_inverseBoundsUv.x;
+ latitude = (latitude - minLatitude) * u_inverseBoundsUv.y;
#endif
- return vec3(longitude, latitude, dist);
+ return vec3(longitude, latitude, height);
}
#endif
From 9e5adaeb153a61863cced475f42a2b6b8f3045dc Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 6 Apr 2022 15:00:33 -0400
Subject: [PATCH 014/679] simplified the iterative ellipse distance
---
Source/Shaders/VoxelFS.glsl | 106 ++++++++++++++++++++++++------------
1 file changed, 70 insertions(+), 36 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 37c3a0db361..e7aadb7658f 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -224,7 +224,9 @@ float hash(vec2 p)
return fract((p3.x + p3.y) * p3.z);
}
#endif
-
+float signNoZero(float v) {
+ return (v < 0.0) ? -1.0 : 1.0;
+}
int intMod(int a, int b) {
return a - (b * (a / b));
}
@@ -832,50 +834,82 @@ vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
// robust iterative solution without trig functions
// https://github.com/0xfaded/ellipse_demo/issues/1
// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
-float ellipseDistanceIterative (vec2 p, in vec2 ab) {
- float px = abs(p[0]);
- float py = abs(p[1]);
-
- float tx = 0.707;
- float ty = 0.707;
-
- float a = ab.x;
- float b = ab.y;
-
- for (int i = 0; i < 3; i++) {
- float x = a * tx;
- float y = b * ty;
-
- float ex = (a*a - b*b) * pow(tx, 3.0) / a;
- float ey = (b*b - a*a) * pow(ty, 3.0) / b;
-
- float rx = x - ex;
- float ry = y - ey;
-
- float qx = px - ex;
- float qy = py - ey;
-
- float r = sqrt(ry * ry + rx * rx);
- float q = sqrt(qy * qy + qx * qx);
+// Pro: Good when radii.x ~= radii.y
+// Con: Breaks at pos.x ~= 0.0, especially inside the ellipse
+// Con: Inaccurate with exterior points and thin ellipses
+float ellipseDistanceIterative (vec2 pos, vec2 radii) {
+ vec2 p = abs(pos);
+ vec2 invRadii = 1.0 / radii;
+ vec2 a = vec2(1.0, -1.0) * (radii.x * radii.x - radii.y * radii.y) * invRadii;
+ vec2 t = vec2(0.70710678118); // sqrt(2) / 2
+ vec2 v = radii * t;
+
+ const int iterations = 3;
+ for (int i = 0; i < iterations; i++) {
+ vec2 e = a * pow(t, vec2(3.0));
+ vec2 q = normalize(p - e) * length(v - e);
+ t = normalize((q + e) * invRadii);
+ v = radii * t;
+ }
+ return length(v * sign(pos) - pos) * sign(p.y - v.y);
+}
+#endif
- tx = clamp((qx * r / q + ex) / a, 0.0, 1.0);
- ty = clamp((qy * r / q + ey) / b, 0.0, 1.0);
- float t = sqrt(ty * ty + tx * tx);
- tx /= t;
- ty /= t;
+#if defined(SHAPE_ELLIPSOID)
+// From: https://www.shadertoy.com/view/4sS3zz
+// Pro: Accurate in most cases
+// Con: Breaks if radii.x ~= radii.y
+float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
+ vec2 p = pos;
+ vec2 ab = radii;
+
+ p = abs(p);
+ if (p.x > p.y) {
+ p = p.yx;
+ ab = ab.yx;
+ }
+
+ float l = ab.y * ab.y - ab.x * ab.x;
+ float m = ab.x * p.x / l;
+ float n = ab.y * p.y / l;
+ float m2 = m * m;
+ float n2 = n * n;
+ float c = (m2 + n2 - 1.0) / 3.0;
+ float c3 = c * c * c;
+ float d = c3 + m2 * n2;
+ float q = d + m2 * n2;
+ float g = m + m * n2;
+
+ float co;
+
+ if (d < 0.0) {
+ float h = acos(q / c3) / 3.0;
+ float s = cos(h) + 2.0;
+ float t = sin(h) * sqrt(3.0);
+ float rx = sqrt(m2 - c * (s + t));
+ float ry = sqrt(m2 - c * (s - t));
+ co = ry + sign(l) * rx + abs(g) / (rx * ry);
+ } else {
+ float h = 2.0 * m * n * sqrt(d);
+ float s = signNoZero(q + h) * pow(abs(q + h), 1.0 / 3.0);
+ float t = signNoZero(q - h) * pow(abs(q - h), 1.0 / 3.0);
+ float rx = -(s + t) - c * 4.0 + 2.0 * m2;
+ float ry = (s - t) * sqrt(3.0);
+ float rm = sqrt(rx * rx + ry * ry);
+ co = ry / sqrt(rm - rx) + 2.0 * g / rm;
}
- float cX = a * tx;
- float cY = b * ty;
- vec2 pos = vec2(cX * sign(p[0]), cY * sign(p[1]));
- return length(pos - p) * sign(py - cY);
+ co = (co - m) / 2.0;
+ float si = sqrt(max(1.0 - co * co, 0.0));
+ vec2 r = ab * vec2(co, si);
+ return length(r - p) * signNoZero(p.y - r.y);
}
#endif
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
// 1) Convert positionUv [0,1] to local space [-1,+1] to normalized cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height). A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
- // 2) Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84). This is an optimization to do math with ellipses instead of ellipsoids.
+ // 2) Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84). This is an optimization so that math can be done with ellipses instead of ellipsoids.
// 3) Compute height from inner ellipse.
// 4) Compute geodetic surface normal.
// 5) Compute longitude from geodetic surface normal.
From 5743dc8258d8b0abd75d00e2ab9d04323ef500b0 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 6 Apr 2022 17:14:23 -0400
Subject: [PATCH 015/679] sanitizing bounds
---
Source/Scene/VoxelPrimitive.js | 214 +++++++++++++-----
.../Widgets/VoxelInspector/VoxelInspector.js | 3 +-
2 files changed, 156 insertions(+), 61 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index a97a02bd85e..e7a76ea8f5b 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1193,15 +1193,79 @@ VoxelPrimitive.prototype.update = function (frameState) {
compoundModelMatrix,
compoundModelMatrixOld
);
+
const shape = this._shape;
const shapeType = provider.shape;
-
+ const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
+ const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
const minBounds = this._minBounds;
const maxBounds = this._maxBounds;
+
+ let isDefaultBoundsMin =
+ minBounds.x === defaultMinBounds.x &&
+ minBounds.y === defaultMinBounds.y &&
+ minBounds.z === defaultMinBounds.z;
+
+ let isDefaultBoundsMax =
+ maxBounds.x === defaultMaxBounds.x &&
+ maxBounds.y === defaultMaxBounds.y &&
+ maxBounds.z === defaultMaxBounds.z;
+
+ // Clamp the min bounds to the valid range.
+ if (!isDefaultBoundsMin) {
+ minBounds.x = CesiumMath.clamp(
+ minBounds.x,
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
+ minBounds.y = CesiumMath.clamp(
+ minBounds.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
+ );
+ if (shapeType !== VoxelShapeType.ELLIPSOID) {
+ minBounds.z = CesiumMath.clamp(
+ minBounds.z,
+ defaultMinBounds.z,
+ defaultMaxBounds.z
+ );
+ }
+ isDefaultBoundsMin =
+ minBounds.x === defaultMinBounds.x &&
+ minBounds.y === defaultMinBounds.y &&
+ minBounds.z === defaultMinBounds.z;
+ }
+
+ // Clamp the max bounds to the valid range.
+ if (!isDefaultBoundsMax) {
+ maxBounds.x = CesiumMath.clamp(
+ maxBounds.x,
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
+ maxBounds.y = CesiumMath.clamp(
+ maxBounds.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
+ );
+ if (shapeType !== VoxelShapeType.ELLIPSOID) {
+ maxBounds.z = CesiumMath.clamp(
+ maxBounds.z,
+ defaultMinBounds.z,
+ defaultMaxBounds.z
+ );
+ }
+ isDefaultBoundsMax =
+ maxBounds.x === defaultMaxBounds.x &&
+ maxBounds.y === defaultMaxBounds.y &&
+ maxBounds.z === defaultMaxBounds.z;
+ }
+
const minBoundsOld = this._minBoundsOld;
const maxBoundsOld = this._maxBoundsOld;
const minBoundsDirty = !Cartesian3.equals(minBounds, minBoundsOld);
const maxBoundsDirty = !Cartesian3.equals(maxBounds, maxBoundsOld);
+
const shapeIsDirty =
compoundModelMatrixDirty || minBoundsDirty || maxBoundsDirty;
@@ -1217,23 +1281,15 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
if (minBoundsDirty || maxBoundsDirty) {
- const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
- const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
- const isDefaultBoundsMinX = minBounds.x === defaultMinBounds.x;
- const isDefaultBoundsMinY = minBounds.y === defaultMinBounds.y;
- const isDefaultBoundsMinZ = minBounds.z === defaultMinBounds.z;
- const isDefaultBoundsMaxX = maxBounds.x === defaultMaxBounds.x;
- const isDefaultBoundsMaxY = maxBounds.y === defaultMaxBounds.y;
- const isDefaultBoundsMaxZ = maxBounds.z === defaultMaxBounds.z;
-
if (minBoundsDirty) {
- const isDefaultOldBoundsMinX = minBoundsOld.x === defaultMinBounds.x;
- const isDefaultOldBoundsMinY = minBoundsOld.y === defaultMinBounds.y;
- const isDefaultOldBoundsMinZ = minBoundsOld.z === defaultMinBounds.z;
+ // Check if the min bounds became default or stopped being default
if (
- isDefaultBoundsMinX !== isDefaultOldBoundsMinX ||
- isDefaultBoundsMinY !== isDefaultOldBoundsMinY ||
- isDefaultBoundsMinZ !== isDefaultOldBoundsMinZ
+ (minBounds.x === defaultMinBounds.x) !==
+ (minBoundsOld.x === defaultMinBounds.x) ||
+ (minBounds.y === defaultMinBounds.y) !==
+ (minBoundsOld.y === defaultMinBounds.y) ||
+ (minBounds.z === defaultMinBounds.z) !==
+ (minBoundsOld.z === defaultMinBounds.z)
) {
this._shaderDirty = true;
}
@@ -1241,13 +1297,14 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
if (maxBoundsDirty) {
- const isDefaultOldBoundsMaxX = maxBoundsOld.x === defaultMaxBounds.x;
- const isDefaultOldBoundsMaxY = maxBoundsOld.y === defaultMaxBounds.y;
- const isDefaultOldBoundsMaxZ = maxBoundsOld.z === defaultMaxBounds.z;
+ // Check if the max bounds became default or stopped being default
if (
- isDefaultBoundsMaxX !== isDefaultOldBoundsMaxX ||
- isDefaultBoundsMaxY !== isDefaultOldBoundsMaxY ||
- isDefaultBoundsMaxZ !== isDefaultOldBoundsMaxZ
+ (maxBounds.x === defaultMaxBounds.x) !==
+ (maxBoundsOld.x === defaultMaxBounds.x) ||
+ (maxBounds.y === defaultMaxBounds.y) !==
+ (maxBoundsOld.y === defaultMaxBounds.y) ||
+ (maxBounds.z === defaultMaxBounds.z) !==
+ (maxBoundsOld.z === defaultMaxBounds.z)
) {
this._shaderDirty = true;
}
@@ -1255,14 +1312,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
// Set uniforms for bounds.
- if (
- !isDefaultBoundsMinX ||
- !isDefaultBoundsMinY ||
- !isDefaultBoundsMinZ ||
- !isDefaultBoundsMaxX ||
- !isDefaultBoundsMaxY ||
- !isDefaultBoundsMaxZ
- ) {
+ if (!isDefaultBoundsMin || !isDefaultBoundsMax) {
uniforms.minBounds = Cartesian3.clone(minBounds, uniforms.minBounds);
uniforms.maxBounds = Cartesian3.clone(maxBounds, uniforms.maxBounds);
uniforms.inverseBounds = Cartesian3.divideComponents(
@@ -1603,29 +1653,82 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Process clipping bounds.
const minClip = this._minClippingBounds;
const maxClip = this._maxClippingBounds;
+
+ let isDefaultClippingBoundsMin =
+ minClip.x === defaultMinBounds.x &&
+ minClip.y === defaultMinBounds.y &&
+ minClip.z === defaultMinBounds.z;
+
+ let isDefaultClippingBoundsMax =
+ maxClip.x === defaultMaxBounds.x &&
+ maxClip.y === defaultMaxBounds.y &&
+ maxClip.z === defaultMaxBounds.z;
+
+ // Clamp the min bounds to the valid range.
+ if (!isDefaultClippingBoundsMin) {
+ minClip.x = CesiumMath.clamp(
+ minClip.x,
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
+ minClip.y = CesiumMath.clamp(
+ minClip.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
+ );
+ if (shapeType !== VoxelShapeType.ELLIPSOID) {
+ minClip.z = CesiumMath.clamp(
+ minClip.z,
+ defaultMinBounds.z,
+ defaultMaxBounds.z
+ );
+ }
+ isDefaultClippingBoundsMin =
+ minClip.x === defaultMinBounds.x &&
+ minClip.y === defaultMinBounds.y &&
+ minClip.z === defaultMinBounds.z;
+ }
+
+ // Clamp the max bounds to the valid range.
+ if (!isDefaultClippingBoundsMax) {
+ maxClip.x = CesiumMath.clamp(
+ maxClip.x,
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
+ maxClip.y = CesiumMath.clamp(
+ maxClip.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
+ );
+ if (shapeType !== VoxelShapeType.ELLIPSOID) {
+ maxClip.z = CesiumMath.clamp(
+ maxClip.z,
+ defaultMinBounds.z,
+ defaultMaxBounds.z
+ );
+ }
+ isDefaultClippingBoundsMax =
+ maxClip.x === defaultMaxBounds.x &&
+ maxClip.y === defaultMaxBounds.y &&
+ maxClip.z === defaultMaxBounds.z;
+ }
+
const minClipOld = this._minClippingBoundsOld;
const maxClipOld = this._maxClippingBoundsOld;
const minClipDirty = !Cartesian3.equals(minClip, minClipOld);
const maxClipDirty = !Cartesian3.equals(maxClip, maxClipOld);
const clippingBoundsDirty = minClipDirty || maxClipDirty;
- if (clippingBoundsDirty) {
- const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
- const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
- const isDefaultClippingBoundsMinX = minClip.x === defaultMinBounds.x;
- const isDefaultClippingBoundsMinY = minClip.y === defaultMinBounds.y;
- const isDefaultClippingBoundsMinZ = minClip.z === defaultMinBounds.z;
- const isDefaultClippingBoundsMaxX = maxClip.x === defaultMaxBounds.x;
- const isDefaultClippingBoundsMaxY = maxClip.y === defaultMaxBounds.y;
- const isDefaultClippingBoundsMaxZ = maxClip.z === defaultMaxBounds.z;
+ if (clippingBoundsDirty) {
if (minClipDirty) {
- const isDefaultOldClipMinX = minClipOld.x === defaultMinBounds.x;
- const isDefaultOldClipMinY = minClipOld.y === defaultMinBounds.y;
- const isDefaultOldClipMinZ = minClipOld.z === defaultMinBounds.z;
if (
- isDefaultClippingBoundsMinX !== isDefaultOldClipMinX ||
- isDefaultClippingBoundsMinY !== isDefaultOldClipMinY ||
- isDefaultClippingBoundsMinZ !== isDefaultOldClipMinZ
+ (minClip.x === defaultMinBounds.x) !==
+ (minClipOld.x === defaultMinBounds.x) ||
+ (minClip.y === defaultMinBounds.y) !==
+ (minClipOld.y === defaultMinBounds.y) ||
+ (minClip.z === defaultMinBounds.z) !==
+ (minClipOld.z === defaultMinBounds.z)
) {
this._shaderDirty = true;
}
@@ -1635,13 +1738,13 @@ VoxelPrimitive.prototype.update = function (frameState) {
);
}
if (maxClipDirty) {
- const isDefaultOldClipMaxX = maxClipOld.x === defaultMaxBounds.x;
- const isDefaultOldClipMaxY = maxClipOld.y === defaultMaxBounds.y;
- const isDefaultOldClipMaxZ = maxClipOld.z === defaultMaxBounds.z;
if (
- isDefaultClippingBoundsMaxX !== isDefaultOldClipMaxX ||
- isDefaultClippingBoundsMaxY !== isDefaultOldClipMaxY ||
- isDefaultClippingBoundsMaxZ !== isDefaultOldClipMaxZ
+ (maxClip.x === defaultMaxBounds.x) !==
+ (maxClipOld.x === defaultMaxBounds.x) ||
+ (maxClip.y === defaultMaxBounds.y) !==
+ (maxClipOld.y === defaultMaxBounds.y) ||
+ (maxClip.z === defaultMaxBounds.z) !==
+ (maxClipOld.z === defaultMaxBounds.z)
) {
this._shaderDirty = true;
}
@@ -1650,14 +1753,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
this._maxClippingBoundsOld
);
}
- if (
- !isDefaultClippingBoundsMinX ||
- !isDefaultClippingBoundsMinY ||
- !isDefaultClippingBoundsMinZ ||
- !isDefaultClippingBoundsMaxX ||
- !isDefaultClippingBoundsMaxY ||
- !isDefaultClippingBoundsMaxZ
- ) {
+ if (!isDefaultClippingBoundsMin || !isDefaultClippingBoundsMax) {
// Set clipping uniforms
uniforms.minClippingBounds = Cartesian3.clone(
minClip,
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
index 2c1e327db37..a72f841a50a 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspector.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -343,7 +343,6 @@ VoxelInspector.prototype.destroy = function () {
};
function makeRangeInput(text, property, min, max, step, displayProperty) {
- step = defaultValue(step, 0.01);
displayProperty = defaultValue(displayProperty, property);
const input = document.createElement("input");
input.setAttribute("data-bind", `value: ${displayProperty}`);
@@ -353,7 +352,7 @@ function makeRangeInput(text, property, min, max, step, displayProperty) {
slider.type = "range";
slider.min = min;
slider.max = max;
- slider.step = step;
+ slider.step = defaultValue(step, "any");
slider.setAttribute("data-bind", `valueUpdate: "input", value: ${property}`);
const wrapper = document.createElement("div");
From f8558154dd49562fda69f6da93b6cc03e3a1dd3b Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 6 Apr 2022 18:20:24 -0400
Subject: [PATCH 016/679] fixed problem with min latitude
---
Apps/Sandcastle/gallery/Voxels.html | 4 ++--
Source/Shaders/VoxelFS.glsl | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index 46f75d97863..75b43ae980f 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -60,9 +60,9 @@
);
const west = minBounds.x;
const east = maxBounds.x;
- // const south = -0.5;
+ const south = -Cesium.Math.PI_OVER_TWO + 0.1;
const north = Cesium.Math.PI_OVER_TWO - 0.1;
- const south = minBounds.y;
+ // const south = minBounds.y;
// const north = maxBounds.y;
const minimumHeight = 1000000.0;
const maximumHeight = 2000000.0;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index e7aadb7658f..3a52d273176 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -589,7 +589,7 @@ vec2 intersectEllipsoidShape(Ray ray)
#endif
#if defined(BOUNDS_1_MIN)
- float halfAngleMin = sign(latMin) * (czm_piOverTwo - abs(latMin));
+ float halfAngleMin = -sign(latMin) * (czm_piOverTwo - abs(latMin));
vec2 botConeIntersect = intersectUncappedCone(ray, halfAngleMin, -1.0);
intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
From f846978bb27731098e1a497ff6faabbd3285138f Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 6 Apr 2022 20:54:01 -0400
Subject: [PATCH 017/679] one less param for cone intersect function
---
Source/Shaders/VoxelFS.glsl | 58 ++++++++++++++++++++++++-------------
1 file changed, 38 insertions(+), 20 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 3a52d273176..1d05d5024f0 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -505,30 +505,46 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
#endif
#if defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_1_MIN) || defined(BOUNDS_1_MAX))
-vec2 intersectUncappedCone(Ray ray, float angle, float side)
+vec2 intersectUncappedCone(Ray ray, float latitude)
{
- if (angle < 0.0) {
- side *= -1.0;
- }
+ float side = sign(latitude);
+ float halfAngle = czm_piOverTwo - abs(latitude);
+
vec3 o = ray.pos;
vec3 d = ray.dir;
- float s = sign(side);
- float h = cos(abs(angle));
+ if (side < 0.0) {
+ o.z *= -1.0;
+ d.z *= -1.0;
+ }
+
+ float h = cos(halfAngle);
float hh = h * h;
-
- float ds = d.z * s;
- float os = o.z * s;
float dd = dot(d, d);
float od = dot(o, d);
float oo = dot(o, o);
- float a = ds * ds - dd * hh;
- float b = ds * os - od * hh;
- float c = os * os - oo * hh;
+ // if (abs(normalize(o).z - h) < 0.1 && abs(d.z - h) < 0.1) {
+ // if (angle > 0.0) {
+ // // if (o.z * s < 0.0) {
+ // // return vec2(0.0, +INF_HIT);
+ // // } else {
+ // // return vec2(-o.z / d.z, 0.0);
+ // // // return (o.z + x * d.z) * s = 0
+ // // }
+ // return vec2(-INF_HIT, +INF_HIT);
+ // } else {
+ // return vec2(NO_HIT, NO_HIT);
+ // }
+ // // return vec2(-10.0, +10.0);
+ // }
+
+ float a = d.z * d.z - dd * hh;
+ float b = d.z * o.z - od * hh;
+ float c = o.z * o.z - oo * hh;
float det = b * b - a * c;
if (det < 0.0) {
- if (angle > 0.0) {
+ if (side > 0.0) {
return vec2(NO_HIT, NO_HIT);
} else {
return vec2(-INF_HIT, +INF_HIT);
@@ -541,10 +557,10 @@ vec2 intersectUncappedCone(Ray ray, float angle, float side)
float tmin = min(t1, t2);
float tmax = max(t1, t2);
- float h1 = (o.z + tmin * d.z) * s;
- float h2 = (o.z + tmax * d.z) * s;
+ float h1 = o.z + tmin * d.z;
+ float h2 = o.z + tmax * d.z;
- if (angle > 0.0) {
+ if (side > 0.0) {
if (h1 < 0.0 && h2 < 0.0) return vec2(NO_HIT, NO_HIT);
else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
@@ -589,15 +605,17 @@ vec2 intersectEllipsoidShape(Ray ray)
#endif
#if defined(BOUNDS_1_MIN)
- float halfAngleMin = -sign(latMin) * (czm_piOverTwo - abs(latMin));
- vec2 botConeIntersect = intersectUncappedCone(ray, halfAngleMin, -1.0);
+ // Flip the inputs because the intersection function expects a cone growing towards +Z.
+ Ray flippedRay = ray;
+ flippedRay.dir.z *= -1.0;
+ flippedRay.pos.z *= -1.0;
+ vec2 botConeIntersect = intersectUncappedCone(flippedRay, -latMin);
intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
#endif
#if defined(BOUNDS_1_MAX)
- float halfAngleMax = sign(latMax) * (czm_piOverTwo - abs(latMax));
- vec2 topConeIntersect = intersectUncappedCone(ray, halfAngleMax, +1.0);
+ vec2 topConeIntersect = intersectUncappedCone(ray, latMax);
intersections[BOUNDS_1_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 0), topConeIntersect.x);
intersections[BOUNDS_1_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 1), topConeIntersect.y);
#endif
From 3ddeeac7d2b9b15dab9d03179fdc8f1e19bfcc04 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Mon, 11 Apr 2022 09:22:43 -0400
Subject: [PATCH 018/679] temp2
---
Apps/Sandcastle/gallery/Voxels.html | 10 +-
Source/Scene/VoxelBoxShape.js | 216 +++++----
Source/Scene/VoxelEllipsoidShape.js | 357 +++++++++-----
Source/Scene/VoxelPrimitive.js | 719 ++++++++--------------------
Source/Scene/VoxelShape.js | 17 +-
Source/Scene/VoxelTraversal.js | 2 +-
Source/Shaders/VoxelFS.glsl | 691 +++++++++++++++-----------
Source/Shaders/VoxelVS.glsl | 1 +
8 files changed, 968 insertions(+), 1045 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index 75b43ae980f..5d247673b62 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -60,10 +60,12 @@
);
const west = minBounds.x;
const east = maxBounds.x;
- const south = -Cesium.Math.PI_OVER_TWO + 0.1;
- const north = Cesium.Math.PI_OVER_TWO - 0.1;
- // const south = minBounds.y;
- // const north = maxBounds.y;
+ //const west = -Cesium.Math.PI + 0.4;
+ //const east = -Cesium.Math.PI + 0.7;
+ // const south = -Cesium.Math.PI_OVER_TWO + 0.1;
+ // const north = Cesium.Math.PI_OVER_TWO - 0.1;
+ const south = minBounds.y;
+ const north = maxBounds.y;
const minimumHeight = 1000000.0;
const maximumHeight = 2000000.0;
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index e133f03881b..47a5cb8e161 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -52,14 +52,6 @@ function VoxelBoxShape() {
*/
this.shapeTransform = new Matrix4();
- /**
- * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
- * The update function must be called before accessing this value.
- * @type {Boolean}
- * @readonly
- */
- this.isVisible = false;
-
/**
* @type {Cartesian3}
* @private
@@ -77,6 +69,27 @@ function VoxelBoxShape() {
VoxelBoxShape.DefaultMaxBounds,
new Cartesian3()
);
+
+ /**
+ * @type {Object.}
+ * @readonly
+ */
+ this.shaderUniforms = {
+ boxMinBounds: new Cartesian3(),
+ boxMaxBounds: new Cartesian3(),
+ };
+
+ /**
+ * @type {Object.}
+ * @readonly
+ */
+ this.shaderDefines = {
+ BOX_INTERSECTION_COUNT: 1,
+ BOX_BOUNDED: undefined,
+ BOX_XY_PLANE: undefined,
+ BOX_XZ_PLANE: undefined,
+ BOX_YZ_PLANE: undefined,
+ };
}
const scratchTranslation = new Cartesian3();
@@ -89,6 +102,7 @@ const scratchRotation = new Matrix3();
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
+ * @returns {Boolean} Whether the shape is visible.
*/
VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
//>>includeStart('debug', pragmas.debug);
@@ -97,10 +111,29 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
Check.typeOf.object("maxBounds", maxBounds);
//>>includeEnd('debug');
- // Don't render if any of the min bounds exceed the max bounds.
- // Don't render if two of the min bounds equal the max bounds because it would be an invisible line.
- // Don't render if all of the min bounds equal the max bounds because it would be an invisible point.
- // Still render if one of the min bounds is equal to the max bounds because it will be a square/rectangle.
+ const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelBoxShape.DefaultMaxBounds;
+
+ minBounds = this._minBounds = Cartesian3.clamp(
+ minBounds,
+ defaultMinBounds,
+ defaultMaxBounds,
+ this._minBounds
+ );
+
+ maxBounds = this._maxBounds = Cartesian3.clamp(
+ maxBounds,
+ defaultMinBounds,
+ defaultMaxBounds,
+ this._maxBounds
+ );
+
+ const scale = Matrix4.getScale(modelMatrix, scratchScale);
+
+ // Not visible if:
+ // - any of the min bounds exceed the max bounds
+ // - two or more of the min bounds equal the max bounds (line and point respectively)
+ // - scale is 0 for any component
if (
minBounds.x > maxBounds.x ||
minBounds.y > maxBounds.y ||
@@ -108,36 +141,23 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
(minBounds.x === maxBounds.x) +
(minBounds.y === maxBounds.y) +
(minBounds.z === maxBounds.z) >=
- 2
+ 2 ||
+ scale.x === 0.0 ||
+ scale.y === 0.0 ||
+ scale.z === 0.0
) {
- this.isVisible = false;
- return;
+ return false;
}
- // If two or more of the scales are 0 the shape will not render.
- // If one of the scales is 0 it will still be visible but will appear as a square/rectangle.
- const scale = Matrix4.getScale(modelMatrix, scratchScale);
- if ((scale.x === 0.0) + (scale.y === 0.0) + (scale.z === 0.0) >= 2) {
- this.isVisible = false;
- return;
- }
-
- this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
- this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
+ this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
this.orientedBoundingBox = getBoxChunkObb(
- minBounds.x,
- maxBounds.x,
- minBounds.y,
- maxBounds.y,
- minBounds.z,
- maxBounds.z,
- modelMatrix,
+ this._minBounds,
+ this._maxBounds,
+ this.shapeTransform,
this.orientedBoundingBox
);
- this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
-
// All of the box bounds go from -1 to +1, so the model matrix scale can be
// used as the oriented bounding box half axes.
this.boundTransform = Matrix4.fromRotationTranslation(
@@ -151,9 +171,31 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
this.boundingSphere
);
- this.isVisible = true;
+ const shaderUniforms = this.shaderUniforms;
+ const shaderDefines = this.shaderDefines;
+
+ shaderUniforms.minBounds = Cartesian3.clone(
+ minBounds,
+ shaderUniforms.minBounds
+ );
+ shaderUniforms.maxBounds = Cartesian3.clone(
+ maxBounds,
+ shaderUniforms.maxBounds
+ );
+
+ const hasBounds =
+ !Cartesian3.equals(minBounds, defaultMinBounds) &&
+ !Cartesian3.equals(maxBounds, defaultMaxBounds);
+ shaderDefines.BOX_BOUNDS = hasBounds ? 1 : undefined;
+
+ return true;
};
+const scratchMinBounds = new Cartesian3();
+const scratchMaxBounds = new Cartesian3();
+const scratchMinLerp = new Cartesian3();
+const scratchMaxLerp = new Cartesian3();
+
/**
* Computes an oriented bounding box for a specified tile.
* The update function must be called before calling this function.
@@ -180,53 +222,33 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
Check.typeOf.object("result", result);
//>>includeEnd('debug');
- const rootTransform = this.shapeTransform;
const sizeAtLevel = 1.0 / Math.pow(2, tileLevel);
- const minBounds = this._minBounds;
- const maxBounds = this._maxBounds;
-
- const minimumX = CesiumMath.lerp(
- minBounds.x,
- maxBounds.x,
- sizeAtLevel * tileX
- );
- const minimumY = CesiumMath.lerp(
- minBounds.y,
- maxBounds.y,
- sizeAtLevel * tileY
- );
- const minimumZ = CesiumMath.lerp(
- minBounds.z,
- maxBounds.z,
- sizeAtLevel * tileZ
- );
- const maximumX = CesiumMath.lerp(
- minBounds.x,
- maxBounds.x,
- sizeAtLevel * (tileX + 1)
- );
- const maximumY = CesiumMath.lerp(
- minBounds.y,
- maxBounds.y,
- sizeAtLevel * (tileY + 1)
- );
- const maximumZ = CesiumMath.lerp(
- minBounds.z,
- maxBounds.z,
- sizeAtLevel * (tileZ + 1)
+ const minBounds = Cartesian3.lerp(
+ this._minBounds,
+ this._maxBounds,
+ Cartesian3.fromElements(
+ sizeAtLevel * tileX,
+ sizeAtLevel * tileY,
+ sizeAtLevel * tileZ,
+ scratchMinLerp
+ ),
+ scratchMinBounds
);
- return getBoxChunkObb(
- minimumX,
- maximumX,
- minimumY,
- maximumY,
- minimumZ,
- maximumZ,
- rootTransform,
- result
+ const maxBounds = Cartesian3.lerp(
+ this._minBounds,
+ this._maxBounds,
+ Cartesian3.fromElements(
+ sizeAtLevel * (tileX + 1),
+ sizeAtLevel * (tileY + 1),
+ sizeAtLevel * (tileZ + 1),
+ scratchMaxLerp
+ ),
+ scratchMaxBounds
);
+
+ return getBoxChunkObb(minBounds, maxBounds, this.shapeTransform, result);
};
/**
@@ -267,38 +289,21 @@ VoxelBoxShape.DefaultMaxBounds = new Cartesian3(+1.0, +1.0, +1.0);
*
* @function
*
- * @param {Number} minimumX The minimumX.
- * @param {Number} maximumX The maximumX.
- * @param {Number} minimumY The minimumY.
- * @param {Number} maximumY The maximumY.
- * @param {Number} minimumZ The minimumZ.
- * @param {Number} maximumZ The maximumZ.
+ * @param {Number} minimumBounds The minimum bounds.
+ * @param {Number} maximumBounds The maximum bounds.
* @param {Matrix4} matrix The matrix to transform the points.
* @param {OrientedBoundingBox} result The object onto which to store the result.
* @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
*
* @private
*/
-function getBoxChunkObb(
- minimumX,
- maximumX,
- minimumY,
- maximumY,
- minimumZ,
- maximumZ,
- matrix,
- result
-) {
+function getBoxChunkObb(minimumBounds, maximumBounds, matrix, result) {
const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
const defaultMaxBounds = VoxelBoxShape.DefaultMaxBounds;
const isDefaultBounds =
- minimumX === defaultMinBounds.x &&
- minimumY === defaultMinBounds.y &&
- minimumZ === defaultMinBounds.z &&
- maximumX === defaultMaxBounds.x &&
- maximumY === defaultMaxBounds.y &&
- maximumZ === defaultMaxBounds.z;
+ Cartesian3.equals(minimumBounds, defaultMinBounds) &&
+ Cartesian3.equals(maximumBounds, defaultMaxBounds);
if (isDefaultBounds) {
result.center = Matrix4.getTranslation(matrix, result.center);
@@ -307,16 +312,15 @@ function getBoxChunkObb(
let scale = Matrix4.getScale(matrix, scratchScale);
const translation = Matrix4.getTranslation(matrix, scratchTranslation);
result.center = Cartesian3.fromElements(
- translation.x + scale.x * 0.5 * (minimumX + maximumX),
- translation.y + scale.y * 0.5 * (maximumY + minimumY),
- translation.z + scale.z * 0.5 * (maximumZ + minimumZ),
+ translation.x + scale.x * 0.5 * (minimumBounds.x + maximumBounds.x),
+ translation.y + scale.y * 0.5 * (maximumBounds.y + minimumBounds.y),
+ translation.z + scale.z * 0.5 * (maximumBounds.z + minimumBounds.z),
result.center
);
-
scale = Cartesian3.fromElements(
- scale.x * 0.5 * (maximumX - minimumX),
- scale.y * 0.5 * (maximumY - minimumY),
- scale.z * 0.5 * (maximumZ - minimumZ),
+ scale.x * 0.5 * (maximumBounds.x - minimumBounds.x),
+ scale.y * 0.5 * (maximumBounds.y - minimumBounds.y),
+ scale.z * 0.5 * (maximumBounds.z - minimumBounds.z),
scratchScale
);
const rotation = Matrix4.getRotation(matrix, scratchRotation);
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 41a5f5cd728..b8271a59621 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -1,5 +1,7 @@
import BoundingSphere from "../Core/BoundingSphere.js";
+import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
+import Cartesian4 from "../Core/Cartesian4.js";
import Check from "../Core/Check.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import CesiumMath from "../Core/Math.js";
@@ -54,14 +56,6 @@ function VoxelEllipsoidShape() {
*/
this.shapeTransform = new Matrix4();
- /**
- * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
- * The update function must be called before accessing this value.
- * @type {Boolean}
- * @readonly
- */
- this.isVisible = false;
-
/**
* @type {Rectangle}
*/
@@ -94,31 +88,48 @@ function VoxelEllipsoidShape() {
* @type {Matrix3
*/
this._rotation = new Matrix3();
-}
-/**
- * @type {Cartesian3}
- * @private
- */
-VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
- -CesiumMath.PI,
- -CesiumMath.PI_OVER_TWO,
- 0.0
-);
+ /**
+ * @type {Object.}
+ * @readonly
+ */
+ this.shaderUniforms = {
+ ellipsoidRectangle: new Cartesian4(),
+ ellipsoidRadiiUv: new Cartesian3(),
+ ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
+ // Wedge uniforms
+ ellipsoidWestUv: 0.0,
+ ellipsoidInverseLongitudeRangeUv: 0.0,
+ // Cone uniforms
+ ellipsoidSouthUv: 0.0,
+ ellipsoidInverseLatitudeRangeUv: 0.0,
+ // Inner ellipsoid uniforms
+ ellipsoidInverseHeightDifferenceUv: 0.0,
+ ellipsoidInverseInnerScaleUv: 0.0,
+ ellipsoidInnerRadiiUv: new Cartesian3(),
+ };
-/**
- * @type {Cartesian3}
- * @private
- */
-VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
- +CesiumMath.PI,
- +CesiumMath.PI_OVER_TWO,
- 1.0
-);
+ /**
+ * @type {Object.}
+ * @readonly
+ */
+ this.shaderDefines = {
+ ELLIPSOID_WEDGE_REGULAR: undefined,
+ ELLIPSOID_WEDGE_FLIPPED: undefined,
+ ELLIPSOID_CONE_BOTTOM_REGULAR: undefined,
+ ELLIPSOID_CONE_BOTTOM_FLIPPED: undefined,
+ ELLIPSOID_CONE_TOP_REGULAR: undefined,
+ ELLIPSOID_CONE_TOP_FLIPPED: undefined,
+ ELLIPSOID_OUTER: undefined,
+ ELLIPSOID_INNER: undefined,
+ ELLIPSOID_INTERSECTION_COUNT: undefined,
+ };
+}
const scratchScale = new Cartesian3();
-const scratchFullScale = new Cartesian3();
const scratchRotationScale = new Matrix3();
+const scratchOuter = new Cartesian3();
+const scratchInner = new Cartesian3();
/**
* Update the shape's state.
@@ -126,6 +137,7 @@ const scratchRotationScale = new Matrix3();
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
+ * @returns {Boolean} Whether the shape is visible.
*/
VoxelEllipsoidShape.prototype.update = function (
modelMatrix,
@@ -138,70 +150,85 @@ VoxelEllipsoidShape.prototype.update = function (
Check.typeOf.object("maxBounds", maxBounds);
//>>includeEnd('debug');
- // Don't let the scale be 0, it will screw up a bunch of math
- const scaleEps = CesiumMath.EPSILON8;
- const scale = Matrix4.getScale(modelMatrix, scratchScale);
- if (Math.abs(scale.x) < scaleEps) {
- scale.x = CesiumMath.signNotZero(scale.x) * scaleEps;
- }
- if (Math.abs(scale.y) < scaleEps) {
- scale.y = CesiumMath.signNotZero(scale.y) * scaleEps;
- }
- if (Math.abs(scale.z) < scaleEps) {
- scale.z = CesiumMath.signNotZero(scale.z) * scaleEps;
- }
+ const defaultMinBounds = VoxelEllipsoidShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelEllipsoidShape.DefaultMaxBounds;
- this._rectangle = Rectangle.fromRadians(
+ const west = CesiumMath.clamp(
minBounds.x,
- minBounds.y,
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
+ const east = CesiumMath.clamp(
maxBounds.x,
- maxBounds.y
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
+ const south = CesiumMath.clamp(
+ minBounds.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
+ );
+ const north = CesiumMath.clamp(
+ maxBounds.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
);
- const minHeight = minBounds.z;
- const maxHeight = maxBounds.z;
+ // Don't let the height go below the center of the ellipsoid.
+ const radii = Matrix4.getScale(modelMatrix, scratchScale);
+ const minRadius = Cartesian3.minimumComponent(radii);
+ const minHeight = Math.max(minBounds.z, -minRadius);
+ const maxHeight = Math.max(maxBounds.z, -minRadius);
+
+ // The closest and farthest a point can be from the center of the ellipsoid.
+ const innerExtent = Cartesian3.add(
+ radii,
+ Cartesian3.fromElements(minHeight, minHeight, minHeight, scratchInner),
+ scratchInner
+ );
+ const outerExtent = Cartesian3.add(
+ radii,
+ Cartesian3.fromElements(maxHeight, maxHeight, maxHeight, scratchOuter),
+ scratchOuter
+ );
+ const maxExtent = Cartesian3.maximumComponent(outerExtent);
- // Exit early if the bounds make the shape invisible.
+ // Exit early if the shape is not visible.
// Note that west may be greater than east when crossing the 180th meridian.
- const rectangle = this._rectangle;
- if (rectangle.south > rectangle.north || minHeight > maxHeight) {
- this.isVisible = false;
- return;
+ const absEpsilon = CesiumMath.EPSILON10;
+ if (
+ south > north ||
+ minHeight > maxHeight ||
+ CesiumMath.equalsEpsilon(outerExtent.x, 0.0, undefined, absEpsilon) ||
+ CesiumMath.equalsEpsilon(outerExtent.y, 0.0, undefined, absEpsilon) ||
+ CesiumMath.equalsEpsilon(outerExtent.z, 0.0, undefined, absEpsilon)
+ ) {
+ return false;
}
+ this._rectangle = Rectangle.fromRadians(west, south, east, north);
this._translation = Matrix4.getTranslation(modelMatrix, this._translation);
this._rotation = Matrix4.getRotation(modelMatrix, this._rotation);
- this._ellipsoid = Ellipsoid.fromCartesian3(scale, this._ellipsoid);
-
- const fullScale = Cartesian3.add(
- scale,
- Cartesian3.fromElements(maxHeight, maxHeight, maxHeight, scratchFullScale),
- scratchFullScale
- );
- const rotationScale = Matrix3.setScale(
- this._rotation,
- fullScale,
- scratchRotationScale
- );
- this.shapeTransform = Matrix4.fromRotationTranslation(
- rotationScale,
- this._translation,
- this.shapeTransform
- );
+ this._ellipsoid = Ellipsoid.fromCartesian3(radii, this._ellipsoid);
+ this._minimumHeight = minHeight;
+ this._maximumHeight = maxHeight;
this.orientedBoundingBox = getEllipsoidChunkObb(
- rectangle.west,
- rectangle.east,
- rectangle.south,
- rectangle.north,
- minHeight,
- maxHeight,
+ this._rectangle,
+ this._minimumHeight,
+ this._maximumHeight,
this._ellipsoid,
this._translation,
this._rotation,
this.orientedBoundingBox
);
+ this.shapeTransform = Matrix4.fromRotationTranslation(
+ Matrix3.setScale(this._rotation, outerExtent, scratchRotationScale),
+ this._translation,
+ this.shapeTransform
+ );
+
this.boundTransform = Matrix4.fromRotationTranslation(
this.orientedBoundingBox.halfAxes,
this.orientedBoundingBox.center,
@@ -213,7 +240,120 @@ VoxelEllipsoidShape.prototype.update = function (
this.boundingSphere
);
- this.isVisible = true;
+ const shaderUniforms = this.shaderUniforms;
+ const shaderDefines = this.shaderDefines;
+
+ shaderUniforms.ellipsoidRectangle = Cartesian4.fromElements(
+ west,
+ south,
+ east,
+ north,
+ shaderUniforms.ellipsoidRectangle
+ );
+
+ // The ellipsoid radii scaled to [0,1]. The max ellipsoid radius will be 1.0 and others will be less.
+ shaderUniforms.ellipsoidRadiiUv = Cartesian3.divideByScalar(
+ outerExtent,
+ maxExtent,
+ shaderUniforms.ellipsoidRadiiUv
+ );
+
+ // Used to compute geodetic surface normal.
+ shaderUniforms.ellipsoidInverseRadiiSquaredUv = Cartesian3.divideComponents(
+ Cartesian3.ONE,
+ Cartesian3.multiplyComponents(
+ shaderUniforms.ellipsoidRadiiUv,
+ shaderUniforms.ellipsoidRadiiUv,
+ shaderUniforms.ellipsoidInverseRadiiSquaredUv
+ ),
+ shaderUniforms.ellipsoidInverseRadiiSquaredUv
+ );
+
+ const rectangleWidth = Rectangle.computeWidth(this._rectangle);
+ const hasInnerEllipsoid = !Cartesian3.equals(innerExtent, Cartesian3.ZERO);
+ const hasWedgeRegular =
+ rectangleWidth >= CesiumMath.PI && rectangleWidth < CesiumMath.TWO_PI;
+ const hasWedgeFlipped = rectangleWidth < CesiumMath.PI;
+ const hasTopConeRegular = north >= 0.0 && north < +CesiumMath.PI_OVER_TWO;
+ const hasTopConeFlipped = north < 0.0;
+ const hasBottomConeRegular = south <= 0.0 && south > -CesiumMath.PI_OVER_TWO;
+ const hasBottomConeFlipped = south > 0.0;
+
+ // Determine how many intersections there are going to be.
+ let intersectionCount = 0;
+
+ // Intersects an outer ellipsoid for the max height.
+ shaderDefines["ELLIPSOID_OUTER"] = intersectionCount * 2;
+ intersectionCount += 1;
+
+ // Intersects an inner ellipsoid for the min height.
+ if (hasInnerEllipsoid) {
+ shaderDefines["ELLIPSOID_INNER"] = intersectionCount * 2;
+ intersectionCount += 1;
+
+ // The percent of space that is between the inner and outer ellipsoid.
+ const thickness = (maxHeight - minHeight) / maxExtent;
+ shaderUniforms.ellipsoidInverseHeightDifferenceUv = 1.0 / thickness;
+
+ // The percent of space that is taken up by the inner ellipsoid.
+ const innerScale = 1.0 - thickness;
+ shaderUniforms.ellipsoidInverseInnerScaleUv = 1.0 / innerScale;
+
+ // The inner ellipsoid radii scaled to [0,innerScale]. The max inner ellipsoid radius will equal innerScale and others will be less.
+ shaderUniforms.ellipsoidInnerRadiiUv = Cartesian3.multiplyByScalar(
+ shaderUniforms.ellipsoidRadiiUv,
+ innerScale,
+ shaderUniforms.ellipsoidInnerRadiiUv
+ );
+ } else {
+ shaderDefines["ELLIPSOID_INNER"] = undefined;
+ }
+
+ // Intersects a wedge for the min and max longitude.
+ if (hasWedgeRegular) {
+ shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = undefined;
+ intersectionCount += 1;
+ } else if (hasWedgeFlipped) {
+ shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = intersectionCount * 2;
+ intersectionCount += 2;
+ } else {
+ shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = undefined;
+ }
+
+ // Intersects a cone for min latitude
+ if (hasBottomConeRegular) {
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = undefined;
+ intersectionCount += 1;
+ } else if (hasBottomConeFlipped) {
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = intersectionCount * 2;
+ intersectionCount += 2;
+ } else {
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = undefined;
+ }
+
+ // Intersects a cone for max latitude
+ if (hasTopConeRegular) {
+ shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = undefined;
+ intersectionCount += 1;
+ } else if (hasTopConeFlipped) {
+ shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = intersectionCount * 2;
+ intersectionCount += 2;
+ } else {
+ shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = undefined;
+ }
+
+ shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = intersectionCount;
+
+ return true;
};
const scratchRectangle = new Rectangle();
@@ -274,10 +414,7 @@ VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
);
return getEllipsoidChunkObb(
- rectangle.west,
- rectangle.east,
- rectangle.south,
- rectangle.north,
+ rectangle,
minHeight,
maxHeight,
this._ellipsoid,
@@ -312,41 +449,12 @@ VoxelEllipsoidShape.prototype.computeApproximateStepSize = function (
return stepSize;
};
-/**
- * Defines the minimum bounds of the shape. Corresponds to minimum longitude, latitude, height.
- *
- * @type {Cartesian3}
- * @constant
- * @readonly
- */
-VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
- -CesiumMath.PI,
- -CesiumMath.PI_OVER_TWO,
- 0.0 // should be -1?
-);
-
-/**
- * Defines the maximum bounds of the shape. Corresponds to maximum longitude, latitude, height.
- *
- * @type {Cartesian3}
- * @constant
- * @readonly
- */
-VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
- +CesiumMath.PI,
- +CesiumMath.PI_OVER_TWO,
- 1.0 // should be 0?
-);
-
/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
*
* @function
*
- * @param {Number} west The minimumX.
- * @param {Number} east The maximumX.
- * @param {Number} south The minimumY.
- * @param {Number} north The maximumY.
+ * @param {Rectangle} rectangle The rectangle.
* @param {Number} minHeight The minimumZ.
* @param {Number} maxHeight The maximumZ.
* @param {Ellipsoid} ellipsoid The ellipsoid.
@@ -357,10 +465,7 @@ VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
* @private
*/
function getEllipsoidChunkObb(
- west,
- east,
- south,
- north,
+ rectangle,
minHeight,
maxHeight,
ellipsoid,
@@ -369,7 +474,7 @@ function getEllipsoidChunkObb(
result
) {
result = OrientedBoundingBox.fromRectangle(
- Rectangle.fromRadians(west, south, east, north, scratchRectangle),
+ rectangle,
minHeight,
maxHeight,
ellipsoid,
@@ -384,4 +489,30 @@ function getEllipsoidChunkObb(
return result;
}
+/**
+ * Defines the minimum bounds of the shape. Corresponds to minimum longitude, latitude, height.
+ *
+ * @type {Cartesian3}
+ * @constant
+ * @readonly
+ */
+VoxelEllipsoidShape.DefaultMinBounds = new Cartesian3(
+ -CesiumMath.PI,
+ -CesiumMath.PI_OVER_TWO,
+ -Number.MAX_VALUE
+);
+
+/**
+ * Defines the maximum bounds of the shape. Corresponds to maximum longitude, latitude, height.
+ *
+ * @type {Cartesian3}
+ * @constant
+ * @readonly
+ */
+VoxelEllipsoidShape.DefaultMaxBounds = new Cartesian3(
+ +CesiumMath.PI,
+ +CesiumMath.PI_OVER_TWO,
+ +Number.MAX_VALUE
+);
+
export default VoxelEllipsoidShape;
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index e7a76ea8f5b..b27bca23c64 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -3,6 +3,7 @@ import Cartesian3 from "../Core/Cartesian3.js";
import Cartesian4 from "../Core/Cartesian4.js";
import CesiumMath from "../Core/Math.js";
import Check from "../Core/Check.js";
+import clone from "../Core/clone.js";
import Color from "../Core/Color.js";
import defaultValue from "../Core/defaultValue.js";
import defer from "../Core/defer.js";
@@ -102,6 +103,12 @@ function VoxelPrimitive(options) {
*/
this._shape = undefined;
+ /**
+ * @type {Boolean}
+ * @private
+ */
+ this._shapeVisible = false;
+
/**
* This member is not created until the provider is ready.
*
@@ -372,12 +379,11 @@ function VoxelPrimitive(options) {
*/
this._disableUpdate = false;
- // Uniforms
/**
* @type {Object.}
* @private
*/
- this._uniformMapValues = {
+ this._uniforms = {
/**
* @ignore
* @type {Texture}
@@ -414,37 +420,33 @@ function VoxelPrimitive(options) {
cameraPositionUv: new Cartesian3(),
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
stepSize: 1.0,
- ellipsoidInverseHeightDifferenceUv: 1.0,
- ellipsoidInverseInnerScaleUv: 1.0,
- ellipsoidRadiiUv: new Cartesian3(),
- ellipsoidInnerRadiiUv: new Cartesian3(),
- ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
- minBounds: new Cartesian3(),
- maxBounds: new Cartesian3(),
- minBoundsUv: new Cartesian3(),
- maxBoundsUv: new Cartesian3(),
- inverseBounds: new Cartesian3(),
- inverseBoundsUv: new Cartesian3(),
minClippingBounds: new Cartesian3(),
maxClippingBounds: new Cartesian3(),
pickColor: new Color(),
};
- // Automatically generate uniform map from the uniform values
/**
+ * Shape specific shader defines from the previous shape update. Used to detect if the shader needs to be rebuilt.
+ * @type {Object.}
+ * @private
+ */
+ this._shapeDefinesOld = {};
+
+ /**
+ * Map uniform names to functions that return the uniform values.
* @type {Object.}
* @private
*/
this._uniformMap = {};
- const uniformMapValues = this._uniformMapValues;
- function getUniformFunction(key) {
- return function () {
- return uniformMapValues[key];
- };
- }
- for (const key in uniformMapValues) {
- if (uniformMapValues.hasOwnProperty(key)) {
- this._uniformMap[`u_${key}`] = getUniformFunction(key);
+
+ const uniforms = this._uniforms;
+ const uniformMap = this._uniformMap;
+ for (const key in uniforms) {
+ if (uniforms.hasOwnProperty(key)) {
+ const name = `u_${key}`;
+ uniformMap[name] = function () {
+ return uniforms[key];
+ };
}
}
@@ -1055,7 +1057,6 @@ const scratchTotalDimensions = new Cartesian3();
const scratchIntersect = new Cartesian4();
const scratchNdcAabb = new Cartesian4();
const scratchScale = new Cartesian3();
-const scratchEllipsoidRadii = new Cartesian3();
const scratchLocalScale = new Cartesian3();
const scratchInverseLocalScale = new Cartesian3();
const scratchRotation = new Matrix3();
@@ -1084,7 +1085,7 @@ const transformPositionUvToLocal = Matrix4.fromRotationTranslation(
VoxelPrimitive.prototype.update = function (frameState) {
const context = frameState.context;
const provider = this._provider;
- const uniforms = this._uniformMapValues;
+ const uniforms = this._uniforms;
// Update the provider, if applicable.
if (defined(provider.update)) {
@@ -1110,12 +1111,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
this._pickId = context.createPickId({
primitive: this,
});
-
- // Set uniforms for picking
uniforms.pickColor = Color.clone(this._pickId.color, uniforms.pickColor);
- // Set member variables that come from the provider.
-
// const keyframeCount = defaultValue(provider.keyframeCount, 1);
// // TODO remove?
// that._keyframeCount = defaultValue(
@@ -1130,15 +1127,12 @@ VoxelPrimitive.prototype.update = function (frameState) {
const dimensions = provider.dimensions;
const shapeType = provider.shape;
+
+ // Set the bounds
const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
const minBounds = defaultValue(provider.minBounds, defaultMinBounds);
const maxBounds = defaultValue(provider.maxBounds, defaultMaxBounds);
- const minimumValues = provider.minimumValues;
- const maximumValues = provider.maximumValues;
-
- const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
- this._shape = new ShapeConstructor();
this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
this._minClippingBounds = Cartesian3.clone(
@@ -1149,6 +1143,34 @@ VoxelPrimitive.prototype.update = function (frameState) {
defaultMaxBounds,
this._maxClippingBounds
);
+
+ // Create the shape object
+ const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
+ this._shape = new ShapeConstructor();
+
+ const shape = this._shape;
+ const shapeDefines = shape.shaderDefines;
+ this._shapeDefinesOld = clone(shapeDefines, true);
+
+ // Add shape uniforms to the uniform map
+ const shapeUniforms = shape.shaderUniforms;
+ const uniformMap = this._uniformMap;
+ for (const key in shapeUniforms) {
+ if (shapeUniforms.hasOwnProperty(key)) {
+ const name = `u_${key}`;
+
+ //>>includeStart('debug', pragmas.debug);
+ if (defined(uniformMap[name])) {
+ throw new DeveloperError(`Uniform name "${name}" is already defined`);
+ }
+ //>>includeEnd('debug');
+
+ uniformMap[name] = function () {
+ return shapeUniforms[key];
+ };
+ }
+ }
+
this._paddingBefore = Cartesian3.clone(
defaultValue(provider.paddingBefore, Cartesian3.ZERO),
this._paddingBefore
@@ -1169,6 +1191,9 @@ VoxelPrimitive.prototype.update = function (frameState) {
this._paddingAfter,
uniforms.paddingAfter
);
+
+ const minimumValues = provider.minimumValues;
+ const maximumValues = provider.maximumValues;
if (defined(minimumValues) && defined(maximumValues)) {
uniforms.minimumValues = minimumValues.slice();
uniforms.maximumValues = maximumValues.slice();
@@ -1178,336 +1203,124 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Check if the shape is dirty before updating it. This needs to happen every
// frame because the member variables can be modified externally via the
// getters.
- const primitiveModelMatrix = this._modelMatrix;
- const providerModelMatrix = defaultValue(
+ const primitiveTransform = this._modelMatrix;
+ const providerTransform = defaultValue(
provider.modelMatrix,
Matrix4.IDENTITY
);
- const compoundModelMatrix = Matrix4.multiplyTransformation(
- providerModelMatrix,
- primitiveModelMatrix,
+ const compoundTransform = Matrix4.multiplyTransformation(
+ providerTransform,
+ primitiveTransform,
this._compoundModelMatrix
);
- const compoundModelMatrixOld = this._compoundModelMatrixOld;
- const compoundModelMatrixDirty = !Matrix4.equals(
- compoundModelMatrix,
- compoundModelMatrixOld
+ const compoundTransformOld = this._compoundModelMatrixOld;
+ const compoundTransformDirty = !Matrix4.equals(
+ compoundTransform,
+ compoundTransformOld
);
const shape = this._shape;
const shapeType = provider.shape;
- const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
- const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
const minBounds = this._minBounds;
const maxBounds = this._maxBounds;
-
- let isDefaultBoundsMin =
- minBounds.x === defaultMinBounds.x &&
- minBounds.y === defaultMinBounds.y &&
- minBounds.z === defaultMinBounds.z;
-
- let isDefaultBoundsMax =
- maxBounds.x === defaultMaxBounds.x &&
- maxBounds.y === defaultMaxBounds.y &&
- maxBounds.z === defaultMaxBounds.z;
-
- // Clamp the min bounds to the valid range.
- if (!isDefaultBoundsMin) {
- minBounds.x = CesiumMath.clamp(
- minBounds.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
- );
- minBounds.y = CesiumMath.clamp(
- minBounds.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
- );
- if (shapeType !== VoxelShapeType.ELLIPSOID) {
- minBounds.z = CesiumMath.clamp(
- minBounds.z,
- defaultMinBounds.z,
- defaultMaxBounds.z
- );
- }
- isDefaultBoundsMin =
- minBounds.x === defaultMinBounds.x &&
- minBounds.y === defaultMinBounds.y &&
- minBounds.z === defaultMinBounds.z;
- }
-
- // Clamp the max bounds to the valid range.
- if (!isDefaultBoundsMax) {
- maxBounds.x = CesiumMath.clamp(
- maxBounds.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
- );
- maxBounds.y = CesiumMath.clamp(
- maxBounds.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
- );
- if (shapeType !== VoxelShapeType.ELLIPSOID) {
- maxBounds.z = CesiumMath.clamp(
- maxBounds.z,
- defaultMinBounds.z,
- defaultMaxBounds.z
- );
- }
- isDefaultBoundsMax =
- maxBounds.x === defaultMaxBounds.x &&
- maxBounds.y === defaultMaxBounds.y &&
- maxBounds.z === defaultMaxBounds.z;
- }
-
const minBoundsOld = this._minBoundsOld;
const maxBoundsOld = this._maxBoundsOld;
const minBoundsDirty = !Cartesian3.equals(minBounds, minBoundsOld);
const maxBoundsDirty = !Cartesian3.equals(maxBounds, maxBoundsOld);
+ const shapeDirty = compoundTransformDirty || minBoundsDirty || maxBoundsDirty;
- const shapeIsDirty =
- compoundModelMatrixDirty || minBoundsDirty || maxBoundsDirty;
-
- // Update the shape if dirty or the first frame.
- if (!this._ready || shapeIsDirty) {
- shape.update(compoundModelMatrix, minBounds, maxBounds);
-
- if (compoundModelMatrixDirty) {
+ // Update the shape on the first frame or if it's dirty.
+ // If the shape is visible it will do some extra work.
+ if (
+ (!this._ready || shapeDirty) &&
+ (this._shapeVisible = shape.update(compoundTransform, minBounds, maxBounds))
+ ) {
+ if (compoundTransformDirty) {
this._compoundModelMatrixOld = Matrix4.clone(
- compoundModelMatrix,
+ compoundTransform,
this._compoundModelMatrixOld
);
}
+ if (minBoundsDirty) {
+ this._minBoundsOld = Cartesian3.clone(minBounds, this._minBoundsOld);
+ }
+ if (maxBoundsDirty) {
+ this._maxBoundsOld = Cartesian3.clone(maxBounds, this._maxBoundsOld);
+ }
- if (minBoundsDirty || maxBoundsDirty) {
- if (minBoundsDirty) {
- // Check if the min bounds became default or stopped being default
- if (
- (minBounds.x === defaultMinBounds.x) !==
- (minBoundsOld.x === defaultMinBounds.x) ||
- (minBounds.y === defaultMinBounds.y) !==
- (minBoundsOld.y === defaultMinBounds.y) ||
- (minBounds.z === defaultMinBounds.z) !==
- (minBoundsOld.z === defaultMinBounds.z)
- ) {
- this._shaderDirty = true;
- }
- this._minBoundsOld = Cartesian3.clone(minBounds, this._minBoundsOld);
- }
-
- if (maxBoundsDirty) {
- // Check if the max bounds became default or stopped being default
- if (
- (maxBounds.x === defaultMaxBounds.x) !==
- (maxBoundsOld.x === defaultMaxBounds.x) ||
- (maxBounds.y === defaultMaxBounds.y) !==
- (maxBoundsOld.y === defaultMaxBounds.y) ||
- (maxBounds.z === defaultMaxBounds.z) !==
- (maxBoundsOld.z === defaultMaxBounds.z)
- ) {
- this._shaderDirty = true;
- }
- this._maxBoundsOld = Cartesian3.clone(maxBounds, this._maxBoundsOld);
- }
-
- // Set uniforms for bounds.
- if (!isDefaultBoundsMin || !isDefaultBoundsMax) {
- uniforms.minBounds = Cartesian3.clone(minBounds, uniforms.minBounds);
- uniforms.maxBounds = Cartesian3.clone(maxBounds, uniforms.maxBounds);
- uniforms.inverseBounds = Cartesian3.divideComponents(
- Cartesian3.ONE,
- Cartesian3.subtract(maxBounds, minBounds, uniforms.inverseBounds),
- uniforms.inverseBounds
- );
-
- if (shapeType === VoxelShapeType.BOX) {
- const minXUv = minBounds.x * 0.5 + 0.5;
- const maxXUv = maxBounds.x * 0.5 + 0.5;
- const minYUv = minBounds.y * 0.5 + 0.5;
- const maxYUv = maxBounds.y * 0.5 + 0.5;
- const minZUv = minBounds.z * 0.5 + 0.5;
- const maxZUv = maxBounds.z * 0.5 + 0.5;
- uniforms.minBoundsUv = Cartesian3.fromElements(
- minXUv,
- minYUv,
- minZUv,
- uniforms.minBoundsUv
- );
- uniforms.maxBoundsUv = Cartesian3.fromElements(
- maxXUv,
- maxYUv,
- maxZUv,
- uniforms.maxBoundsUv
- );
- } else if (shapeType === VoxelShapeType.ELLIPSOID) {
- const minLongitudeUv =
- (minBounds.x - defaultMinBounds.x) /
- (defaultMaxBounds.x - defaultMinBounds.x);
- const maxLongitudeUv =
- (maxBounds.x - defaultMinBounds.x) /
- (defaultMaxBounds.x - defaultMinBounds.x);
- const minLatitudeUv =
- (minBounds.y - defaultMinBounds.y) /
- (defaultMaxBounds.y - defaultMinBounds.y);
- const maxLatitudeUv =
- (maxBounds.y - defaultMinBounds.y) /
- (defaultMaxBounds.y - defaultMinBounds.y);
- const minHeightUv = 0.0; // don't know what to do with these yet
- const maxHeightUv = 0.0; // don't know what to do with these yet
-
- uniforms.minBoundsUv = Cartesian3.fromElements(
- minLongitudeUv,
- minLatitudeUv,
- minHeightUv,
- uniforms.minBoundsUv
- );
- uniforms.maxBoundsUv = Cartesian3.fromElements(
- maxLongitudeUv,
- maxLatitudeUv,
- maxHeightUv,
- uniforms.maxBoundsUv
- );
- } else if (shapeType === VoxelShapeType.CYLINDER) {
- const minRadiusUv = minBounds.x;
- const maxRadiusUv = maxBounds.x;
- const minHeightUv = minBounds.y * 0.5 + 0.5;
- const maxHeightUv = maxBounds.y * 0.5 + 0.5;
- const minAngleUv = (minBounds.z + CesiumMath.PI) / CesiumMath.TWO_PI;
- const maxAngleUv = (maxBounds.z + CesiumMath.PI) / CesiumMath.TWO_PI;
- uniforms.minBoundsUv = Cartesian3.fromElements(
- minRadiusUv,
- minHeightUv,
- minAngleUv,
- uniforms.minBoundsUv
- );
- uniforms.maxBoundsUv = Cartesian3.fromElements(
- maxRadiusUv,
- maxHeightUv,
- maxAngleUv,
- uniforms.maxBoundsUv
- );
+ // Rebuild the shader if any of the shape defines changed.
+ const shapeDefines = shape.shaderDefines;
+ const shapeDefinesOld = this._shapeDefinesOld;
+ let shapeDefinesChanged = false;
+ for (const property in shapeDefines) {
+ if (shapeDefines.hasOwnProperty(property)) {
+ const value = shapeDefines[property];
+ const valueOld = shapeDefinesOld[property];
+ if (value !== valueOld) {
+ shapeDefinesChanged = true;
+ break;
}
-
- uniforms.inverseBoundsUv = Cartesian3.divideComponents(
- Cartesian3.ONE,
- Cartesian3.subtract(
- uniforms.maxBoundsUv,
- uniforms.minBoundsUv,
- uniforms.inverseBoundsUv
- ),
- uniforms.inverseBoundsUv
- );
}
}
-
- // Set other uniforms when the shape is dirty
- if (shapeType === VoxelShapeType.ELLIPSOID) {
- const radii = Matrix4.getScale(
- compoundModelMatrix,
- scratchEllipsoidRadii
- );
- const minHeight = minBounds.z;
- const maxHeight = maxBounds.z;
- // The farthest distance a point can be from the center of the ellipsoid.
- const maxExtent = Cartesian3.maximumComponent(radii) + maxHeight;
- // The percent of space that is between the inner and outer ellipsoid
- const thickness = (maxHeight - minHeight) / maxExtent;
- // The percent of space that is taken up by the inner ellipsoid.
- const innerScale = 1.0 - thickness;
-
- // The ellipsoid radii scaled to [0,1]. The max ellipsoid radius will be 1.0 and others will be less.
- uniforms.ellipsoidRadiiUv = Cartesian3.fromElements(
- (radii.x + maxHeight) / maxExtent,
- (radii.y + maxHeight) / maxExtent,
- (radii.z + maxHeight) / maxExtent,
- uniforms.ellipsoidRadiiUv
- );
-
- // The inner ellipsoid radii scaled to [0,innerScale]. The max inner ellipsoid radius will be innerScale and others will be less.
- uniforms.ellipsoidInnerRadiiUv = Cartesian3.multiplyByScalar(
- uniforms.ellipsoidRadiiUv,
- innerScale,
- uniforms.ellipsoidInnerRadiiUv
- );
-
- // Used to compute geodetic surface normal.
- uniforms.ellipsoidInverseRadiiSquaredUv = Cartesian3.divideComponents(
- Cartesian3.ONE,
- Cartesian3.multiplyComponents(
- uniforms.ellipsoidRadiiUv,
- uniforms.ellipsoidRadiiUv,
- uniforms.ellipsoidInverseRadiiSquaredUv
- ),
- uniforms.ellipsoidInverseRadiiSquaredUv
- );
- uniforms.ellipsoidInverseHeightDifferenceUv = 1.0 / thickness;
- uniforms.ellipsoidInverseInnerScaleUv = 1.0 / innerScale;
+ if (shapeDefinesChanged) {
+ this._shaderDirty = true;
+ this._shapeDefinesOld = clone(shapeDefines, true);
}
- // Math that's only valid if the shape is visible.
- if (shape.isVisible) {
- const transformPositionLocalToWorld = shape.shapeTransform;
- const transformPositionWorldToLocal = Matrix4.inverse(
- transformPositionLocalToWorld,
- scratchTransformPositionWorldToLocal
- );
- const rotation = Matrix4.getRotation(
- transformPositionLocalToWorld,
- scratchRotation
- );
- // Note that inverse(rotation) is the same as transpose(rotation)
- const inverseRotation = Matrix3.transpose(
- rotation,
- scratchInverseRotation
- );
- const scale = Matrix4.getScale(
- transformPositionLocalToWorld,
- scratchScale
- );
- const maximumScaleComponent = Cartesian3.maximumComponent(scale);
- const localScale = Cartesian3.divideByScalar(
- scale,
- maximumScaleComponent,
- scratchLocalScale
- );
- const inverseLocalScale = Cartesian3.divideComponents(
- Cartesian3.ONE,
- localScale,
- scratchInverseLocalScale
- );
- const rotationAndLocalScale = Matrix3.multiplyByScale(
- rotation,
- localScale,
- scratchRotationAndLocalScale
- );
+ const transformPositionLocalToWorld = shape.shapeTransform;
+ const transformPositionWorldToLocal = Matrix4.inverse(
+ transformPositionLocalToWorld,
+ scratchTransformPositionWorldToLocal
+ );
+ const rotation = Matrix4.getRotation(
+ transformPositionLocalToWorld,
+ scratchRotation
+ );
+ // Note that inverse(rotation) is the same as transpose(rotation)
+ const inverseRotation = Matrix3.transpose(rotation, scratchInverseRotation);
+ const scale = Matrix4.getScale(transformPositionLocalToWorld, scratchScale);
+ const maximumScaleComponent = Cartesian3.maximumComponent(scale);
+ const localScale = Cartesian3.divideByScalar(
+ scale,
+ maximumScaleComponent,
+ scratchLocalScale
+ );
+ const inverseLocalScale = Cartesian3.divideComponents(
+ Cartesian3.ONE,
+ localScale,
+ scratchInverseLocalScale
+ );
+ const rotationAndLocalScale = Matrix3.multiplyByScale(
+ rotation,
+ localScale,
+ scratchRotationAndLocalScale
+ );
- // Set member variables when the shape is dirty
- const dimensions = provider.dimensions;
- this._stepSizeUv = shape.computeApproximateStepSize(dimensions);
- this._transformPositionWorldToUv = Matrix4.multiply(
- transformPositionLocalToUv,
- transformPositionWorldToLocal,
- this._transformPositionWorldToUv
- );
- this._transformPositionUvToWorld = Matrix4.multiply(
- transformPositionLocalToWorld,
- transformPositionUvToLocal,
- this._transformPositionUvToWorld
- );
- this._transformDirectionWorldToLocal = Matrix3.setScale(
- inverseRotation,
- inverseLocalScale,
- this._transformDirectionWorldToLocal
- );
- this._transformNormalLocalToWorld = Matrix3.inverseTranspose(
- rotationAndLocalScale,
- this._transformNormalLocalToWorld
- );
- }
+ // Set member variables when the shape is dirty
+ const dimensions = provider.dimensions;
+ this._stepSizeUv = shape.computeApproximateStepSize(dimensions);
+ this._transformPositionWorldToUv = Matrix4.multiply(
+ transformPositionLocalToUv,
+ transformPositionWorldToLocal,
+ this._transformPositionWorldToUv
+ );
+ this._transformPositionUvToWorld = Matrix4.multiply(
+ transformPositionLocalToWorld,
+ transformPositionUvToLocal,
+ this._transformPositionUvToWorld
+ );
+ this._transformDirectionWorldToLocal = Matrix3.setScale(
+ inverseRotation,
+ inverseLocalScale,
+ this._transformDirectionWorldToLocal
+ );
+ this._transformNormalLocalToWorld = Matrix3.inverseTranspose(
+ rotationAndLocalScale,
+ this._transformNormalLocalToWorld
+ );
}
- // Initialize the voxel traversal now that the shape is ready to use. This only happens once.
+ // Initialize from the ready shape. This only happens once.
if (!this._ready) {
const dimensions = provider.dimensions;
const paddingBefore = this._paddingBefore;
@@ -1547,6 +1360,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
);
// Set uniforms that come from the traversal.
+ // TODO: should this be done in VoxelTraversal?
const traversal = this._traversal;
const useLeafNodeTexture = traversal.useLeafNodeTexture;
@@ -1594,11 +1408,18 @@ VoxelPrimitive.prototype.update = function (frameState) {
megatexture.regionSizeUv,
uniforms.megatextureTileSizeUv
);
- } else if (shape.isVisible) {
- // Find the keyframe location to render at. Doesn't need to be a whole number.
- let keyframeLocation = 0.0;
+ }
+
+ // Update the traversal and prepare for rendering.
+ // This doesn't happen on the first update frame. It needs to wait until the
+ // primitive is made ready after the end of the first update frame.
+ if (this._ready && this._shapeVisible) {
+ const traversal = this._traversal;
const clock = this._clock;
const timeIntervalCollection = this._timeIntervalCollection;
+
+ // Find the keyframe location to render at. Doesn't need to be a whole number.
+ let keyframeLocation = 0.0;
if (defined(timeIntervalCollection) && defined(clock)) {
let date = clock.currentTime;
let timeInterval;
@@ -1634,12 +1455,10 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
// Update the voxel traversal
- const traversal = this._traversal;
-
const hasLoadedData = traversal.update(
frameState,
keyframeLocation,
- shapeIsDirty, // recomputeBoundingVolumes
+ shapeDirty, // recomputeBoundingVolumes
this._disableUpdate // pauseUpdate
);
@@ -1654,6 +1473,9 @@ VoxelPrimitive.prototype.update = function (frameState) {
const minClip = this._minClippingBounds;
const maxClip = this._maxClippingBounds;
+ const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
+ const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
+
let isDefaultClippingBoundsMin =
minClip.x === defaultMinBounds.x &&
minClip.y === defaultMinBounds.y &&
@@ -1808,7 +1630,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
const cameraPositionWorld = frameState.camera.positionWC;
// Update uniforms that can change every frame
- const uniforms = this._uniformMapValues;
uniforms.transformPositionViewToUv = Matrix4.multiply(
transformPositionWorldToUv,
transformPositionViewToWorld,
@@ -1975,12 +1796,10 @@ function buildDrawCommands(that, context) {
const useLogDepth = that._useLogDepth;
const paddingBefore = that.paddingBefore;
const paddingAfter = that.paddingAfter;
- const minBounds = that._minBounds;
- const maxBounds = that._maxBounds;
+ const shape = that._shape;
+ const shapeDefines = shape.shaderDefines;
const minimumValues = provider.minimumValues;
const maximumValues = provider.maximumValues;
- const minClippingBounds = that._minClippingBounds;
- const maxClippingBounds = that._maxClippingBounds;
const keyframeCount = that._keyframeCount;
const despeckle = that._despeckle;
const jitter = that._jitter;
@@ -2077,195 +1896,35 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
- const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
- const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
- const isDefaultMinX = minBounds.x === defaultMinBounds.x;
- const isDefaultMinY = minBounds.y === defaultMinBounds.y;
- const isDefaultMinZ = minBounds.z === defaultMinBounds.z;
- const isDefaultMaxX = maxBounds.x === defaultMaxBounds.x;
- const isDefaultMaxY = maxBounds.y === defaultMaxBounds.y;
- const isDefaultMaxZ = maxBounds.z === defaultMaxBounds.z;
-
- let useBounds = false;
- if (shapeType === VoxelShapeType.BOX) {
- useBounds =
- !isDefaultMinX ||
- !isDefaultMaxX ||
- !isDefaultMinY ||
- !isDefaultMaxY ||
- !isDefaultMinZ ||
- !isDefaultMaxZ;
- } else if (shapeType === VoxelShapeType.CYLINDER) {
- useBounds =
- !isDefaultMinX ||
- !isDefaultMaxX ||
- !isDefaultMinY ||
- !isDefaultMaxY ||
- !isDefaultMinZ ||
- !isDefaultMaxZ;
- } else if (shapeType === VoxelShapeType.ELLIPSOID) {
- const radii = Matrix4.getScale(that._compoundModelMatrix, scratchScale);
- const hasInnerEllipsoid = !(
- radii.x === radii.y &&
- radii.y === radii.z &&
- minBounds.z === -radii.x
- );
- useBounds =
- !isDefaultMinX ||
- !isDefaultMaxX ||
- !isDefaultMinY ||
- !isDefaultMaxY ||
- hasInnerEllipsoid;
- }
-
- if (useBounds) {
- shaderBuilder.addDefine("BOUNDS", undefined, ShaderDestination.FRAGMENT);
- }
- if (!isDefaultMinX) {
- shaderBuilder.addDefine(
- "BOUNDS_0_MIN",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
- if (!isDefaultMaxX) {
- shaderBuilder.addDefine(
- "BOUNDS_0_MAX",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
- if (!isDefaultMinY) {
- shaderBuilder.addDefine(
- "BOUNDS_1_MIN",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
- if (!isDefaultMaxY) {
- shaderBuilder.addDefine(
- "BOUNDS_1_MAX",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
- if (!isDefaultMinZ) {
- shaderBuilder.addDefine(
- "BOUNDS_2_MIN",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
- if (!isDefaultMaxZ) {
- shaderBuilder.addDefine(
- "BOUNDS_2_MAX",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
- let intersectionCount = 0;
- if (shapeType === VoxelShapeType.BOX) {
- // A bounded box is still a box, so it has the same number of shape intersections: 1
- intersectionCount = 1;
- } else if (shapeType === VoxelShapeType.ELLIPSOID) {
- // Intersects an outer ellipsoid for the max radius
- {
- shaderBuilder.addDefine(
- "BOUNDS_2_MAX_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
- }
- // Intersects an inner ellipsoid for the min radius
- if (!isDefaultMinZ) {
- shaderBuilder.addDefine(
- "BOUNDS_2_MIN_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
- }
- // Intersects a cone for min latitude
- if (!isDefaultMinY) {
- shaderBuilder.addDefine(
- "BOUNDS_1_MIN_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
- }
- // Intersects a cone for max latitude
- if (!isDefaultMaxY) {
- shaderBuilder.addDefine(
- "BOUNDS_1_MAX_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
- }
- // Intersects a wedge for the min and max longitude
- if (!isDefaultMinX || !isDefaultMaxX) {
- shaderBuilder.addDefine(
- "BOUNDS_0_MIN_MAX_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
- }
- } else if (shapeType === VoxelShapeType.CYLINDER) {
- // Intersects a capped cylinder for the max radius
- // The min and max height are handled as part of the capped cylinder intersection
- {
- shaderBuilder.addDefine(
- "BOUNDS_0_MAX_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
- }
- // Intersects an inner infinite cylinder for the min radius
- if (!isDefaultMinX) {
- shaderBuilder.addDefine(
- "BOUNDS_0_MIN_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
- }
- // Intersects a wedge for the min and max theta
- if (!isDefaultMinZ || !isDefaultMaxZ) {
- shaderBuilder.addDefine(
- "BOUNDS_2_MIN_MAX_IDX",
- intersectionCount,
- ShaderDestination.FRAGMENT
- );
- intersectionCount++;
+ // Shape specific defines
+ for (const key in shapeDefines) {
+ if (shapeDefines.hasOwnProperty(key)) {
+ const value = shapeDefines[key];
+ if (defined(value)) {
+ shaderBuilder.addDefine(key, value, ShaderDestination.FRAGMENT);
+ }
}
}
- // The intersection count is multiplied by 2 because there is an enter and exit for each intersection
- shaderBuilder.addDefine(
- "SHAPE_INTERSECTION_COUNT",
- intersectionCount * 2,
- ShaderDestination.FRAGMENT
- );
- const useClippingBounds =
- minClippingBounds.x !== defaultMinBounds.x ||
- minClippingBounds.y !== defaultMinBounds.y ||
- minClippingBounds.z !== defaultMinBounds.z ||
- maxClippingBounds.x !== defaultMaxBounds.x ||
- maxClippingBounds.y !== defaultMaxBounds.y ||
- maxClippingBounds.z !== defaultMaxBounds.z;
- if (useClippingBounds) {
- shaderBuilder.addDefine(
- "CLIPPING_BOUNDS",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
+
+ // const useClippingBounds =
+ // minClippingBounds.x !== defaultMinBounds.x ||
+ // minClippingBounds.y !== defaultMinBounds.y ||
+ // minClippingBounds.z !== defaultMinBounds.z ||
+ // maxClippingBounds.x !== defaultMaxBounds.x ||
+ // maxClippingBounds.y !== defaultMaxBounds.y ||
+ // maxClippingBounds.z !== defaultMaxBounds.z;
+ // if (useClippingBounds) {
+ // shaderBuilder.addDefine(
+ // "CLIPPING_BOUNDS",
+ // undefined,
+ // ShaderDestination.FRAGMENT
+ // );
+ // }
// Fragment shader uniforms
+ // The reason this uniform is added by shader builder is because some of the
+ // dynamically generated shader code reads from it.
shaderBuilder.addUniform(
"sampler2D",
"u_megatextureTextures[METADATA_COUNT]",
diff --git a/Source/Scene/VoxelShape.js b/Source/Scene/VoxelShape.js
index 0264d5a3b8b..4291a2563d3 100644
--- a/Source/Scene/VoxelShape.js
+++ b/Source/Scene/VoxelShape.js
@@ -68,14 +68,18 @@ Object.defineProperties(VoxelShape.prototype, {
},
/**
- * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
- * The update function must be called before accessing this value.
- *
- * @memberof VoxelShape.prototype
- * @type {Boolean}
+ * @type {Object.}
+ * @readonly
+ */
+ shaderUniforms: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * @type {Object.}
* @readonly
*/
- isVisible: {
+ shaderDefines: {
get: DeveloperError.throwInstantiationError,
},
});
@@ -86,6 +90,7 @@ Object.defineProperties(VoxelShape.prototype, {
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
+ * @returns {Boolean} Whether the shape is visible.
*/
VoxelShape.prototype.update = DeveloperError.throwInstantiationError;
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index d99e412cf1a..21d2f3e5150 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -50,7 +50,7 @@ function VoxelTraversal(
maximumTextureMemoryByteLength
) {
/**
- * TODO: maybe this shouldn't be stored?
+ * TODO: maybe this shouldn't be stored or passed into update function?
* @type {VoxelPrimitive}
* @private
*/
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 1d05d5024f0..bad0f04fce0 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -9,7 +9,6 @@ Below is an example of how this code might look. Properties like "temperature" a
#define SHAPE_BOX
#define SHAPE_ELLIPSOID
#define SHAPE_CYLINDER
-#define SHAPE_INTERSECTION_COUNT ###
#define MEGATEXTURE_2D
#define MEGATEXTURE_3D
#define DEPTH_TEST
@@ -136,21 +135,26 @@ void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
#define STEP_COUNT_MAX 1000 // Harcoded value because GLSL doesn't like variable length loops
#define OCTREE_MAX_LEVELS 32 // Harcoded value because GLSL doesn't like variable length loops
#define ALPHA_ACCUM_MAX 0.98 // Must be > 0.0 and <= 1.0
-#define NO_HIT -czm_infinity
-#define INF_HIT czm_infinity
-#if defined(MEGATEXTURE_2D)
-uniform ivec2 u_megatextureSliceDimensions; // number of slices per tile, in two dimensions
-uniform ivec2 u_megatextureTileDimensions; // number of tiles per megatexture, in two dimensions
-uniform vec2 u_megatextureVoxelSizeUv;
-uniform vec2 u_megatextureSliceSizeUv;
-uniform vec2 u_megatextureTileSizeUv;
-#endif
+#define NO_HIT (-czm_infinity)
+#define INF_HIT (czm_infinity * 0.5)
+#define POSITIVE_ENTRY 0.0
+#define POSITIVE_EXIT 1.0
+#define NEGATIVE_ENTRY 2.0
+#define NEGATIVE_EXIT 3.0
uniform ivec3 u_dimensions; // does not include padding
#if defined(PADDING)
-uniform ivec3 u_paddingBefore;
-uniform ivec3 u_paddingAfter;
+ uniform ivec3 u_paddingBefore;
+ uniform ivec3 u_paddingAfter;
+#endif
+
+#if defined(MEGATEXTURE_2D)
+ uniform ivec2 u_megatextureSliceDimensions; // number of slices per tile, in two dimensions
+ uniform ivec2 u_megatextureTileDimensions; // number of tiles per megatexture, in two dimensions
+ uniform vec2 u_megatextureVoxelSizeUv;
+ uniform vec2 u_megatextureSliceSizeUv;
+ uniform vec2 u_megatextureTileSizeUv;
#endif
uniform sampler2D u_octreeInternalNodeTexture;
@@ -160,6 +164,19 @@ uniform sampler2D u_octreeLeafNodeTexture;
uniform vec2 u_octreeLeafNodeTexelSizeUv;
uniform int u_octreeLeafNodeTilesPerRow;
+struct OctreeNodeData {
+ int data;
+ int flag;
+};
+
+struct SampleData {
+ int megatextureIndex;
+ int levelsAbove;
+ #if (SAMPLE_COUNT > 1)
+ float weight;
+ #endif
+};
+
uniform mat4 u_transformPositionViewToUv;
uniform mat4 u_transformPositionUvToView;
uniform mat3 u_transformDirectionViewToLocal;
@@ -167,43 +184,90 @@ uniform mat3 u_transformNormalLocalToWorld;
uniform vec3 u_cameraPositionUv;
uniform float u_stepSize;
-#if defined(BOUNDS)
-uniform vec3 u_minBounds; // Bounds from the voxel primitive
-uniform vec3 u_maxBounds; // Bounds from the voxel primitive
-uniform vec3 u_minBoundsUv; // Similar to u_minBounds but relative to UV space [0,1]
-uniform vec3 u_maxBoundsUv; // Similar to u_maxBounds but relative to UV space [0,1]
-uniform vec3 u_inverseBounds; // Equal to 1.0 / (u_maxBounds - u_minBounds)
-uniform vec3 u_inverseBoundsUv; // Equal to 1.0 / (u_maxBoundsUv - u_minBoundsUv)
+#if defined(PICKING)
+ uniform vec4 u_pickColor;
#endif
#if defined(CLIPPING_BOUNDS)
-uniform vec3 u_minClippingBounds;
-uniform vec3 u_maxClippingBounds;
+ uniform vec3 u_minClippingBounds;
+ uniform vec3 u_maxClippingBounds;
+#endif
+
+#if defined(SHAPE_BOX)
+ #define BOX_INTERSECTION_COUNT 1
+ uniform vec2 u_boxMinBounds;
+ uniform vec2 u_boxMaxBounds;
#endif
#if defined(SHAPE_ELLIPSOID)
-uniform float u_ellipsoidInverseHeightDifferenceUv;
-uniform float u_ellipsoidInverseInnerScaleUv;
-uniform vec3 u_ellipsoidRadiiUv; // [0,1]
-uniform vec3 u_ellipsoidInnerRadiiUv; // [0,1]
-uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
+ /* Ellipsoid defines:
+ #define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
+ #define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
+ #define ELLIPSOID_CONE_BOTTOM_REGULAR ### // when there's a bottom cone
+ #define ELLIPSOID_CONE_BOTTOM_FLIPPED ### // when cone shape has two intersection intervals
+ #define ELLIPSOID_CONE_TOP_REGULAR ### // when there's a top cone
+ #define ELLIPSOID_CONE_TOP_FLIPPED ### // when cone shape has two intersection intervals
+ #define ELLIPSOID_INNER ### // when there's an inner ellipsoid
+ #define ELLIPSOID_OUTER ### // outer ellipsoid - always defined
+ #define ELLIPSOID_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
+ */
+
+ // Ellipsoid uniforms
+ uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
+ uniform vec3 u_ellipsoidRadiiUv; // [0,1]
+ uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
+
+ #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
+ uniform float u_ellipsoidWestUv;
+ uniform float u_ellipsoidInverseLongitudeRangeUv;
+ #endif
+ #if defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ uniform float u_ellipsoidSouthUv;
+ uniform float u_ellipsoidInverseLatitudeRangeUv;
+ #endif
+ #if defined(ELLIPSOID_INNER)
+ uniform float u_ellipsoidInverseHeightDifferenceUv;
+ uniform float u_ellipsoidInverseInnerScaleUv;
+ uniform vec3 u_ellipsoidInnerRadiiUv; // [0,1]
+ #endif
#endif
-#if defined(PICKING)
-uniform vec4 u_pickColor;
+#if defined(SHAPE_CYLINDER)
+ /* Cylinder defines:
+ #define CYLINDER_INNER ### // when there's an inner cylinder
+ #define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
+ #define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
+ #define CYLINDER_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
+ */
+
+ #if defined(CYLINDER_INNER)
+ uniform float u_something;
+ #endif
+ #if defined(CYLINDER_WEDGE_REGULAR) || defined(CYLINDER_WEDGE_FLIPPED)
+ uniform float u_cylinderMinAngle;
+ uniform float u_cylinderInverseAngleRange;
+ #endif
#endif
-struct OctreeNodeData {
- int data;
- int flag;
-};
+// Determine how many intersections there are going to be
+#if defined(SHAPE_BOX)
+ #define SHAPE_INTERSECTION_COUNT BOX_INTERSECTION_COUNT
+#elif defined(SHAPE_ELLIPSOID)
+ #define SHAPE_INTERSECTION_COUNT ELLIPSOID_INTERSECTION_COUNT
+#elif defined(SHAPE_CYLINDER)
+ #define SHAPE_INTERSECTION_COUNT CYLINDER_INTERSECTION_COUNT
+#endif
-struct SampleData {
- int megatextureIndex;
- int levelsAbove;
- #if (SAMPLE_COUNT > 1)
- float weight;
- #endif
+#if defined(DEPTH_TEST)
+ #define DEPTH_INTERSECTION_IDX (SHAPE_INTERSECTION_COUNT * 2)
+ #define SCENE_INTERSECTION_COUNT (SHAPE_INTERSECTION_COUNT + 1)
+#else
+ #define SCENE_INTERSECTION_COUNT SHAPE_INTERSECTION_COUNT
+#endif
+
+struct Intersections {
+ vec2 intersections[SCENE_INTERSECTION_COUNT * 2];
+ int index;
};
struct Ray {
@@ -267,75 +331,6 @@ vec2 index1DTo2DTexcoord(int index, ivec2 dimensions, vec2 uvScale)
// --------------------------------------------------------
// Intersection tests, shape coordinate conversions, etc
// --------------------------------------------------------
-#if (defined(SHAPE_ELLIPSOID) || defined(SHAPE_CYLINDER)) && defined(BOUNDS)
-vec2 resolveIntersections(vec2 intersections[SHAPE_INTERSECTION_COUNT])
-{
- // TODO: completely skip shape if both of its Ts are below 0.0?
- vec2 entryExitT = vec2(NO_HIT, NO_HIT);
-
- // Sort the intersections from min T to max T with bubble sort.
- // Note: If this sorting function changes, some of the intersection test may
- // need to be updated. Search for "bubble sort" to find those areas.
-
- const int sortPasses = SHAPE_INTERSECTION_COUNT - 1;
- for (int n = sortPasses; n > 0; --n)
- {
- for (int i = 0; i < sortPasses; ++i)
- {
- // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
- // loop with non-constant condition, so it has to break early instead
- if (i >= n) { break; }
-
- vec2 intersect0 = intersections[i];
- vec2 intersect1 = intersections[i+1];
-
- float idx0 = intersect0.x;
- float idx1 = intersect1.x;
- float t0 = intersect0.y;
- float t1 = intersect1.y;
-
- float tmin = min(t0, t1);
- float tmax = max(t0, t1);
- float idxmin = tmin == t0 ? idx0 : idx1;
- float idxmax = tmin == t0 ? idx1 : idx0;
-
- intersections[i] = vec2(idxmin, tmin);
- intersections[i+1] = vec2(idxmax, tmax);
- }
- }
-
- int surroundCount = 0;
- bool surroundIsPositive = false;
- for (int i = 0; i < SHAPE_INTERSECTION_COUNT; i++)
- {
- vec2 entry = intersections[i];
- float idx = entry.x;
- float t = entry.y;
-
- bool currShapeIsPositive = idx <= 1.0;
- bool enter = mod(idx, 2.0) == 0.0;
-
- surroundCount += enter ? +1 : -1;
- surroundIsPositive = currShapeIsPositive ? enter : surroundIsPositive;
-
- // entering positive or exiting negative
- if (surroundCount == 1 && surroundIsPositive && enter == currShapeIsPositive) {
- entryExitT.x = t;
- }
-
- // exiting positive or entering negative after being inside positive
- // TODO: Can this be simplified?
- if ((!enter && currShapeIsPositive && surroundCount == 0) || (enter && !currShapeIsPositive && surroundCount == 2 && surroundIsPositive)) {
- entryExitT.y = t;
-
- // entry and exit have been found, so the loop can stop
- break;
- }
- }
- return entryExitT;
-}
-#endif
-
#if defined(SHAPE_BOX)
// Unit cube from [-1, +1]
vec2 intersectUnitCube(Ray ray)
@@ -353,7 +348,7 @@ vec2 intersectUnitCube(Ray ray)
float tMax = min(min(m1.x, m1.y), m1.z);
if (tMin >= tMax) {
- return vec2(NO_HIT, NO_HIT);
+ return vec2(NO_HIT);
}
return vec2(tMin, tMax);
@@ -369,7 +364,7 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
float t = -o.z / d.z;
vec2 planePos = o.xy + d.xy * t;
if (any(greaterThan(abs(planePos), vec2(1.0)))) {
- return vec2(NO_HIT, NO_HIT);
+ return vec2(NO_HIT);
}
return vec2(t, t);
@@ -377,9 +372,9 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
#endif
#if defined(SHAPE_BOX)
-vec2 intersectBoxShape(Ray ray)
+vec2 intersectBoxShape(Ray ray, out Intersections ix)
{
- #if defined(BOUNDS)
+ #if defined(BOX_BOUNDED)
vec3 pos = 0.5 * (u_minBounds + u_maxBounds);
vec3 scale = 0.5 * (u_maxBounds - u_minBounds);
@@ -408,13 +403,14 @@ vec2 intersectBoxShape(Ray ray)
Ray unitRay = Ray((ray.pos - pos) / scale, ray.dir / scale);
return intersectUnitCube(unitRay);
}
- #else
- return intersectUnitCube(ray);
#endif
+
+ return intersectUnitCube(ray);
+
}
#endif
-#if ((defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)) || defined(SHAPE_CYLINDER) && (defined(BOUNDS_2_MIN) || defined(BOUNDS_2_MAX))))
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_REGULAR)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_REGULAR))
vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
{
vec2 o = ray.pos.xy;
@@ -436,22 +432,33 @@ vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
float tmax = max(t1, t2);
float smin = tmin == t1 ? s1 : s2;
float smax = tmin == t1 ? s2 : s1;
-
+
bool e = tmin >= 0.0;
bool f = tmax >= 0.0;
bool g = smin >= 0.0;
bool h = smax >= 0.0;
-
- // if () return vec2(tmin, tmax);
- // else if () return vec2(-INF_HIT, tmin);
- // else if () return vec2(-INF_HIT, tmax);
- // else if () return vec2(tmax, +INF_HIT);
- // else return vec2(NO_HIT, NO_HIT);
-
+
if (e != g && f == h) return vec2(tmin, tmax);
else if (e == g && f == h) return vec2(-INF_HIT, tmin);
else if (e != g && f != h) return vec2(tmax, +INF_HIT);
- else return vec2(NO_HIT, NO_HIT);
+ else return vec2(NO_HIT);
+}
+#endif
+
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_FLIPPED)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_FLIPPED))
+vec2 intersectHalfSpace(Ray ray, float angle)
+{
+ vec2 o = ray.pos.xy;
+ vec2 d = ray.dir.xy;
+ vec2 n = vec2(sin(angle), -cos(angle));
+
+ float a = dot(o, n);
+ float b = dot(d, n);
+ float t = -a / b;
+ float s = sign(a);
+
+ if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
+ else return vec2(-INF_HIT, t);
}
#endif
@@ -466,7 +473,7 @@ vec2 intersectUnitSphere(Ray ray)
float det = b * b - c;
if (det < 0.0) {
- return vec2(NO_HIT, NO_HIT);
+ return vec2(NO_HIT);
}
det = sqrt(det);
@@ -491,7 +498,7 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NO_HIT, NO_HIT);
+ return vec2(NO_HIT);
}
det = sqrt(det);
@@ -504,51 +511,20 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(BOUNDS_1_MIN) || defined(BOUNDS_1_MAX))
-vec2 intersectUncappedCone(Ray ray, float latitude)
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+vec2 intersectDoubleEndedCone(Ray ray, float latitude)
{
- float side = sign(latitude);
- float halfAngle = czm_piOverTwo - abs(latitude);
-
vec3 o = ray.pos;
vec3 d = ray.dir;
- if (side < 0.0) {
- o.z *= -1.0;
- d.z *= -1.0;
- }
-
- float h = cos(halfAngle);
+ float h = cos(czm_piOverTwo - abs(latitude));
float hh = h * h;
- float dd = dot(d, d);
- float od = dot(o, d);
- float oo = dot(o, o);
-
- // if (abs(normalize(o).z - h) < 0.1 && abs(d.z - h) < 0.1) {
- // if (angle > 0.0) {
- // // if (o.z * s < 0.0) {
- // // return vec2(0.0, +INF_HIT);
- // // } else {
- // // return vec2(-o.z / d.z, 0.0);
- // // // return (o.z + x * d.z) * s = 0
- // // }
- // return vec2(-INF_HIT, +INF_HIT);
- // } else {
- // return vec2(NO_HIT, NO_HIT);
- // }
- // // return vec2(-10.0, +10.0);
- // }
-
- float a = d.z * d.z - dd * hh;
- float b = d.z * o.z - od * hh;
- float c = o.z * o.z - oo * hh;
+ float a = d.z * d.z - dot(d, d) * hh;
+ float b = d.z * o.z - dot(o, d) * hh;
+ float c = o.z * o.z - dot(o, o) * hh;
float det = b * b - a * c;
if (det < 0.0) {
- if (side > 0.0) {
- return vec2(NO_HIT, NO_HIT);
- } else {
- return vec2(-INF_HIT, +INF_HIT);
- }
+ return vec2(NO_HIT);
}
det = sqrt(det);
@@ -556,78 +532,131 @@ vec2 intersectUncappedCone(Ray ray, float latitude)
float t2 = (-b + det) / a;
float tmin = min(t1, t2);
float tmax = max(t1, t2);
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+vec4 intersectFlippedCone(Ray ray, float latitude) {
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ vec2 ix = intersectDoubleEndedCone(ray, latitude);
+
+ if (ix.x == NO_HIT) {
+ return vec4(NO_HIT);
+ }
+ float tmin = ix.x;
+ float tmax = ix.y;
float h1 = o.z + tmin * d.z;
float h2 = o.z + tmax * d.z;
- if (side > 0.0) {
- if (h1 < 0.0 && h2 < 0.0) return vec2(NO_HIT, NO_HIT);
- else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
- else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
- else return vec2(tmin, tmax);
- } else {
- if (h1 < 0.0 && h2 < 0.0) return vec2(-INF_HIT, +INF_HIT);
- else if (h1 < 0.0) return vec2(-INF_HIT, tmax);
- else if (h2 < 0.0) return vec2(tmin, +INF_HIT);
- else if (tmin < 0.0) return vec2(tmax, +INF_HIT);
- else return vec2(-INF_HIT, tmin);
+ // One interval
+ if (h1 < 0.0 && h2 < 0.0) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
+ else if (h1 < 0.0) return vec4(-INF_HIT, tmax, NO_HIT, NO_HIT);
+ else if (h2 < 0.0) return vec4(tmin, +INF_HIT, NO_HIT, NO_HIT);
+ else if (tmin < 0.0) return vec4(tmax, +INF_HIT, NO_HIT, NO_HIT);
+ // Two intervals
+ else return vec4(-INF_HIT, tmin, tmax, +INF_HIT);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_TOP_REGULAR))
+vec2 intersectRegularCone(Ray ray, float latitude) {
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ vec2 ix = intersectDoubleEndedCone(ray, latitude);
+
+ if (ix.x == NO_HIT) {
+ return vec2(NO_HIT);
}
+
+ float tmin = ix.x;
+ float tmax = ix.y;
+ float h1 = o.z + tmin * d.z;
+ float h2 = o.z + tmax * d.z;
+
+ if (h1 < 0.0 && h2 < 0.0) return vec2(NO_HIT);
+ else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
+ else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
+ else return vec2(tmin, tmax);
}
#endif
#if defined(SHAPE_ELLIPSOID)
-vec2 intersectEllipsoidShape(Ray ray)
+void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
{
- #if !defined(BOUNDS)
- return intersectUnitSphereUnnormalizedDirection(ray);
- #else
- float lonMin = u_minBounds.x; // [-pi,+pi]
- float lonMax = u_maxBounds.x; // [-pi,+pi]
- float latMin = u_minBounds.y; // [-halfPi,+halfPi]
- float latMax = u_maxBounds.y; // [-halfPi,+halfPi]
- float heightMin = u_minBounds.z; // [-inf,+inf]
- float heightMax = u_maxBounds.z; // [-inf,+inf]
-
- vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- if (outerIntersect == vec2(NO_HIT, NO_HIT)) {
- return vec2(NO_HIT, NO_HIT);
- }
+ // Outer ellipsoid
+ vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
+ ix.intersections[ELLIPSOID_OUTER + 0] = vec2(outerIntersect.x, POSITIVE_ENTRY);
+ ix.intersections[ELLIPSOID_OUTER + 1] = vec2(outerIntersect.y, POSITIVE_EXIT);
+
+ // Exit early if the outer ellipsoid was missed.
+ if (outerIntersect.x == NO_HIT) {
+ return;
+ }
+
+ // Inner ellipsoid
+ #if defined(ELLIPSOID_INNER)
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ ix.intersections[ELLIPSOID_INNER + 0] = vec2(innerIntersect.x, NEGATIVE_ENTRY);
+ ix.intersections[ELLIPSOID_INNER + 1] = vec2(innerIntersect.y, NEGATIVE_EXIT);
+ #endif
- vec2 intersections[SHAPE_INTERSECTION_COUNT];
- intersections[0] = vec2(float(0), outerIntersect.x);
- intersections[1] = vec2(float(1), outerIntersect.y);
+ // Bottom cone
+ #if defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED)
+ // Flip the inputs because the intersection function expects a cone growing towards +Z.
+ float flippedSouth = -u_ellipsoidRectangle.y;
+ Ray flippedRay = ray;
+ flippedRay.dir.z *= -1.0;
+ flippedRay.pos.z *= -1.0;
- #if defined(BOUNDS_2_MIN)
- Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
- vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- intersections[2] = vec2(float(2), innerIntersect.x);
- intersections[3] = vec2(float(3), innerIntersect.y);
- #endif
-
- #if defined(BOUNDS_1_MIN)
- // Flip the inputs because the intersection function expects a cone growing towards +Z.
- Ray flippedRay = ray;
- flippedRay.dir.z *= -1.0;
- flippedRay.pos.z *= -1.0;
- vec2 botConeIntersect = intersectUncappedCone(flippedRay, -latMin);
- intersections[BOUNDS_1_MIN_IDX * 2 + 0] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 0), botConeIntersect.x);
- intersections[BOUNDS_1_MIN_IDX * 2 + 1] = vec2(float(BOUNDS_1_MIN_IDX * 2 + 1), botConeIntersect.y);
+ #if defined(ELLIPSOID_CONE_BOT_REGULAR)
+ vec2 botConeIx = intersectRegularCone(flippedRay, flippedSouth);
+ ix.intersections[ELLIPSOID_CONE_BOT_REGULAR + 0] = vec2(botConeIx.x, -1.0);
+ ix.intersections[ELLIPSOID_CONE_BOT_REGULAR + 1] = vec2(botConeIx.y, -1.0);
+ #elif defined(ELLIPSOID_CONE_BOT_FLIPPED)
+ vec4 botConeIx = intersectFlippedCone(flippedRay, flippedSouth);
+ ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 0] = vec2(botConeIx.x, -1.0);
+ ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 1] = vec2(botConeIx.y, -1.0);
+ ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 2] = vec2(botConeIx.z, -1.0);
+ ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 3] = vec2(botConeIx.w, -1.0);
#endif
-
- #if defined(BOUNDS_1_MAX)
- vec2 topConeIntersect = intersectUncappedCone(ray, latMax);
- intersections[BOUNDS_1_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 0), topConeIntersect.x);
- intersections[BOUNDS_1_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_1_MAX_IDX * 2 + 1), topConeIntersect.y);
+ #endif
+
+ // Top cone
+ #if defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ float north = u_ellipsoidRectangle.w;
+ #if defined(ELLIPSOID_CONE_TOP_REGULAR)
+ vec2 topConeIntersect = intersectRegularCone(ray, north);
+ ix.intersections[ELLIPSOID_CONE_TOP_REGULAR + 0] = vec2(topConeIntersect.x, -1.0);
+ ix.intersections[ELLIPSOID_CONE_TOP_REGULAR + 1] = vec2(topConeIntersect.y, -1.0);
+ #elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ vec4 topConeIntersect = intersectFlippedCone(ray, north);
+ ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 0] = vec2(topConeIntersect.x, -1.0);
+ ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 1] = vec2(topConeIntersect.y, -1.0);
+ ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 2] = vec2(topConeIntersect.z, -1.0);
+ ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 3] = vec2(topConeIntersect.w, -1.0);
#endif
-
- #if defined(BOUNDS_0_MIN) || defined(BOUNDS_0_MAX)
- vec2 wedgeIntersect = intersectWedge(ray, lonMin, lonMax);
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
- intersections[BOUNDS_0_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_0_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
+ #endif
+
+ // Wedge
+ #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
+ float west = u_ellipsoidRectangle.x; // [-pi,+pi]
+ float east = u_ellipsoidRectangle.z; // [-pi,+pi]
+ #if defined(ELLIPSOID_WEDGE_REGULAR)
+ vec2 wedgeIntersect = intersectWedge(ray, west, east);
+ ix.intersections[ELLIPSOID_WEDGE_REGULAR + 0] = vec2(wedgeIntersect.x, -1.0);
+ ix.intersections[ELLIPSOID_WEDGE_REGULAR + 1] = vec2(wedgeIntersect.y, -1.0);
+ #elif defined(ELLIPSOID_WEDGE_FLIPPED)
+ vec2 planeIntersectWest = intersectHalfSpace(ray, west);
+ vec2 planeIntersectEast = intersectHalfSpace(ray, east);
+ ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 0] = vec2(planeIntersectWest.x, -1.0);
+ ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 1] = vec2(planeIntersectWest.y, -1.0);
+ ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 2] = vec2(planeIntersectEast.x, -1.0);
+ ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 3] = vec2(planeIntersectEast.y, -1.0);
#endif
-
- return resolveIntersections(intersections);
- // return vec2(0.0);
#endif
}
#endif
@@ -644,7 +673,7 @@ vec2 intersectUnitCylinder(Ray ray)
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NO_HIT, NO_HIT);
+ return vec2(NO_HIT);
}
det = sqrt(det);
@@ -682,7 +711,7 @@ vec2 intersectUnitCircle(Ray ray) {
float distSqr = dot(zPlanePos, zPlanePos);
if (distSqr > 1.0) {
- return vec2(NO_HIT, NO_HIT);
+ return vec2(NO_HIT);
}
return vec2(t, t);
@@ -701,7 +730,7 @@ vec2 intersectInfiniteUnitCylinder(Ray ray)
float det = b * b - a * c;
if (det < 0.0) {
- return vec2(NO_HIT, NO_HIT);
+ return vec2(NO_HIT);
}
det = sqrt(det);
@@ -715,7 +744,7 @@ vec2 intersectInfiniteUnitCylinder(Ray ray)
#endif
#if defined(SHAPE_CYLINDER)
-vec2 intersectCylinderShape(Ray ray)
+void intersectCylinderShape(Ray ray, inout Intersections ix)
{
#if !defined(BOUNDS)
return intersectUnitCylinder(ray);
@@ -744,13 +773,12 @@ vec2 intersectCylinderShape(Ray ray)
outerIntersect = intersectUnitCylinder(outerRay);
}
- if (outerIntersect == vec2(NO_HIT, NO_HIT)) {
- return vec2(NO_HIT, NO_HIT);
+ if (outerIntersect.x == NO_HIT) {
+ return;
}
- vec2 intersections[SHAPE_INTERSECTION_COUNT];
- intersections[0] = vec2(float(0), outerIntersect.x);
- intersections[1] = vec2(float(1), outerIntersect.y);
+ ix.intersections[0] = vec2(outerIntersect.x, float(0));
+ ix.intersections[1] = vec2(outerIntersect.y, float(1));
#if defined(BOUNDS_0_MIN)
vec3 innerScale = vec3(minRadius, minRadius, 1.0);
@@ -776,7 +804,7 @@ vec2 intersectCylinderShape(Ray ray)
// [outerMin, innerMin, innerMax, outerMax] will bubble sort to
// [outerMin, innerMin, innerMax, outerMax] which will work correctly.
- // Note: If resolveIntersections() changes its sorting function
+ // Note: If sortIntersections() changes its sorting function
// from bubble sort to something else, this code may need to change.
intersections[0] = vec2(float(0), outerIntersect.x);
@@ -790,47 +818,105 @@ vec2 intersectCylinderShape(Ray ray)
vec2 wedgeIntersect = intersectWedge(ray, minAngle, maxAngle);
intersections[BOUNDS_2_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_2_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
intersections[BOUNDS_2_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_2_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
- #endif
-
- return resolveIntersections(intersections);
+ #endif
#endif
}
#endif
-vec2 intersectShape(vec3 positionUv, vec3 directionUv) {
- // Do a ray-shape intersection to find the exact starting and ending points.
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- Ray ray = Ray(positionUv * 2.0 - 1.0, directionUv * 2.0);
+void sortIntersections(inout Intersections ix) {
+ // Sort the intersections from min T to max T with bubble sort.
+ // Note: If this sorting function changes, some of the intersection test may
+ // need to be updated. Search for "bubble sort" to find those areas.
+ const int passes = SCENE_INTERSECTION_COUNT * 2 - 1;
+ for (int n = passes; n > 0; --n) {
+ for (int i = 0; i < passes; ++i) {
+ // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to break early instead
+ if (i >= n) { break; }
+
+ vec2 intersect0 = ix.intersections[i + 0];
+ vec2 intersect1 = ix.intersections[i + 1];
- #if defined(SHAPE_BOX)
- vec2 entryExitT = intersectBoxShape(ray);
- #elif defined(SHAPE_ELLIPSOID)
- vec2 entryExitT = intersectEllipsoidShape(ray);
- #elif defined(SHAPE_CYLINDER)
- vec2 entryExitT = intersectCylinderShape(ray);
- #endif
+ float t0 = intersect0.x;
+ float t1 = intersect1.x;
+ float b0 = intersect0.y;
+ float b1 = intersect1.y;
- if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
- // Intersection is invalid when start and end are behind the ray.
- return vec2(NO_HIT, NO_HIT);
+ float tmin = min(t0, t1);
+ float tmax = max(t0, t1);
+ float bmin = tmin == t0 ? b0 : b1;
+ float bmax = tmin == t0 ? b1 : b0;
+
+ ix.intersections[i + 0] = vec2(tmin, bmin);
+ ix.intersections[i + 1] = vec2(tmax, bmax);
+ }
}
+}
+
+vec2 nextIntersection(inout Intersections ix) {
+ vec2 entryExitT = vec2(NO_HIT);
- // Set start to 0 when ray is inside the shape.
- entryExitT.x = max(entryExitT.x, 0.0);
+ #if (SCENE_INTERSECTION_COUNT == 1)
+ if (ix.index == 0) {
+ entryExitT.x = ix.intersections[0].x;
+ entryExitT.y = ix.intersections[1].x;
+ ix.index += 1;
+ }
+ #else
+ int surroundCount = 0;
+ bool surroundIsPositive = false;
+ const int passCount = SCENE_INTERSECTION_COUNT * 2;
+ for (int i = 0; i < passCount; i++)
+ {
+ vec2 intersect = ix.intersections[i];
+ float t = intersect.x;
+ bool currShapeIsPositive = intersect.y < 2.0;
+ bool enter = mod(intersect.y, 2.0) == 0.0;
+
+ surroundCount += enter ? +1 : -1;
+ surroundIsPositive = currShapeIsPositive ? enter : surroundIsPositive;
+
+ // The loop should be: for (i = ix.index; i < passCount; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to continue instead.
+ if (i < ix.index) {
+ continue;
+ }
+
+ // entering positive or exiting negative
+ if (surroundCount == 1 && surroundIsPositive && enter == currShapeIsPositive) {
+ entryExitT.x = t;
+ }
+
+ // exiting positive or entering negative after being inside positive
+ // TODO: Can this be simplified?
+ bool exitPositive = !enter && currShapeIsPositive && surroundCount == 0;
+ bool enterNegativeFromPositive = enter && !currShapeIsPositive && surroundCount == 2 && surroundIsPositive;
+ if (exitPositive || enterNegativeFromPositive) {
+ entryExitT.y = t;
+
+ // entry and exit have been found, so the loop can stop
+ if (exitPositive) {
+ ix.index = SCENE_INTERSECTION_COUNT * 2;
+ } else {
+ ix.index = i + 1;
+ }
+ break;
+ }
+ }
+ #endif
return entryExitT;
}
#if defined(DEPTH_TEST)
-float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 viewPosUv, vec3 viewDirUv) {
+float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directionUv) {
float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenUv));
if (logDepthOrDepth != 0.0) {
// Calculate how far the ray must travel before it hits the depth buffer.
vec4 eyeCoordinateDepth = czm_windowToEyeCoordinates(fragCoord, logDepthOrDepth);
eyeCoordinateDepth /= eyeCoordinateDepth.w;
vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
- return dot(viewDirUv, depthPositionUv - viewPosUv);
+ return dot(directionUv, depthPositionUv - positionUv);
} else {
// There's no depth at this position so set it to some really far value.
return czm_infinity;
@@ -838,6 +924,48 @@ float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 viewPosUv, vec3 viewDir
}
#endif
+vec2 intersectScene(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directionUv, out Intersections ix) {
+ // Do a ray-shape intersection to find the exact starting and ending points.
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ Ray ray = Ray(positionUv * 2.0 - 1.0, directionUv * 2.0);
+
+ #if defined(SHAPE_BOX)
+ intersectBoxShape(ray, ix);
+ #elif defined(SHAPE_ELLIPSOID)
+ intersectEllipsoidShape(ray, ix);
+ #elif defined(SHAPE_CYLINDER)
+ intersectCylinderShape(ray, ix);f
+ #endif
+
+ // Exit early if the shape was completely missed
+ if (ix.intersections[0].x == NO_HIT) {
+ return vec2(NO_HIT);
+ }
+
+ #if defined(DEPTH_TEST)
+ float depthT = intersectDepth(fragCoord, screenUv, positionUv, directionUv);
+ ix.intersections[DEPTH_INTERSECTION_IDX + 0] = vec2(depthT, NEGATIVE_ENTRY);
+ ix.intersections[DEPTH_INTERSECTION_IDX + 1] = vec2(+INF_HIT, NEGATIVE_EXIT);
+ #endif
+
+ sortIntersections(ix);
+
+ // Find the first intersection interval
+ ix.index = 0;
+ vec2 entryExitT = nextIntersection(ix);
+
+ // Intersection is invalid when start and end are behind the ray.
+ if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
+ return vec2(NO_HIT);
+ }
+
+ // Set start to 0 when ray is inside the shape.
+ entryExitT.x = max(entryExitT.x, 0.0);
+
+ return entryExitT;
+}
+
#if defined(SHAPE_BOX)
vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
vec3 positionShape = positionUv;
@@ -939,12 +1067,20 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
float longitude = (atan(geodeticSurfaceNormal.y, geodeticSurfaceNormal.x) + czm_pi) / czm_twoPi; // 5
float latitude = (asin(geodeticSurfaceNormal.z) + czm_piOverTwo) / czm_pi; // 6
- #if defined(BOUNDS)
+ // #if (defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED))
+ // {
+ // longitude = (longitude - u_ellipsoidWestUv) * u_ellipsoidInverseLongitudeRangeUv;
+ // }
+ // #endif
+ // #if (defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+ // {
+ // latitude = (latitude - u_ellipsoidSouthUv) * u_ellipsoidInverseLatitudeRangeUv;
+ // }
+ // #endif
+ #if (defined(ELLIPSOID_INNER))
+ {
height *= u_ellipsoidInverseHeightDifferenceUv;
- float minLongitude = u_minBoundsUv.x;
- float minLatitude = u_minBoundsUv.y;
- longitude = (longitude - minLongitude) * u_inverseBoundsUv.x;
- latitude = (latitude - minLatitude) * u_inverseBoundsUv.y;
+ }
#endif
return vec3(longitude, latitude, height);
@@ -1281,25 +1417,18 @@ void main()
vec3 viewDirWorld = normalize(czm_inverseViewRotation * eyeDirection); // normalize again just in case
vec3 viewDirUv = normalize(u_transformDirectionViewToLocal * eyeDirection); // normalize again just in case
vec3 viewPosUv = u_cameraPositionUv;
- vec2 entryExitT = intersectShape(viewPosUv, viewDirUv);
- // Exit early if the shape was completely missed.
- if (entryExitT == vec2(NO_HIT, NO_HIT)) {
+ Intersections ix;
+ vec2 entryExitT = intersectScene(fragCoord.xy, screenUv, viewPosUv, viewDirUv, ix);
+
+ // Exit early if the scene was completely missed.
+ if (entryExitT == vec2(NO_HIT)) {
discard;
}
float currT = entryExitT.x;
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
-
- #if defined(DEPTH_TEST)
- float depthT = intersectDepth(fragCoord.xy, screenUv, viewPosUv, viewDirUv);
-
- // Exit early if the depth is before the start position.
- if (depthT <= currT) {
- discard;
- }
- #endif
vec4 colorAccum = vec4(0.0);
@@ -1394,25 +1523,17 @@ void main()
currT += stepT;
positionUv += stepT * viewDirUv;
- // Exit early if the ray is occluded by depth texture
- #if defined(DEPTH_TEST)
- if (currT >= depthT) {
- break;
- }
- #endif
-
- // Do another intersection test against the shape if the ray has entered empty space
+ // Check if there's more intersections.
if (currT > endT) {
- vec2 entryExitT = intersectShape(positionUv, viewDirUv);
-
- // Stop raymarching if it doesn't hit anything
- if (entryExitT == vec2(NO_HIT, NO_HIT)) {
+ vec2 entryExitT = nextIntersection(ix);
+ if (entryExitT == vec2(NO_HIT)) {
break;
+ } else {
+ // Found another intersection. Keep raymarching.
+ currT += entryExitT.x;
+ endT += entryExitT.y;
+ positionUv += entryExitT.x * viewDirUv;
}
-
- currT += entryExitT.x;
- endT += entryExitT.y;
- positionUv += entryExitT.x * viewDirUv;
}
// Traverse the tree from the current ray position.
diff --git a/Source/Shaders/VoxelVS.glsl b/Source/Shaders/VoxelVS.glsl
index 6c29c959b1c..c83efe4ea4e 100644
--- a/Source/Shaders/VoxelVS.glsl
+++ b/Source/Shaders/VoxelVS.glsl
@@ -8,4 +8,5 @@ void main() {
vec2 translation = 0.5 * (aabbMax + aabbMin);
vec2 scale = 0.5 * (aabbMax - aabbMin);
gl_Position = vec4(position * scale + translation, 0.0, 1.0);
+ gl_Position = vec4(position, 0.0, 1.0);
}
From 944c78136052222f424fe82f3f74d2f7188f79d7 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Tue, 12 Apr 2022 18:36:49 -0400
Subject: [PATCH 019/679] better box
---
Apps/Sandcastle/gallery/Voxels.html | 20 +--
Source/Scene/VoxelBoxShape.js | 197 ++++++++++++++++++++++------
Source/Scene/VoxelPrimitive.js | 6 +-
Source/Shaders/VoxelFS.glsl | 93 ++++++-------
4 files changed, 212 insertions(+), 104 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index 5d247673b62..c8ce699bf5e 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -396,21 +396,6 @@
});
Sandcastle.addToolbarMenu([
- {
- text: "Ellipsoid - Procedural Tile",
- onselect: function () {
- const provider = new ProceduralSingleTileVoxelProvider(
- Cesium.VoxelShapeType.ELLIPSOID
- );
- const primitive = createPrimitive(provider, customShaderColor);
- primitive.readyPromise.then(function () {
- console.log(primitive.boundingSphere.center);
- viewer.camera.flyToBoundingSphere(primitive.boundingSphere, {
- duration: 0.0,
- });
- });
- },
- },
{
text: "Box - Procedural Tile",
onselect: function () {
@@ -474,6 +459,11 @@
Cesium.VoxelShapeType.ELLIPSOID
);
const primitive = createPrimitive(provider, customShaderColor);
+ primitive.readyPromise.then(function () {
+ viewer.camera.flyToBoundingSphere(primitive.boundingSphere, {
+ duration: 0.0,
+ });
+ });
},
},
{
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index 47a5cb8e161..ed1a8c60ebb 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -75,8 +75,11 @@ function VoxelBoxShape() {
* @readonly
*/
this.shaderUniforms = {
- boxMinBounds: new Cartesian3(),
- boxMaxBounds: new Cartesian3(),
+ boxScaleUvToBoundsUv: new Cartesian3(),
+ boxTranslateUvToBoundsUv: new Cartesian3(),
+ boxScaleUvToBounds: new Cartesian3(),
+ boxTranslateUvToBounds: new Cartesian3(),
+ boxTransformUvToBounds: new Matrix4(),
};
/**
@@ -84,7 +87,7 @@ function VoxelBoxShape() {
* @readonly
*/
this.shaderDefines = {
- BOX_INTERSECTION_COUNT: 1,
+ BOX_INTERSECTION_COUNT: 1, // never changes
BOX_BOUNDED: undefined,
BOX_XY_PLANE: undefined,
BOX_XZ_PLANE: undefined,
@@ -95,6 +98,31 @@ function VoxelBoxShape() {
const scratchTranslation = new Cartesian3();
const scratchScale = new Cartesian3();
const scratchRotation = new Matrix3();
+const scratchTransformLocalToBounds = new Matrix4();
+const scratchBoundsTranslation = new Cartesian3();
+const scratchBoundsScale = new Cartesian3();
+
+const transformUvToLocal = Matrix4.fromRotationTranslation(
+ Matrix3.fromUniformScale(2.0, new Matrix3()),
+ new Cartesian3(-1.0, -1.0, -1.0),
+ new Matrix4()
+);
+
+const transformXYZToZYX = Matrix4.fromRotation(
+ Matrix3.fromColumnMajorArray(
+ [0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0],
+ new Matrix3()
+ ),
+ new Matrix4()
+);
+
+const transformXYZToXZY = Matrix4.fromRotation(
+ Matrix3.fromColumnMajorArray(
+ [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0],
+ new Matrix3()
+ ),
+ new Matrix4()
+);
/**
* Update the shape's state.
@@ -130,10 +158,10 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
const scale = Matrix4.getScale(modelMatrix, scratchScale);
- // Not visible if:
+ // Box is not visible if:
// - any of the min bounds exceed the max bounds
- // - two or more of the min bounds equal the max bounds (line and point respectively)
- // - scale is 0 for any component
+ // - two or more of the min bounds equal the max bounds (line / point)
+ // - scale is 0 for any component (too annoying to reconstruct rotation matrix)
if (
minBounds.x > maxBounds.x ||
minBounds.y > maxBounds.y ||
@@ -174,27 +202,113 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
const shaderUniforms = this.shaderUniforms;
const shaderDefines = this.shaderDefines;
- shaderUniforms.minBounds = Cartesian3.clone(
- minBounds,
- shaderUniforms.minBounds
- );
- shaderUniforms.maxBounds = Cartesian3.clone(
- maxBounds,
- shaderUniforms.maxBounds
- );
+ // To keep things simple, clear the defines every frame
+ shaderDefines["BOX_BOUNDED"] = undefined;
+ shaderDefines["BOX_XY_PLANE"] = undefined;
+ shaderDefines["BOX_XZ_PLANE"] = undefined;
+ shaderDefines["BOX_YZ_PLANE"] = undefined;
- const hasBounds =
- !Cartesian3.equals(minBounds, defaultMinBounds) &&
- !Cartesian3.equals(maxBounds, defaultMaxBounds);
- shaderDefines.BOX_BOUNDS = hasBounds ? 1 : undefined;
+ if (
+ minBounds.x !== defaultMinBounds.x ||
+ minBounds.y !== defaultMinBounds.y ||
+ minBounds.z !== defaultMinBounds.z ||
+ maxBounds.x !== defaultMaxBounds.x ||
+ maxBounds.y !== defaultMaxBounds.y ||
+ maxBounds.z !== defaultMaxBounds.z
+ ) {
+ shaderDefines["BOX_BOUNDED"] = true;
+
+ // inverse(scale)
+ // inverse(maxBoundsUv - minBoundsUv)
+ // inverse((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
+ // inverse(0.5 * (maxBounds - minBounds))
+ // 2.0 / (maxBounds - minBounds) // with divide by zero protection
+ const boundsScaleLocalToBounds = Cartesian3.fromElements(
+ 2.0 / (minBounds.x === maxBounds.x ? 1.0 : maxBounds.x - minBounds.x),
+ 2.0 / (minBounds.y === maxBounds.y ? 1.0 : maxBounds.y - minBounds.y),
+ 2.0 / (minBounds.z === maxBounds.z ? 1.0 : maxBounds.z - minBounds.z),
+ scratchBoundsScale
+ );
+
+ // -inverse(scale) * translation // affine inverse
+ // -inverse(scale) * 0.5 * (minBounds + maxBounds)
+ const boundsTranslateLocalToBounds = Cartesian3.fromElements(
+ -boundsScaleLocalToBounds.x * 0.5 * (minBounds.x + maxBounds.x),
+ -boundsScaleLocalToBounds.y * 0.5 * (minBounds.y + maxBounds.y),
+ -boundsScaleLocalToBounds.z * 0.5 * (minBounds.z + maxBounds.z),
+ scratchBoundsTranslation
+ );
+
+ let transformLocalToBounds = Matrix4.fromRotationTranslation(
+ Matrix3.fromScale(boundsScaleLocalToBounds),
+ boundsTranslateLocalToBounds,
+ scratchTransformLocalToBounds
+ );
+
+ if (
+ minBounds.x === maxBounds.x ||
+ minBounds.y === maxBounds.y ||
+ minBounds.z === maxBounds.z
+ ) {
+ let transformAxisConversion;
+ if (minBounds.x === maxBounds.x) {
+ shaderDefines["BOX_YZ_PLANE"] = true;
+ transformAxisConversion = transformXYZToZYX;
+ } else if (minBounds.y === maxBounds.y) {
+ shaderDefines["BOX_XZ_PLANE"] = true;
+ transformAxisConversion = transformXYZToXZY;
+ } else if (minBounds.z === maxBounds.z) {
+ shaderDefines["BOX_XY_PLANE"] = true;
+ transformAxisConversion = Matrix4.IDENTITY;
+ }
+ transformLocalToBounds = Matrix4.multiply(
+ transformAxisConversion,
+ transformLocalToBounds,
+ transformLocalToBounds
+ );
+ }
+
+ shaderUniforms.boxTransformUvToBounds = Matrix4.multiplyTransformation(
+ transformLocalToBounds,
+ transformUvToLocal,
+ shaderUniforms.boxTransformUvToBounds
+ );
+ shaderUniforms.boxScaleUvToBounds = Matrix4.getScale(
+ shaderUniforms.boxTransformUvToBounds,
+ shaderUniforms.boxScaleUvToBounds
+ );
+ shaderUniforms.boxTranslateUvToBounds = Matrix4.getTranslation(
+ shaderUniforms.boxTransformUvToBounds,
+ shaderUniforms.boxTranslateUvToBounds
+ );
+
+ // Go from UV space to bounded UV space:
+ // delerp(posUv, minBoundsUv, maxBoundsUv)
+ // (posUv - minBoundsUv) / (maxBoundsUv - minBoundsUv)
+ // posUv / (maxBoundsUv - minBoundsUv) - minBoundsUv / (maxBoundsUv - minBoundsUv)
+ // scale = 1.0 / (maxBoundsUv - minBoundsUv)
+ // scale = 1.0 / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
+ // scale = 2.0 / (maxBounds - minBounds)
+ // translation = -minBoundsUv / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
+ // translation = -2.0 * (minBounds * 0.5 + 0.5) / (maxBounds - minBounds)
+ // translation = -scale * (minBounds * 0.5 + 0.5)
+ shaderUniforms.boxScaleUvToBoundsUv = Cartesian3.clone(
+ boundsScaleLocalToBounds,
+ shaderUniforms.boxScaleUvToBoundsUv
+ );
+ shaderUniforms.boxTranslateUvToBoundsUv = Cartesian3.fromElements(
+ -boundsScaleLocalToBounds.x * (minBounds.x * 0.5 + 0.5),
+ -boundsScaleLocalToBounds.y * (minBounds.y * 0.5 + 0.5),
+ -boundsScaleLocalToBounds.z * (minBounds.z * 0.5 + 0.5),
+ shaderUniforms.boxTranslateUvToBoundsUv
+ );
+ }
return true;
};
-const scratchMinBounds = new Cartesian3();
-const scratchMaxBounds = new Cartesian3();
-const scratchMinLerp = new Cartesian3();
-const scratchMaxLerp = new Cartesian3();
+const scratchTileMinBounds = new Cartesian3();
+const scratchTileMaxBounds = new Cartesian3();
/**
* Computes an oriented bounding box for a specified tile.
@@ -222,33 +336,30 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForTile = function (
Check.typeOf.object("result", result);
//>>includeEnd('debug');
+ const minBounds = this._minBounds;
+ const maxBounds = this._maxBounds;
const sizeAtLevel = 1.0 / Math.pow(2, tileLevel);
- const minBounds = Cartesian3.lerp(
- this._minBounds,
- this._maxBounds,
- Cartesian3.fromElements(
- sizeAtLevel * tileX,
- sizeAtLevel * tileY,
- sizeAtLevel * tileZ,
- scratchMinLerp
- ),
- scratchMinBounds
+ const tileMinBounds = Cartesian3.fromElements(
+ CesiumMath.lerp(minBounds.x, maxBounds.x, sizeAtLevel * tileX),
+ CesiumMath.lerp(minBounds.y, maxBounds.y, sizeAtLevel * tileY),
+ CesiumMath.lerp(minBounds.z, maxBounds.z, sizeAtLevel * tileZ),
+ scratchTileMinBounds
);
- const maxBounds = Cartesian3.lerp(
- this._minBounds,
- this._maxBounds,
- Cartesian3.fromElements(
- sizeAtLevel * (tileX + 1),
- sizeAtLevel * (tileY + 1),
- sizeAtLevel * (tileZ + 1),
- scratchMaxLerp
- ),
- scratchMaxBounds
+ const tileMaxBounds = Cartesian3.fromElements(
+ CesiumMath.lerp(minBounds.x, maxBounds.x, sizeAtLevel * (tileX + 1)),
+ CesiumMath.lerp(minBounds.y, maxBounds.y, sizeAtLevel * (tileY + 1)),
+ CesiumMath.lerp(minBounds.z, maxBounds.z, sizeAtLevel * (tileZ + 1)),
+ scratchTileMaxBounds
);
- return getBoxChunkObb(minBounds, maxBounds, this.shapeTransform, result);
+ return getBoxChunkObb(
+ tileMinBounds,
+ tileMaxBounds,
+ this.shapeTransform,
+ result
+ );
};
/**
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index b27bca23c64..3a3e6fbdaed 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1299,6 +1299,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Set member variables when the shape is dirty
const dimensions = provider.dimensions;
this._stepSizeUv = shape.computeApproximateStepSize(dimensions);
+ // TODO: check which of the `multiply` can be `multiplyTransformation`
this._transformPositionWorldToUv = Matrix4.multiply(
transformPositionLocalToUv,
transformPositionWorldToLocal,
@@ -1899,8 +1900,11 @@ function buildDrawCommands(that, context) {
// Shape specific defines
for (const key in shapeDefines) {
if (shapeDefines.hasOwnProperty(key)) {
- const value = shapeDefines[key];
+ let value = shapeDefines[key];
+ // if value is undefined, don't define it
+ // if value is true, define it to nothing
if (defined(value)) {
+ value = value === true ? undefined : value;
shaderBuilder.addDefine(key, value, ShaderDestination.FRAGMENT);
}
}
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index bad0f04fce0..b7f8bd9e420 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -194,9 +194,26 @@ uniform float u_stepSize;
#endif
#if defined(SHAPE_BOX)
- #define BOX_INTERSECTION_COUNT 1
- uniform vec2 u_boxMinBounds;
- uniform vec2 u_boxMaxBounds;
+ /* Box defines:
+ #define BOX_INTERSECTION_COUNT ### // always 1
+ #define BOX_BOUNDED
+ #define BOX_XY_PLANE
+ #define BOX_XZ_PLANE
+ #define BOX_YZ_PLANE
+ */
+
+ // Box uniforms:
+ #if defined(BOX_BOUNDED)
+ uniform vec3 u_boxScaleUvToBoundsUv;
+ uniform vec3 u_boxTranslateUvToBoundsUv;
+ #if defined(BOX_XY_PLANE) || defined(BOX_XZ_PLANE) || defined(BOX_YZ_PLANE)
+ uniform mat4 u_boxTransformUvToBounds;
+ #else
+ // Similar to u_boxTransformUvToBounds but fewer instructions needed.
+ uniform vec3 u_boxScaleUvToBounds;
+ uniform vec3 u_boxTranslateUvToBounds;
+ #endif
+ #endif
#endif
#if defined(SHAPE_ELLIPSOID)
@@ -240,6 +257,7 @@ uniform float u_stepSize;
#define CYLINDER_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
*/
+ // Cylinder uniforms
#if defined(CYLINDER_INNER)
uniform float u_something;
#endif
@@ -372,41 +390,28 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
#endif
#if defined(SHAPE_BOX)
-vec2 intersectBoxShape(Ray ray, out Intersections ix)
+void intersectBoxShape(Ray ray, out Intersections ix)
{
- #if defined(BOX_BOUNDED)
- vec3 pos = 0.5 * (u_minBounds + u_maxBounds);
- vec3 scale = 0.5 * (u_maxBounds - u_minBounds);
-
- if (any(equal(scale, vec3(0.0)))) {
- // Transform the ray into unit space on Z plane
- Ray flatRay;
- if (scale.x == 0.0) {
- flatRay = Ray(
- (ray.pos.yzx - pos.yzx) / vec3(scale.yz, 1.0),
- ray.dir.yzx / vec3(scale.yz, 1.0)
- );
- } else if (scale.y == 0.0) {
- flatRay = Ray(
- (ray.pos.xzy - pos.xzy) / vec3(scale.xz, 1.0),
- ray.dir.xzy / vec3(scale.xz, 1.0)
- );
- } else if (scale.z == 0.0) {
- flatRay = Ray(
- (ray.pos.xyz - pos.xyz) / vec3(scale.xy, 1.0),
- ray.dir.xyz / vec3(scale.xy, 1.0)
- );
- }
- return intersectUnitSquare(flatRay);
- } else {
- // Transform the ray into "unit space"
- Ray unitRay = Ray((ray.pos - pos) / scale, ray.dir / scale);
- return intersectUnitCube(unitRay);
- }
+ #if !defined(BOX_BOUNDED)
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir = ray.dir * 2.0;
+ vec2 entryExit = intersectUnitCube(ray);
+ #elif defined(BOX_XY_PLANE) || defined(BOX_XZ_PLANE) || defined(BOX_YZ_PLANE)
+ // Transform the ray into unit square space on Z plane
+ ray.pos = vec3(u_boxTransformUvToBounds * vec4(ray.pos, 1.0));
+ ray.dir = vec3(u_boxTransformUvToBounds * vec4(ray.dir, 0.0));
+ vec2 entryExit = intersectUnitSquare(ray);
+ #else
+ // Transform the ray into unit cube space
+ ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxTranslateUvToBounds;
+ ray.dir *= u_boxScaleUvToBounds;
+ vec2 entryExit = intersectUnitCube(ray);
#endif
- return intersectUnitCube(ray);
-
+ ix.intersections[0] = vec2(entryExit.x, POSITIVE_ENTRY);
+ ix.intersections[1] = vec2(entryExit.y, POSITIVE_EXIT);
}
#endif
@@ -925,11 +930,9 @@ float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 direct
#endif
vec2 intersectScene(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directionUv, out Intersections ix) {
+ Ray ray = Ray(positionUv, directionUv);
+
// Do a ray-shape intersection to find the exact starting and ending points.
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- Ray ray = Ray(positionUv * 2.0 - 1.0, directionUv * 2.0);
-
#if defined(SHAPE_BOX)
intersectBoxShape(ray, ix);
#elif defined(SHAPE_ELLIPSOID)
@@ -968,11 +971,11 @@ vec2 intersectScene(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directi
#if defined(SHAPE_BOX)
vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
- vec3 positionShape = positionUv;
- #if defined(BOUNDS)
- positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
+ #if defined(BOX_BOUNDED)
+ return positionUv * u_boxScaleUvToBoundsUv + u_boxTranslateUvToBoundsUv;
+ #else
+ return positionUv;
#endif
- return positionShape;
}
#endif
@@ -1422,7 +1425,7 @@ void main()
vec2 entryExitT = intersectScene(fragCoord.xy, screenUv, viewPosUv, viewDirUv, ix);
// Exit early if the scene was completely missed.
- if (entryExitT == vec2(NO_HIT)) {
+ if (entryExitT.x == NO_HIT) {
discard;
}
@@ -1526,7 +1529,7 @@ void main()
// Check if there's more intersections.
if (currT > endT) {
vec2 entryExitT = nextIntersection(ix);
- if (entryExitT == vec2(NO_HIT)) {
+ if (entryExitT.x == NO_HIT) {
break;
} else {
// Found another intersection. Keep raymarching.
From c412d3655a96f873a7ad6015591632aae42f3fb3 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 13 Apr 2022 10:26:31 -0400
Subject: [PATCH 020/679] made shader intersection interval code more self
contained
---
Source/Scene/VoxelBoxShape.js | 3 +-
Source/Scene/VoxelEllipsoidShape.js | 50 ++--
Source/Shaders/VoxelFS.glsl | 351 +++++++++++++++-------------
3 files changed, 214 insertions(+), 190 deletions(-)
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index ed1a8c60ebb..2438159a8d3 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -88,6 +88,7 @@ function VoxelBoxShape() {
*/
this.shaderDefines = {
BOX_INTERSECTION_COUNT: 1, // never changes
+ BOX_INTERSECTION_INDEX: 0, // never changes
BOX_BOUNDED: undefined,
BOX_XY_PLANE: undefined,
BOX_XZ_PLANE: undefined,
@@ -202,7 +203,7 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
const shaderUniforms = this.shaderUniforms;
const shaderDefines = this.shaderDefines;
- // To keep things simple, clear the defines every frame
+ // To keep things simple, clear the defines every time
shaderDefines["BOX_BOUNDED"] = undefined;
shaderDefines["BOX_XY_PLANE"] = undefined;
shaderDefines["BOX_XZ_PLANE"] = undefined;
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index b8271a59621..c343523864e 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -153,6 +153,7 @@ VoxelEllipsoidShape.prototype.update = function (
const defaultMinBounds = VoxelEllipsoidShape.DefaultMinBounds;
const defaultMaxBounds = VoxelEllipsoidShape.DefaultMaxBounds;
+ // Clamp the longitude / latitude to the valid range
const west = CesiumMath.clamp(
minBounds.x,
defaultMinBounds.x,
@@ -180,7 +181,7 @@ VoxelEllipsoidShape.prototype.update = function (
const minHeight = Math.max(minBounds.z, -minRadius);
const maxHeight = Math.max(maxBounds.z, -minRadius);
- // The closest and farthest a point can be from the center of the ellipsoid.
+ // Compute the closest and farthest a point can be from the center of the ellipsoid.
const innerExtent = Cartesian3.add(
radii,
Cartesian3.fromElements(minHeight, minHeight, minHeight, scratchInner),
@@ -243,6 +244,17 @@ VoxelEllipsoidShape.prototype.update = function (
const shaderUniforms = this.shaderUniforms;
const shaderDefines = this.shaderDefines;
+ // To keep things simple, clear the defines every time
+ shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = undefined;
+ shaderDefines["ELLIPSOID_OUTER"] = undefined;
+ shaderDefines["ELLIPSOID_INNER"] = undefined;
+ shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = undefined;
+
shaderUniforms.ellipsoidRectangle = Cartesian4.fromElements(
west,
south,
@@ -272,7 +284,8 @@ VoxelEllipsoidShape.prototype.update = function (
const rectangleWidth = Rectangle.computeWidth(this._rectangle);
const hasInnerEllipsoid = !Cartesian3.equals(innerExtent, Cartesian3.ZERO);
const hasWedgeRegular =
- rectangleWidth >= CesiumMath.PI && rectangleWidth < CesiumMath.TWO_PI;
+ rectangleWidth >= CesiumMath.PI &&
+ CesiumMath.lessThan(rectangleWidth, CesiumMath.TWO_PI, absEpsilon);
const hasWedgeFlipped = rectangleWidth < CesiumMath.PI;
const hasTopConeRegular = north >= 0.0 && north < +CesiumMath.PI_OVER_TWO;
const hasTopConeFlipped = north < 0.0;
@@ -283,12 +296,12 @@ VoxelEllipsoidShape.prototype.update = function (
let intersectionCount = 0;
// Intersects an outer ellipsoid for the max height.
- shaderDefines["ELLIPSOID_OUTER"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_OUTER"] = intersectionCount;
intersectionCount += 1;
// Intersects an inner ellipsoid for the min height.
if (hasInnerEllipsoid) {
- shaderDefines["ELLIPSOID_INNER"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_INNER"] = intersectionCount;
intersectionCount += 1;
// The percent of space that is between the inner and outer ellipsoid.
@@ -305,50 +318,33 @@ VoxelEllipsoidShape.prototype.update = function (
innerScale,
shaderUniforms.ellipsoidInnerRadiiUv
);
- } else {
- shaderDefines["ELLIPSOID_INNER"] = undefined;
}
// Intersects a wedge for the min and max longitude.
if (hasWedgeRegular) {
- shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = intersectionCount * 2;
- shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = undefined;
+ shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = intersectionCount;
intersectionCount += 1;
} else if (hasWedgeFlipped) {
- shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = intersectionCount;
intersectionCount += 2;
- } else {
- shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = undefined;
}
// Intersects a cone for min latitude
if (hasBottomConeRegular) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = intersectionCount * 2;
- shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = intersectionCount;
intersectionCount += 1;
} else if (hasBottomConeFlipped) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = intersectionCount;
intersectionCount += 2;
- } else {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = undefined;
}
// Intersects a cone for max latitude
if (hasTopConeRegular) {
- shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = intersectionCount * 2;
- shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = undefined;
+ shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = intersectionCount;
intersectionCount += 1;
} else if (hasTopConeFlipped) {
- shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = intersectionCount * 2;
+ shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = intersectionCount;
intersectionCount += 2;
- } else {
- shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = undefined;
}
shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = intersectionCount;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index b7f8bd9e420..db72be16bb7 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -138,10 +138,6 @@ void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
#define NO_HIT (-czm_infinity)
#define INF_HIT (czm_infinity * 0.5)
-#define POSITIVE_ENTRY 0.0
-#define POSITIVE_EXIT 1.0
-#define NEGATIVE_ENTRY 2.0
-#define NEGATIVE_EXIT 3.0
uniform ivec3 u_dimensions; // does not include padding
#if defined(PADDING)
@@ -196,6 +192,7 @@ uniform float u_stepSize;
#if defined(SHAPE_BOX)
/* Box defines:
#define BOX_INTERSECTION_COUNT ### // always 1
+ #define BOX_INTERSECTION_INDEX ### // always 0
#define BOX_BOUNDED
#define BOX_XY_PLANE
#define BOX_XZ_PLANE
@@ -230,10 +227,12 @@ uniform float u_stepSize;
*/
// Ellipsoid uniforms
- uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
uniform vec3 u_ellipsoidRadiiUv; // [0,1]
uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
+ #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED) || defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
+ #endif
#if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
uniform float u_ellipsoidWestUv;
uniform float u_ellipsoidInverseLongitudeRangeUv;
@@ -277,26 +276,20 @@ uniform float u_stepSize;
#endif
#if defined(DEPTH_TEST)
- #define DEPTH_INTERSECTION_IDX (SHAPE_INTERSECTION_COUNT * 2)
+ #define DEPTH_INTERSECTION_INDEX SHAPE_INTERSECTION_COUNT
#define SCENE_INTERSECTION_COUNT (SHAPE_INTERSECTION_COUNT + 1)
#else
#define SCENE_INTERSECTION_COUNT SHAPE_INTERSECTION_COUNT
#endif
-struct Intersections {
- vec2 intersections[SCENE_INTERSECTION_COUNT * 2];
- int index;
-};
-
+// --------------------------------------------------------
+// Misc math
+// --------------------------------------------------------
struct Ray {
vec3 pos;
vec3 dir;
};
-// --------------------------------------------------------
-// Misc math
-// --------------------------------------------------------
-
#if defined(JITTER)
#define HASHSCALE 50.0
float hash(vec2 p)
@@ -349,6 +342,131 @@ vec2 index1DTo2DTexcoord(int index, ivec2 dimensions, vec2 uvScale)
// --------------------------------------------------------
// Intersection tests, shape coordinate conversions, etc
// --------------------------------------------------------
+struct Intersections {
+ // Don't access these member variables directly - call the functions instead.
+
+ #if (SCENE_INTERSECTION_COUNT > 1)
+ // Store an array of intersections. Each intersection is composed of:
+ // x for the T value
+ // y for the shape type - which encodes positive vs negative and entering vs exiting
+ // For example:
+ // y = 0: positive shape entry
+ // y = 1: positive shape exit
+ // y = 2: negative shape entry
+ // y = 3: negative shape exit
+ vec2 intersections[SCENE_INTERSECTION_COUNT * 2];
+
+ // Maintain state for future nextIntersection calls
+ int index;
+ int surroundCount;
+ bool surroundIsPositive;
+ #else
+ // When there's only one positive shape intersection none of the extra stuff is needed.
+ float intersections[2];
+ #endif
+};
+
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#if (SCENE_INTERSECTION_COUNT > 1)
+ #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) ix.intersections[index * 2 + 0] = vec2(entryExit.x, float(index > 0) * 2.0 + 0.0); ix.intersections[index * 2 + 1] = vec2(entryExit.y, float(index > 0) * 2.0 + 1.0)
+#else
+ #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) ix.intersections[0] = entryExit.x; ix.intersections[1] = entryExit.y
+#endif
+
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#if (SCENE_INTERSECTION_COUNT > 1)
+ #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) ix.intersections[index].x
+#else
+ #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) ix.intersections[index]
+#endif
+
+#if (SCENE_INTERSECTION_COUNT > 1)
+vec2 nextIntersection(inout Intersections ix) {
+ vec2 entryExitT = vec2(NO_HIT);
+
+ const int passCount = SCENE_INTERSECTION_COUNT * 2;
+ for (int i = 0; i < passCount; i++) {
+ // The loop should be: for (i = ix.index; i < passCount; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to continue instead.
+ if (i < ix.index) {
+ continue;
+ }
+
+ vec2 intersect = ix.intersections[i];
+ float t = intersect.x;
+ bool currShapeIsPositive = intersect.y < 2.0;
+ bool enter = mod(intersect.y, 2.0) == 0.0;
+
+ ix.surroundCount += enter ? +1 : -1;
+ ix.surroundIsPositive = currShapeIsPositive ? enter : ix.surroundIsPositive;
+
+ // entering positive or exiting negative
+ if (ix.surroundCount == 1 && ix.surroundIsPositive && enter == currShapeIsPositive) {
+ entryExitT.x = t;
+ }
+
+ // exiting positive or entering negative after being inside positive
+ // TODO: Can this be simplified?
+ bool exitPositive = !enter && currShapeIsPositive && ix.surroundCount == 0;
+ bool enterNegativeFromPositive = enter && !currShapeIsPositive && ix.surroundCount == 2 && ix.surroundIsPositive;
+ if (exitPositive || enterNegativeFromPositive) {
+ entryExitT.y = t;
+
+ // entry and exit have been found, so the loop can stop
+ if (exitPositive) {
+ // After exiting positive shape there is nothing left to intersect, so jump to the end index.
+ ix.index = SCENE_INTERSECTION_COUNT * 2;
+ } else {
+ // There could be more intersections against the positive shape in the future.
+ ix.index = i + 1;
+ }
+ break;
+ }
+ }
+
+ return entryExitT;
+}
+#endif
+
+#if (SCENE_INTERSECTION_COUNT > 1)
+vec2 initializeIntersections(inout Intersections ix) {
+ // Sort the intersections from min T to max T with bubble sort.
+ // Note: If this sorting function changes, some of the intersection test may
+ // need to be updated. Search for "bubble sort" to find those areas.
+ const int passes = SCENE_INTERSECTION_COUNT * 2 - 1;
+ for (int n = passes; n > 0; --n) {
+ for (int i = 0; i < passes; ++i) {
+ // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to break early instead
+ if (i >= n) { break; }
+
+ vec2 intersect0 = ix.intersections[i + 0];
+ vec2 intersect1 = ix.intersections[i + 1];
+
+ float t0 = intersect0.x;
+ float t1 = intersect1.x;
+ float b0 = intersect0.y;
+ float b1 = intersect1.y;
+
+ float tmin = min(t0, t1);
+ float tmax = max(t0, t1);
+ float bmin = tmin == t0 ? b0 : b1;
+ float bmax = tmin == t0 ? b1 : b0;
+
+ ix.intersections[i + 0] = vec2(tmin, bmin);
+ ix.intersections[i + 1] = vec2(tmax, bmax);
+ }
+ }
+
+ // Prepare initial state for nextIntersection
+ ix.index = 0;
+ ix.surroundCount = 0;
+ ix.surroundIsPositive = false;
+
+ return nextIntersection(ix);
+}
+#endif
+
#if defined(SHAPE_BOX)
// Unit cube from [-1, +1]
vec2 intersectUnitCube(Ray ray)
@@ -410,8 +528,7 @@ void intersectBoxShape(Ray ray, out Intersections ix)
vec2 entryExit = intersectUnitCube(ray);
#endif
- ix.intersections[0] = vec2(entryExit.x, POSITIVE_ENTRY);
- ix.intersections[1] = vec2(entryExit.y, POSITIVE_EXIT);
+ setIntersection(ix, BOX_INTERSECTION_INDEX, entryExit);
}
#endif
@@ -545,14 +662,14 @@ vec2 intersectDoubleEndedCone(Ray ray, float latitude)
vec4 intersectFlippedCone(Ray ray, float latitude) {
vec3 o = ray.pos;
vec3 d = ray.dir;
- vec2 ix = intersectDoubleEndedCone(ray, latitude);
+ vec2 intersect = intersectDoubleEndedCone(ray, latitude);
- if (ix.x == NO_HIT) {
+ if (intersect.x == NO_HIT) {
return vec4(NO_HIT);
}
- float tmin = ix.x;
- float tmax = ix.y;
+ float tmin = intersect.x;
+ float tmax = intersect.y;
float h1 = o.z + tmin * d.z;
float h2 = o.z + tmax * d.z;
@@ -570,14 +687,14 @@ vec4 intersectFlippedCone(Ray ray, float latitude) {
vec2 intersectRegularCone(Ray ray, float latitude) {
vec3 o = ray.pos;
vec3 d = ray.dir;
- vec2 ix = intersectDoubleEndedCone(ray, latitude);
+ vec2 intersect = intersectDoubleEndedCone(ray, latitude);
- if (ix.x == NO_HIT) {
+ if (intersect.x == NO_HIT) {
return vec2(NO_HIT);
}
- float tmin = ix.x;
- float tmax = ix.y;
+ float tmin = intersect.x;
+ float tmax = intersect.y;
float h1 = o.z + tmin * d.z;
float h2 = o.z + tmax * d.z;
@@ -591,10 +708,14 @@ vec2 intersectRegularCone(Ray ray, float latitude) {
#if defined(SHAPE_ELLIPSOID)
void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
{
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir *= 2.0;
+
// Outer ellipsoid
vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- ix.intersections[ELLIPSOID_OUTER + 0] = vec2(outerIntersect.x, POSITIVE_ENTRY);
- ix.intersections[ELLIPSOID_OUTER + 1] = vec2(outerIntersect.y, POSITIVE_EXIT);
+ setIntersection(ix, ELLIPSOID_OUTER, outerIntersect);
// Exit early if the outer ellipsoid was missed.
if (outerIntersect.x == NO_HIT) {
@@ -605,8 +726,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
#if defined(ELLIPSOID_INNER)
Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- ix.intersections[ELLIPSOID_INNER + 0] = vec2(innerIntersect.x, NEGATIVE_ENTRY);
- ix.intersections[ELLIPSOID_INNER + 1] = vec2(innerIntersect.y, NEGATIVE_EXIT);
+ setIntersection(ix, ELLIPSOID_INNER, innerIntersect);
#endif
// Bottom cone
@@ -619,14 +739,11 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
#if defined(ELLIPSOID_CONE_BOT_REGULAR)
vec2 botConeIx = intersectRegularCone(flippedRay, flippedSouth);
- ix.intersections[ELLIPSOID_CONE_BOT_REGULAR + 0] = vec2(botConeIx.x, -1.0);
- ix.intersections[ELLIPSOID_CONE_BOT_REGULAR + 1] = vec2(botConeIx.y, -1.0);
+ setIntersection(ix, ELLIPSOID_CONE_BOT_REGULAR, botConeIx);
#elif defined(ELLIPSOID_CONE_BOT_FLIPPED)
vec4 botConeIx = intersectFlippedCone(flippedRay, flippedSouth);
- ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 0] = vec2(botConeIx.x, -1.0);
- ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 1] = vec2(botConeIx.y, -1.0);
- ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 2] = vec2(botConeIx.z, -1.0);
- ix.intersections[ELLIPSOID_CONE_BOT_FLIPPED + 3] = vec2(botConeIx.w, -1.0);
+ setIntersection(ix, ELLIPSOID_CONE_BOT_FLIPPED + 0, botConeIx.xy);
+ setIntersection(ix, ELLIPSOID_CONE_BOT_FLIPPED + 1, botConeIx.zw);
#endif
#endif
@@ -635,14 +752,11 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
float north = u_ellipsoidRectangle.w;
#if defined(ELLIPSOID_CONE_TOP_REGULAR)
vec2 topConeIntersect = intersectRegularCone(ray, north);
- ix.intersections[ELLIPSOID_CONE_TOP_REGULAR + 0] = vec2(topConeIntersect.x, -1.0);
- ix.intersections[ELLIPSOID_CONE_TOP_REGULAR + 1] = vec2(topConeIntersect.y, -1.0);
+ setIntersection(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersect);
#elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
vec4 topConeIntersect = intersectFlippedCone(ray, north);
- ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 0] = vec2(topConeIntersect.x, -1.0);
- ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 1] = vec2(topConeIntersect.y, -1.0);
- ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 2] = vec2(topConeIntersect.z, -1.0);
- ix.intersections[ELLIPSOID_CONE_TOP_FLIPPED + 3] = vec2(topConeIntersect.w, -1.0);
+ setIntersection(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersect.xy);
+ setIntersection(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersect.zw);
#endif
#endif
@@ -652,15 +766,12 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
float east = u_ellipsoidRectangle.z; // [-pi,+pi]
#if defined(ELLIPSOID_WEDGE_REGULAR)
vec2 wedgeIntersect = intersectWedge(ray, west, east);
- ix.intersections[ELLIPSOID_WEDGE_REGULAR + 0] = vec2(wedgeIntersect.x, -1.0);
- ix.intersections[ELLIPSOID_WEDGE_REGULAR + 1] = vec2(wedgeIntersect.y, -1.0);
+ setIntersection(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
#elif defined(ELLIPSOID_WEDGE_FLIPPED)
vec2 planeIntersectWest = intersectHalfSpace(ray, west);
vec2 planeIntersectEast = intersectHalfSpace(ray, east);
- ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 0] = vec2(planeIntersectWest.x, -1.0);
- ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 1] = vec2(planeIntersectWest.y, -1.0);
- ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 2] = vec2(planeIntersectEast.x, -1.0);
- ix.intersections[ELLIPSOID_WEDGE_FLIPPED + 3] = vec2(planeIntersectEast.y, -1.0);
+ setIntersection(ix, ELLIPSOID_WEDGE_FLIPPED + 0, planeIntersectWest);
+ setIntersection(ix, ELLIPSOID_WEDGE_FLIPPED + 1, planeIntersectEast);
#endif
#endif
}
@@ -782,8 +893,7 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
return;
}
- ix.intersections[0] = vec2(outerIntersect.x, float(0));
- ix.intersections[1] = vec2(outerIntersect.y, float(1));
+ setIntersection(ix, BOX_INTERSECTION_INDEX, outerIntersect);
#if defined(BOUNDS_0_MIN)
vec3 innerScale = vec3(minRadius, minRadius, 1.0);
@@ -828,91 +938,6 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
}
#endif
-void sortIntersections(inout Intersections ix) {
- // Sort the intersections from min T to max T with bubble sort.
- // Note: If this sorting function changes, some of the intersection test may
- // need to be updated. Search for "bubble sort" to find those areas.
- const int passes = SCENE_INTERSECTION_COUNT * 2 - 1;
- for (int n = passes; n > 0; --n) {
- for (int i = 0; i < passes; ++i) {
- // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
- // loop with non-constant condition, so it has to break early instead
- if (i >= n) { break; }
-
- vec2 intersect0 = ix.intersections[i + 0];
- vec2 intersect1 = ix.intersections[i + 1];
-
- float t0 = intersect0.x;
- float t1 = intersect1.x;
- float b0 = intersect0.y;
- float b1 = intersect1.y;
-
- float tmin = min(t0, t1);
- float tmax = max(t0, t1);
- float bmin = tmin == t0 ? b0 : b1;
- float bmax = tmin == t0 ? b1 : b0;
-
- ix.intersections[i + 0] = vec2(tmin, bmin);
- ix.intersections[i + 1] = vec2(tmax, bmax);
- }
- }
-}
-
-vec2 nextIntersection(inout Intersections ix) {
- vec2 entryExitT = vec2(NO_HIT);
-
- #if (SCENE_INTERSECTION_COUNT == 1)
- if (ix.index == 0) {
- entryExitT.x = ix.intersections[0].x;
- entryExitT.y = ix.intersections[1].x;
- ix.index += 1;
- }
- #else
- int surroundCount = 0;
- bool surroundIsPositive = false;
- const int passCount = SCENE_INTERSECTION_COUNT * 2;
- for (int i = 0; i < passCount; i++)
- {
- vec2 intersect = ix.intersections[i];
- float t = intersect.x;
- bool currShapeIsPositive = intersect.y < 2.0;
- bool enter = mod(intersect.y, 2.0) == 0.0;
-
- surroundCount += enter ? +1 : -1;
- surroundIsPositive = currShapeIsPositive ? enter : surroundIsPositive;
-
- // The loop should be: for (i = ix.index; i < passCount; ++i) {...} but WebGL1 cannot
- // loop with non-constant condition, so it has to continue instead.
- if (i < ix.index) {
- continue;
- }
-
- // entering positive or exiting negative
- if (surroundCount == 1 && surroundIsPositive && enter == currShapeIsPositive) {
- entryExitT.x = t;
- }
-
- // exiting positive or entering negative after being inside positive
- // TODO: Can this be simplified?
- bool exitPositive = !enter && currShapeIsPositive && surroundCount == 0;
- bool enterNegativeFromPositive = enter && !currShapeIsPositive && surroundCount == 2 && surroundIsPositive;
- if (exitPositive || enterNegativeFromPositive) {
- entryExitT.y = t;
-
- // entry and exit have been found, so the loop can stop
- if (exitPositive) {
- ix.index = SCENE_INTERSECTION_COUNT * 2;
- } else {
- ix.index = i + 1;
- }
- break;
- }
- }
- #endif
-
- return entryExitT;
-}
-
#if defined(DEPTH_TEST)
float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directionUv) {
float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenUv));
@@ -941,22 +966,24 @@ vec2 intersectScene(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directi
intersectCylinderShape(ray, ix);f
#endif
- // Exit early if the shape was completely missed
- if (ix.intersections[0].x == NO_HIT) {
+ // Check if the positive shape was completely missed, and if so, exit early.
+ float entryPositiveShapeT = getIntersection(ix, 0);
+ if (entryPositiveShapeT == NO_HIT) {
return vec2(NO_HIT);
}
#if defined(DEPTH_TEST)
float depthT = intersectDepth(fragCoord, screenUv, positionUv, directionUv);
- ix.intersections[DEPTH_INTERSECTION_IDX + 0] = vec2(depthT, NEGATIVE_ENTRY);
- ix.intersections[DEPTH_INTERSECTION_IDX + 1] = vec2(+INF_HIT, NEGATIVE_EXIT);
+ setIntersection(ix, DEPTH_INTERSECTION_INDEX, vec2(depthT, +INF_HIT));
#endif
- sortIntersections(ix);
-
// Find the first intersection interval
- ix.index = 0;
- vec2 entryExitT = nextIntersection(ix);
+ #if (SCENE_INTERSECTION_COUNT > 1)
+ vec2 entryExitT = initializeIntersections(ix);
+ #else
+ float exitPositiveShapeT = getIntersection(ix, 1);
+ vec2 entryExitT = vec2(entryPositiveShapeT, exitPositiveShapeT);
+ #endif
// Intersection is invalid when start and end are behind the ray.
if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
@@ -1070,20 +1097,16 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
float longitude = (atan(geodeticSurfaceNormal.y, geodeticSurfaceNormal.x) + czm_pi) / czm_twoPi; // 5
float latitude = (asin(geodeticSurfaceNormal.z) + czm_piOverTwo) / czm_pi; // 6
- // #if (defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED))
- // {
- // longitude = (longitude - u_ellipsoidWestUv) * u_ellipsoidInverseLongitudeRangeUv;
- // }
- // #endif
- // #if (defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
- // {
- // latitude = (latitude - u_ellipsoidSouthUv) * u_ellipsoidInverseLatitudeRangeUv;
- // }
- // #endif
+ #if (defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED))
+ longitude = (longitude - u_ellipsoidWestUv) * u_ellipsoidInverseLongitudeRangeUv;
+ #endif
+
+ #if (defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+ latitude = (latitude - u_ellipsoidSouthUv) * u_ellipsoidInverseLatitudeRangeUv;
+ #endif
+
#if (defined(ELLIPSOID_INNER))
- {
height *= u_ellipsoidInverseHeightDifferenceUv;
- }
#endif
return vec3(longitude, latitude, height);
@@ -1528,15 +1551,19 @@ void main()
// Check if there's more intersections.
if (currT > endT) {
- vec2 entryExitT = nextIntersection(ix);
- if (entryExitT.x == NO_HIT) {
+ #if (SCENE_INTERSECTION_COUNT == 1)
break;
- } else {
- // Found another intersection. Keep raymarching.
- currT += entryExitT.x;
- endT += entryExitT.y;
- positionUv += entryExitT.x * viewDirUv;
- }
+ #else
+ vec2 entryExitT = nextIntersection(ix);
+ if (entryExitT.x == NO_HIT) {
+ break;
+ } else {
+ // Found another intersection. Keep raymarching.
+ currT += entryExitT.x;
+ endT += entryExitT.y;
+ positionUv += entryExitT.x * viewDirUv;
+ }
+ #endif
}
// Traverse the tree from the current ray position.
From 327dd5afac9cc88268c35c2445c52cfd6e3369dc Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 13 Apr 2022 12:10:41 -0400
Subject: [PATCH 021/679] simplified options for depth intersection
---
Source/Scene/VoxelCylinderShape.js | 133 ++++++++-----
Source/Scene/VoxelEllipsoidShape.js | 4 +-
.../Functions/windowToEyeCoordinates.glsl | 96 +++++----
Source/Shaders/VoxelFS.glsl | 185 ++++++++++--------
4 files changed, 246 insertions(+), 172 deletions(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index 520e6fc76d6..f462c1c4403 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -52,14 +52,6 @@ function VoxelCylinderShape() {
*/
this.shapeTransform = new Matrix4();
- /**
- * Check if the shape is visible. For example, if the shape has zero scale it will be invisible.
- * The update function must be called before accessing this value.
- * @type {Boolean}
- * @readonly
- */
- this.isVisible = false;
-
/**
* @type {Number}
* @private
@@ -95,6 +87,29 @@ function VoxelCylinderShape() {
* @private
*/
this._maximumAngle = VoxelCylinderShape.DefaultMaxBounds.z;
+
+ /**
+ * @type {Object.}
+ * @readonly
+ */
+ this.shaderUniforms = {
+ cylinderInnerRadiusUv: 0.0,
+ cylinderMinAngleUv: 0.0,
+ cylinderInverseAngleUv: 0.0,
+ };
+
+ /**
+ * @type {Object.}
+ * @readonly
+ */
+ this.shaderDefines = {
+ CYLINDER_INTERSECTION_COUNT: undefined,
+ CYLINDER_INNER: undefined,
+ CYLINDER_OUTER: undefined,
+ CYLINDER_INNER_OUTER_EQUAL: undefined,
+ CYLINDER_WEDGE_REGULAR: undefined,
+ CYLINDER_WEDGE_FLIPPED: undefined,
+ };
}
const scratchTestAngles = new Array(6);
@@ -220,51 +235,64 @@ VoxelCylinderShape.prototype.update = function (
Check.typeOf.object("maxBounds", maxBounds);
//>>includeEnd('debug');
- // Don't let the scale be 0, it will screw up a bunch of math
- const scaleEps = CesiumMath.EPSILON8;
const scale = Matrix4.getScale(modelMatrix, scratchScale);
- if (Math.abs(scale.x) < scaleEps) {
- scale.x = CesiumMath.signNotZero(scale.x) * scaleEps;
- }
- if (Math.abs(scale.y) < scaleEps) {
- scale.y = CesiumMath.signNotZero(scale.y) * scaleEps;
- }
- if (Math.abs(scale.z) < scaleEps) {
- scale.z = CesiumMath.signNotZero(scale.z) * scaleEps;
- }
+ const defaultMinBounds = VoxelCylinderShape.DefaultMinBounds;
+ const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds;
- // // If two or more of the scales are 0 the shape will not render.
- // // If the X scale or Y scale is 0 the shape will appear as a square/rectangle.
- // // If the Z scale is 0 the shape will appear as an circle/ellipse.
- // const xIsZero = scale.x === 0.0;
- // const yIsZero = scale.y === 0.0;
- // const zIsZero = scale.z === 0.0;
- // if (xIsZero + yIsZero + zIsZero >= 2) {
- // this.isVisible = false;
- // return;
- // }
-
- this._minimumRadius = minBounds.x; // [0,1]
- this._maximumRadius = maxBounds.x; // [0,1]
- this._minimumHeight = minBounds.y; // [-1,+1]
- this._maximumHeight = maxBounds.y; // [-1,+1]
- this._minimumAngle = CesiumMath.negativePiToPi(minBounds.z); // [-halfPi,+halfPi]
- this._maximumAngle = CesiumMath.negativePiToPi(maxBounds.z); // [-halfPi,+halfPi]
+ // Clamp the radii to the valid range
+ const minRadius = CesiumMath.clamp(
+ minBounds.x,
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
+ const maxRadius = CesiumMath.clamp(
+ maxBounds.x,
+ defaultMinBounds.x,
+ defaultMaxBounds.x
+ );
- const minRadius = this._minimumRadius;
- const maxRadius = this._maximumRadius;
- const minHeight = this._minimumHeight;
- const maxHeight = this._maximumHeight;
- const minAngle = this._minimumAngle;
- const maxAngle = this._maximumAngle;
+ // Clamp the heights to the valid range
+ const minHeight = CesiumMath.clamp(
+ minBounds.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
+ );
+ const maxHeight = CesiumMath.clamp(
+ maxBounds.y,
+ defaultMinBounds.y,
+ defaultMaxBounds.y
+ );
+
+ // Clamp the angles to the valid range
+ const minAngle = CesiumMath.negativePiToPi(minBounds.z);
+ const maxAngle = CesiumMath.negativePiToPi(maxBounds.z);
- // Exit early if the bounds make the shape invisible.
+ const outerExtent = Cartesian3.fromElements(
+ scale.x * maxRadius,
+ scale.y * maxRadius,
+ scale.z * 0.5 * (maxHeight - minHeight)
+ );
+
+ // Exit early if the shape is not visible.
// Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
- if (minRadius > maxRadius || minHeight > maxHeight) {
- this.isVisible = false;
- return;
+ const absEpsilon = CesiumMath.EPSILON10;
+ if (
+ minRadius > maxRadius ||
+ minHeight > maxHeight ||
+ CesiumMath.equalsEpsilon(outerExtent.x, 0.0, undefined, absEpsilon) ||
+ CesiumMath.equalsEpsilon(outerExtent.y, 0.0, undefined, absEpsilon) ||
+ CesiumMath.equalsEpsilon(outerExtent.z, 0.0, undefined, absEpsilon)
+ ) {
+ return false;
}
+ this._minimumRadius = minRadius; // [0,1]
+ this._maximumRadius = maxRadius; // [0,1]
+ this._minimumHeight = minHeight; // [-1,+1]
+ this._maximumHeight = maxHeight; // [-1,+1]
+ this._minimumAngle = minAngle; // [-halfPi,+halfPi]
+ this._maximumAngle = maxAngle; // [-halfPi,+halfPi]
+
this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
this.orientedBoundingBox = getCylinderChunkObb(
@@ -289,7 +317,18 @@ VoxelCylinderShape.prototype.update = function (
this.boundingSphere
);
- this.isVisible = true;
+ const shaderUniforms = this.shaderUniforms;
+ const shaderDefines = this.shaderDefines;
+
+ // To keep things simple, clear the defines every time
+ shaderDefines["CYLINDER_INTERSECTION_COUNT"] = undefined;
+ shaderDefines["CYLINDER_INNER"] = undefined;
+ shaderDefines["CYLINDER_OUTER"] = undefined;
+ shaderDefines["CYLINDER_INNER_OUTER_EQUAL"] = undefined;
+ shaderDefines["CYLINDER_WEDGE_REGULAR"] = undefined;
+ shaderDefines["CYLINDER_WEDGE_FLIPPED"] = undefined;
+
+ return true;
};
/**
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index c343523864e..e08d31ac555 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -114,6 +114,7 @@ function VoxelEllipsoidShape() {
* @readonly
*/
this.shaderDefines = {
+ ELLIPSOID_INTERSECTION_COUNT: undefined,
ELLIPSOID_WEDGE_REGULAR: undefined,
ELLIPSOID_WEDGE_FLIPPED: undefined,
ELLIPSOID_CONE_BOTTOM_REGULAR: undefined,
@@ -122,7 +123,6 @@ function VoxelEllipsoidShape() {
ELLIPSOID_CONE_TOP_FLIPPED: undefined,
ELLIPSOID_OUTER: undefined,
ELLIPSOID_INNER: undefined,
- ELLIPSOID_INTERSECTION_COUNT: undefined,
};
}
@@ -245,6 +245,7 @@ VoxelEllipsoidShape.prototype.update = function (
const shaderDefines = this.shaderDefines;
// To keep things simple, clear the defines every time
+ shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = undefined;
shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = undefined;
shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = undefined;
shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = undefined;
@@ -253,7 +254,6 @@ VoxelEllipsoidShape.prototype.update = function (
shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = undefined;
shaderDefines["ELLIPSOID_OUTER"] = undefined;
shaderDefines["ELLIPSOID_INNER"] = undefined;
- shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = undefined;
shaderUniforms.ellipsoidRectangle = Cartesian4.fromElements(
west,
diff --git a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl
index b049c51f4ac..fb902ad40fe 100644
--- a/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl
+++ b/Source/Shaders/Builtin/Functions/windowToEyeCoordinates.glsl
@@ -1,38 +1,13 @@
-/**
- * Transforms a position from window to eye coordinates.
- * The transform from window to normalized device coordinates is done using components
- * of (@link czm_viewport} and {@link czm_viewportTransformation} instead of calculating
- * the inverse of czm_viewportTransformation
. The transformation from
- * normalized device coordinates to clip coordinates is done using fragmentCoordinate.w
,
- * which is expected to be the scalar used in the perspective divide. The transformation
- * from clip to eye coordinates is done using {@link czm_inverseProjection}.
- *
- * @name czm_windowToEyeCoordinates
- * @glslFunction
- *
- * @param {vec4} fragmentCoordinate The position in window coordinates to transform.
- *
- * @returns {vec4} The transformed position in eye coordinates.
- *
- * @see czm_modelToWindowCoordinates
- * @see czm_eyeToWindowCoordinates
- * @see czm_inverseProjection
- * @see czm_viewport
- * @see czm_viewportTransformation
- *
- * @example
- * vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord);
- */
-vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate)
+vec4 czm_screenToEyeCoordinates(vec4 screenCoordinate)
{
// Reconstruct NDC coordinates
- float x = 2.0 * (fragmentCoordinate.x - czm_viewport.x) / czm_viewport.z - 1.0;
- float y = 2.0 * (fragmentCoordinate.y - czm_viewport.y) / czm_viewport.w - 1.0;
- float z = (fragmentCoordinate.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2];
+ float x = 2.0 * screenCoordinate.x - 1.0;
+ float y = 2.0 * screenCoordinate.y - 1.0;
+ float z = (screenCoordinate.z - czm_viewportTransformation[3][2]) / czm_viewportTransformation[2][2];
vec4 q = vec4(x, y, z, 1.0);
// Reverse the perspective division to obtain clip coordinates.
- q /= fragmentCoordinate.w;
+ q /= screenCoordinate.w;
// Reverse the projection transformation to obtain eye coordinates.
if (!(czm_inverseProjection == mat4(0.0))) // IE and Edge sometimes do something weird with != between mat4s
@@ -59,16 +34,20 @@ vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate)
}
/**
- * Transforms a position given as window x/y and a depth or a log depth from window to eye coordinates.
- * This function produces more accurate results for window positions with log depth than
- * conventionally unpacking the log depth using czm_reverseLogDepth and using the standard version
- * of czm_windowToEyeCoordinates.
+ * Transforms a position from window to eye coordinates.
+ * The transform from window to normalized device coordinates is done using components
+ * of (@link czm_viewport} and {@link czm_viewportTransformation} instead of calculating
+ * the inverse of czm_viewportTransformation
. The transformation from
+ * normalized device coordinates to clip coordinates is done using fragmentCoordinate.w
,
+ * which is expected to be the scalar used in the perspective divide. The transformation
+ * from clip to eye coordinates is done using {@link czm_inverseProjection}.
*
* @name czm_windowToEyeCoordinates
* @glslFunction
*
- * @param {vec2} fragmentCoordinateXY The XY position in window coordinates to transform.
- * @param {float} depthOrLogDepth A depth or log depth for the fragment.
+ * @param {vec4} fragmentCoordinate The position in window coordinates to transform.
+ *
+ * @returns {vec4} The transformed position in eye coordinates.
*
* @see czm_modelToWindowCoordinates
* @see czm_eyeToWindowCoordinates
@@ -76,9 +55,16 @@ vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate)
* @see czm_viewport
* @see czm_viewportTransformation
*
- * @returns {vec4} The transformed position in eye coordinates.
+ * @example
+ * vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord);
*/
-vec4 czm_windowToEyeCoordinates(vec2 fragmentCoordinateXY, float depthOrLogDepth)
+vec4 czm_windowToEyeCoordinates(vec4 fragmentCoordinate)
+{
+ vec2 screenCoordXY = (fragmentCoordinate.xy - czm_viewport.xy) / czm_viewport.zw;
+ return czm_screenToEyeCoordinates(vec4(screenCoordXY, fragmentCoordinate.zw));
+}
+
+vec4 czm_screenToEyeCoordinates(vec2 screenCoordinateXY, float depthOrLogDepth)
{
// See reverseLogDepth.glsl. This is separate to re-use the pow.
#if defined(LOG_DEPTH) || defined(LOG_DEPTH_READ_ONLY)
@@ -87,13 +73,39 @@ vec4 czm_windowToEyeCoordinates(vec2 fragmentCoordinateXY, float depthOrLogDepth
float log2Depth = depthOrLogDepth * czm_log2FarDepthFromNearPlusOne;
float depthFromNear = pow(2.0, log2Depth) - 1.0;
float depthFromCamera = depthFromNear + near;
- vec4 windowCoord = vec4(fragmentCoordinateXY, far * (1.0 - near / depthFromCamera) / (far - near), 1.0);
- vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);
+ vec4 screenCoord = vec4(screenCoordinateXY, far * (1.0 - near / depthFromCamera) / (far - near), 1.0);
+ vec4 eyeCoordinate = czm_screenToEyeCoordinates(screenCoord);
eyeCoordinate.w = 1.0 / depthFromCamera; // Better precision
return eyeCoordinate;
#else
- vec4 windowCoord = vec4(fragmentCoordinateXY, depthOrLogDepth, 1.0);
- vec4 eyeCoordinate = czm_windowToEyeCoordinates(windowCoord);
+ vec4 screenCoord = vec4(screenCoordinateXY, depthOrLogDepth, 1.0);
+ vec4 eyeCoordinate = czm_screenToEyeCoordinates(screenCoord);
#endif
return eyeCoordinate;
}
+
+/**
+ * Transforms a position given as window x/y and a depth or a log depth from window to eye coordinates.
+ * This function produces more accurate results for window positions with log depth than
+ * conventionally unpacking the log depth using czm_reverseLogDepth and using the standard version
+ * of czm_windowToEyeCoordinates.
+ *
+ * @name czm_windowToEyeCoordinates
+ * @glslFunction
+ *
+ * @param {vec2} fragmentCoordinateXY The XY position in window coordinates to transform.
+ * @param {float} depthOrLogDepth A depth or log depth for the fragment.
+ *
+ * @see czm_modelToWindowCoordinates
+ * @see czm_eyeToWindowCoordinates
+ * @see czm_inverseProjection
+ * @see czm_viewport
+ * @see czm_viewportTransformation
+ *
+ * @returns {vec4} The transformed position in eye coordinates.
+ */
+vec4 czm_windowToEyeCoordinates(vec2 fragmentCoordinateXY, float depthOrLogDepth)
+{
+ vec2 screenCoordXY = (fragmentCoordinateXY.xy - czm_viewport.xy) / czm_viewport.zw;
+ return czm_screenToEyeCoordinates(screenCoordXY, depthOrLogDepth);
+}
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index db72be16bb7..268ba6580a7 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -250,7 +250,9 @@ uniform float u_stepSize;
#if defined(SHAPE_CYLINDER)
/* Cylinder defines:
+ #define CYLINDER_OUTER ### // outer cylinder
#define CYLINDER_INNER ### // when there's an inner cylinder
+ #define CYLINDER_INNER_OUTER_EQUAL ### // when inner and outer cylinder have the same radius
#define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
#define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
#define CYLINDER_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
@@ -258,7 +260,7 @@ uniform float u_stepSize;
// Cylinder uniforms
#if defined(CYLINDER_INNER)
- uniform float u_something;
+ uniform float u_cylinderInnerRadiusUv;
#endif
#if defined(CYLINDER_WEDGE_REGULAR) || defined(CYLINDER_WEDGE_FLIPPED)
uniform float u_cylinderMinAngle;
@@ -368,16 +370,23 @@ struct Intersections {
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
#if (SCENE_INTERSECTION_COUNT > 1)
- #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) ix.intersections[index * 2 + 0] = vec2(entryExit.x, float(index > 0) * 2.0 + 0.0); ix.intersections[index * 2 + 1] = vec2(entryExit.y, float(index > 0) * 2.0 + 1.0)
+ #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)].x
#else
- #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) ix.intersections[0] = entryExit.x; ix.intersections[1] = entryExit.y
+ #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)]
#endif
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
#if (SCENE_INTERSECTION_COUNT > 1)
- #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) ix.intersections[index].x
+ #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = vec2((t), float(!positive) * 2.0 + float(!enter))
#else
- #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) ix.intersections[index]
+ #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = (t)
+#endif
+
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#if (SCENE_INTERSECTION_COUNT > 1)
+ #define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = vec2((entryExit).x, float((index) > 0) * 2.0 + 0.0); (ix).intersections[(index) * 2 + 1] = vec2((entryExit).y, float((index) > 0) * 2.0 + 1.0)
+#else
+ #define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = (entryExit).x; (ix).intersections[(index) * 2 + 1] = (entryExit).y
#endif
#if (SCENE_INTERSECTION_COUNT > 1)
@@ -528,7 +537,7 @@ void intersectBoxShape(Ray ray, out Intersections ix)
vec2 entryExit = intersectUnitCube(ray);
#endif
- setIntersection(ix, BOX_INTERSECTION_INDEX, entryExit);
+ setIntersectionPair(ix, BOX_INTERSECTION_INDEX, entryExit);
}
#endif
@@ -715,7 +724,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
// Outer ellipsoid
vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- setIntersection(ix, ELLIPSOID_OUTER, outerIntersect);
+ setIntersectionPair(ix, ELLIPSOID_OUTER, outerIntersect);
// Exit early if the outer ellipsoid was missed.
if (outerIntersect.x == NO_HIT) {
@@ -726,7 +735,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
#if defined(ELLIPSOID_INNER)
Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- setIntersection(ix, ELLIPSOID_INNER, innerIntersect);
+ setIntersectionPair(ix, ELLIPSOID_INNER, innerIntersect);
#endif
// Bottom cone
@@ -739,11 +748,11 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
#if defined(ELLIPSOID_CONE_BOT_REGULAR)
vec2 botConeIx = intersectRegularCone(flippedRay, flippedSouth);
- setIntersection(ix, ELLIPSOID_CONE_BOT_REGULAR, botConeIx);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOT_REGULAR, botConeIx);
#elif defined(ELLIPSOID_CONE_BOT_FLIPPED)
vec4 botConeIx = intersectFlippedCone(flippedRay, flippedSouth);
- setIntersection(ix, ELLIPSOID_CONE_BOT_FLIPPED + 0, botConeIx.xy);
- setIntersection(ix, ELLIPSOID_CONE_BOT_FLIPPED + 1, botConeIx.zw);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOT_FLIPPED + 0, botConeIx.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOT_FLIPPED + 1, botConeIx.zw);
#endif
#endif
@@ -752,11 +761,11 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
float north = u_ellipsoidRectangle.w;
#if defined(ELLIPSOID_CONE_TOP_REGULAR)
vec2 topConeIntersect = intersectRegularCone(ray, north);
- setIntersection(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersect);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersect);
#elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
vec4 topConeIntersect = intersectFlippedCone(ray, north);
- setIntersection(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersect.xy);
- setIntersection(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersect.zw);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersect.zw);
#endif
#endif
@@ -766,12 +775,12 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
float east = u_ellipsoidRectangle.z; // [-pi,+pi]
#if defined(ELLIPSOID_WEDGE_REGULAR)
vec2 wedgeIntersect = intersectWedge(ray, west, east);
- setIntersection(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
#elif defined(ELLIPSOID_WEDGE_FLIPPED)
vec2 planeIntersectWest = intersectHalfSpace(ray, west);
vec2 planeIntersectEast = intersectHalfSpace(ray, east);
- setIntersection(ix, ELLIPSOID_WEDGE_FLIPPED + 0, planeIntersectWest);
- setIntersection(ix, ELLIPSOID_WEDGE_FLIPPED + 1, planeIntersectEast);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 0, planeIntersectWest);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 1, planeIntersectEast);
#endif
#endif
}
@@ -862,6 +871,11 @@ vec2 intersectInfiniteUnitCylinder(Ray ray)
#if defined(SHAPE_CYLINDER)
void intersectCylinderShape(Ray ray, inout Intersections ix)
{
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir *= 2.0;
+
#if !defined(BOUNDS)
return intersectUnitCylinder(ray);
#else
@@ -893,78 +907,90 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
return;
}
- setIntersection(ix, BOX_INTERSECTION_INDEX, outerIntersect);
+ setIntersectionPair(ix, CYLINDER_OUTER, outerIntersect);
- #if defined(BOUNDS_0_MIN)
- vec3 innerScale = vec3(minRadius, minRadius, 1.0);
- Ray innerRay = Ray((ray.pos - pos) / innerScale, ray.dir / innerScale);
- vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
-
- // TODO: use define instead of branch
- if (minRadius != maxRadius) {
- intersections[2] = vec2(float(2), innerIntersect.x);
- intersections[3] = vec2(float(3), innerIntersect.y);
- } else {
- // When the cylinder is perfectly thin it's necessary to sandwich the
- // inner cylinder intersection inside the outer cylinder intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the cylinder to be invisible because it will think the ray
- // is still inside the inner (negative) cylinder after exiting the
- // outer (positive) cylinder.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If sortIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
-
- intersections[0] = vec2(float(0), outerIntersect.x);
- intersections[1] = vec2(float(2), innerIntersect.x);
- intersections[2] = vec2(float(3), innerIntersect.y);
- intersections[3] = vec2(float(1), outerIntersect.y);
- }
+ #if defined(CYLINDER_INNER_OUTER_EQUAL)
+
+ #else
+
#endif
- #if defined(BOUNDS_2_MIN) || defined(BOUNDS_2_MAX)
+ vec3 innerScale = vec3(minRadius, minRadius, 1.0);
+ Ray innerRay = Ray((ray.pos - pos) / innerScale, ray.dir / innerScale);
+ vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
+
+ // TODO: use define instead of branch
+ if (minRadius != maxRadius) {
+ intersections[2] = vec2(float(2), innerIntersect.x);
+ intersections[3] = vec2(float(3), innerIntersect.y);
+ } else {
+ // When the cylinder is perfectly thin it's necessary to sandwich the
+ // inner cylinder intersection inside the outer cylinder intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the cylinder to be invisible because it will think the ray
+ // is still inside the inner (negative) cylinder after exiting the
+ // outer (positive) cylinder.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If sortIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ }
+
+ #if defined(CYLINDER_WEDGE_REGULAR)
vec2 wedgeIntersect = intersectWedge(ray, minAngle, maxAngle);
- intersections[BOUNDS_2_MIN_MAX_IDX * 2 + 0] = vec2(float(BOUNDS_2_MIN_MAX_IDX * 2 + 0), wedgeIntersect.x);
- intersections[BOUNDS_2_MIN_MAX_IDX * 2 + 1] = vec2(float(BOUNDS_2_MIN_MAX_IDX * 2 + 1), wedgeIntersect.y);
- #endif
+ setIntersectionPair(ix, CYLINDER_WEDGE_REGULAR, wedgeIntersect);
+ #elif defined(CYLINDER_WEDGE_FLIPPED)
+ vec2 planeIntersectMinAngle = intersectHalfSpace(ray, minAngle);
+ vec2 planeIntersectMaxAngle = intersectHalfSpace(ray, maxAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_FLIPPED + 0, planeIntersectMinAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_FLIPPED + 1, planeIntersectMaxAngle);
+ #endif
#endif
}
#endif
+void intersectShape(Ray ray, out Intersections ix) {
+ #if defined(SHAPE_BOX)
+ intersectBoxShape(ray, ix);
+ #elif defined(SHAPE_ELLIPSOID)
+ intersectEllipsoidShape(ray, ix);
+ #elif defined(SHAPE_CYLINDER)
+ intersectCylinderShape(ray, ix);
+ #endif
+}
+
#if defined(DEPTH_TEST)
-float intersectDepth(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directionUv) {
- float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenUv));
+float intersectDepth(vec2 screenCoord, Ray ray) {
+ float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenCoord));
if (logDepthOrDepth != 0.0) {
// Calculate how far the ray must travel before it hits the depth buffer.
- vec4 eyeCoordinateDepth = czm_windowToEyeCoordinates(fragCoord, logDepthOrDepth);
+ vec4 eyeCoordinateDepth = czm_screenToEyeCoordinates(screenCoord, logDepthOrDepth);
eyeCoordinateDepth /= eyeCoordinateDepth.w;
vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
- return dot(directionUv, depthPositionUv - positionUv);
+ return dot(depthPositionUv - ray.pos, ray.dir);
} else {
// There's no depth at this position so set it to some really far value.
- return czm_infinity;
+ return +INF_HIT;
}
}
#endif
-vec2 intersectScene(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directionUv, out Intersections ix) {
+vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Intersections ix) {
Ray ray = Ray(positionUv, directionUv);
// Do a ray-shape intersection to find the exact starting and ending points.
- #if defined(SHAPE_BOX)
- intersectBoxShape(ray, ix);
- #elif defined(SHAPE_ELLIPSOID)
- intersectEllipsoidShape(ray, ix);
- #elif defined(SHAPE_CYLINDER)
- intersectCylinderShape(ray, ix);f
- #endif
+ intersectShape(ray, ix);
// Check if the positive shape was completely missed, and if so, exit early.
float entryPositiveShapeT = getIntersection(ix, 0);
@@ -972,9 +998,10 @@ vec2 intersectScene(vec2 fragCoord, vec2 screenUv, vec3 positionUv, vec3 directi
return vec2(NO_HIT);
}
+ // Intersect depth texture
#if defined(DEPTH_TEST)
- float depthT = intersectDepth(fragCoord, screenUv, positionUv, directionUv);
- setIntersection(ix, DEPTH_INTERSECTION_INDEX, vec2(depthT, +INF_HIT));
+ float depthT = intersectDepth(screenCoord, ray);
+ setIntersectionPair(ix, DEPTH_INTERSECTION_INDEX, vec2(depthT, +INF_HIT));
#endif
// Find the first intersection interval
@@ -1119,7 +1146,6 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
float radius = length(positionLocal.xy); // [0,1]
float height = positionUv.z; // [0,1]
float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
- vec3 positionShape = vec3(radius, height, angle);
#if defined(BOUNDS)
positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
@@ -1128,20 +1154,18 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
// and set the shape space position to 1 (front) or 0 (back) accordingly.
#endif
- return positionShape;
+ return vec3(radius, height, angle);
}
#endif
vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
#if defined(SHAPE_BOX)
- vec3 positionShape = transformFromUvToBoxSpace(positionUv);
+ return transformFromUvToBoxSpace(positionUv);
#elif defined(SHAPE_ELLIPSOID)
- vec3 positionShape = transformFromUvToEllipsoidSpace(positionUv);
+ return transformFromUvToEllipsoidSpace(positionUv);
#elif defined(SHAPE_CYLINDER)
- vec3 positionShape = transformFromUvToCylinderSpace(positionUv);
+ return transformFromUvToCylinderSpace(positionUv);
#endif
-
- return positionShape;
}
// --------------------------------------------------------
@@ -1437,15 +1461,14 @@ void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpac
void main()
{
vec4 fragCoord = gl_FragCoord;
- vec2 screenUv = (fragCoord.xy - czm_viewport.xy) / czm_viewport.zw;
- vec4 eyeCoordinate = czm_windowToEyeCoordinates(fragCoord);
- vec3 eyeDirection = normalize(eyeCoordinate.xyz);
+ vec2 screenCoord = (fragCoord.xy - czm_viewport.xy) / czm_viewport.zw; // [0,1]
+ vec3 eyeDirection = normalize(czm_windowToEyeCoordinates(fragCoord).xyz);
vec3 viewDirWorld = normalize(czm_inverseViewRotation * eyeDirection); // normalize again just in case
vec3 viewDirUv = normalize(u_transformDirectionViewToLocal * eyeDirection); // normalize again just in case
vec3 viewPosUv = u_cameraPositionUv;
Intersections ix;
- vec2 entryExitT = intersectScene(fragCoord.xy, screenUv, viewPosUv, viewDirUv, ix);
+ vec2 entryExitT = intersectScene(screenCoord, viewPosUv, viewDirUv, ix);
// Exit early if the scene was completely missed.
if (entryExitT.x == NO_HIT) {
@@ -1477,7 +1500,7 @@ void main()
float stepT = u_stepSize * levelStepMult;
#if defined(JITTER)
- float noise = hash(screenUv); // [0,1]
+ float noise = hash(screenCoord); // [0,1]
currT += noise * stepT;
positionUv += noise * stepT * viewDirUv;
#endif
From ea5e344cb8cf3aeb2591cd461b333ea403824f56 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 13 Apr 2022 16:32:40 -0400
Subject: [PATCH 022/679] fixed some parts of the cylinder shape
---
Source/Scene/VoxelBoxShape.js | 17 +-
Source/Scene/VoxelCylinderShape.js | 417 ++++++++++++++++++++--------
Source/Scene/VoxelEllipsoidShape.js | 18 +-
Source/Shaders/VoxelFS.glsl | 225 +++++++--------
4 files changed, 428 insertions(+), 249 deletions(-)
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index 2438159a8d3..cbd62a05358 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -87,8 +87,8 @@ function VoxelBoxShape() {
* @readonly
*/
this.shaderDefines = {
- BOX_INTERSECTION_COUNT: 1, // never changes
- BOX_INTERSECTION_INDEX: 0, // never changes
+ BOX_INTERSECTION_COUNT: undefined,
+ BOX_INTERSECTION_INDEX: undefined,
BOX_BOUNDED: undefined,
BOX_XY_PLANE: undefined,
BOX_XZ_PLANE: undefined,
@@ -204,10 +204,15 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
const shaderDefines = this.shaderDefines;
// To keep things simple, clear the defines every time
- shaderDefines["BOX_BOUNDED"] = undefined;
- shaderDefines["BOX_XY_PLANE"] = undefined;
- shaderDefines["BOX_XZ_PLANE"] = undefined;
- shaderDefines["BOX_YZ_PLANE"] = undefined;
+ for (const key in shaderDefines) {
+ if (shaderDefines.hasOwnProperty(key)) {
+ shaderDefines[key] = undefined;
+ }
+ }
+
+ // Never changes
+ shaderDefines["BOX_INTERSECTION_COUNT"] = 1;
+ shaderDefines["BOX_INTERSECTION_INDEX"] = 0;
if (
minBounds.x !== defaultMinBounds.x ||
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index f462c1c4403..c6d9d7833c0 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -93,9 +93,18 @@ function VoxelCylinderShape() {
* @readonly
*/
this.shaderUniforms = {
+ cylinderScaleUvToBounds: new Cartesian3(),
+ cylinderTranslateUvToBounds: new Cartesian3(),
+ cylinderScaleUvToInnerBounds: new Cartesian3(),
+ cylinderTranslateUvToInnerBounds: new Cartesian3(),
cylinderInnerRadiusUv: 0.0,
+ cylinderInverseRadiusRangeUv: 0.0,
+ cylinderMinHeightUv: 0.0,
+ cylinderInverseHeightRangeUv: 0.0,
+ cylinderMinAngle: 0.0,
+ cylinderMaxAngle: 0.0,
cylinderMinAngleUv: 0.0,
- cylinderInverseAngleUv: 0.0,
+ cylinderInverseAngleRangeUv: 0.0,
};
/**
@@ -104,118 +113,29 @@ function VoxelCylinderShape() {
*/
this.shaderDefines = {
CYLINDER_INTERSECTION_COUNT: undefined,
+ CYLINDER_OUTER_INDEX: undefined,
+ CYLINDER_OUTER_NON_DEFAULT: undefined,
CYLINDER_INNER: undefined,
- CYLINDER_OUTER: undefined,
CYLINDER_INNER_OUTER_EQUAL: undefined,
+ CYLINDER_INNER_INDEX: undefined,
+ CYLINDER_HEIGHT_NON_DEFAULT: undefined,
+ CYLINDER_HEIGHT_ZERO: undefined,
+ CYLINDER_WEDGE_INDEX: undefined,
CYLINDER_WEDGE_REGULAR: undefined,
CYLINDER_WEDGE_FLIPPED: undefined,
};
}
-const scratchTestAngles = new Array(6);
-
-// Preallocated arrays for all of the possible test angle counts
-const scratchPositions = [
- new Array(),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
-];
-
const scratchScale = new Cartesian3();
+const scratchBoundsTranslation = new Cartesian3();
+const scratchBoundsScale = new Cartesian3();
+const scratchTransformLocalToBounds = new Matrix4();
+const scratchTransformUvToBounds = new Matrix4();
+const transformUvToLocal = Matrix4.fromRotationTranslation(
+ Matrix3.fromUniformScale(2.0, new Matrix3()),
+ new Cartesian3(-1.0, -1.0, -1.0),
+ new Matrix4()
+);
/**
* Update the shape's state.
@@ -236,31 +156,35 @@ VoxelCylinderShape.prototype.update = function (
//>>includeEnd('debug');
const scale = Matrix4.getScale(modelMatrix, scratchScale);
- const defaultMinBounds = VoxelCylinderShape.DefaultMinBounds;
- const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds;
+ const defaultMinRadius = VoxelCylinderShape.DefaultMinBounds.x;
+ const defaultMaxRadius = VoxelCylinderShape.DefaultMaxBounds.x;
+ const defaultMinHeight = VoxelCylinderShape.DefaultMinBounds.y;
+ const defaultMaxHeight = VoxelCylinderShape.DefaultMaxBounds.y;
+ const defaultMinAngle = VoxelCylinderShape.DefaultMinBounds.z;
+ const defaultMaxAngle = VoxelCylinderShape.DefaultMaxBounds.z;
// Clamp the radii to the valid range
const minRadius = CesiumMath.clamp(
minBounds.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
+ defaultMinRadius,
+ defaultMaxRadius
);
const maxRadius = CesiumMath.clamp(
maxBounds.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
+ defaultMinRadius,
+ defaultMaxRadius
);
// Clamp the heights to the valid range
const minHeight = CesiumMath.clamp(
minBounds.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
+ defaultMinHeight,
+ defaultMaxHeight
);
const maxHeight = CesiumMath.clamp(
maxBounds.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
+ defaultMinHeight,
+ defaultMaxHeight
);
// Clamp the angles to the valid range
@@ -277,6 +201,7 @@ VoxelCylinderShape.prototype.update = function (
// Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
const absEpsilon = CesiumMath.EPSILON10;
if (
+ maxRadius === 0.0 ||
minRadius > maxRadius ||
minHeight > maxHeight ||
CesiumMath.equalsEpsilon(outerExtent.x, 0.0, undefined, absEpsilon) ||
@@ -317,16 +242,163 @@ VoxelCylinderShape.prototype.update = function (
this.boundingSphere
);
+ const isDefaultOuterCylinder = maxRadius === defaultMaxRadius;
+ const hasInnerCylinder = minRadius > defaultMinRadius;
+ const isDefaultHeight =
+ minHeight === defaultMinHeight && maxHeight === defaultMaxHeight;
+
+ const angleWidth =
+ maxAngle - minAngle + (maxAngle < minAngle) * CesiumMath.TWO_PI;
+ const hasWedgeRegular =
+ angleWidth >= CesiumMath.PI &&
+ CesiumMath.lessThan(angleWidth, CesiumMath.TWO_PI, absEpsilon);
+ const hasWedgeFlipped = angleWidth < CesiumMath.PI;
+ const hasWedge = hasWedgeRegular || hasWedgeFlipped;
+
const shaderUniforms = this.shaderUniforms;
const shaderDefines = this.shaderDefines;
// To keep things simple, clear the defines every time
- shaderDefines["CYLINDER_INTERSECTION_COUNT"] = undefined;
- shaderDefines["CYLINDER_INNER"] = undefined;
- shaderDefines["CYLINDER_OUTER"] = undefined;
- shaderDefines["CYLINDER_INNER_OUTER_EQUAL"] = undefined;
- shaderDefines["CYLINDER_WEDGE_REGULAR"] = undefined;
- shaderDefines["CYLINDER_WEDGE_FLIPPED"] = undefined;
+ for (const key in shaderDefines) {
+ if (shaderDefines.hasOwnProperty(key)) {
+ shaderDefines[key] = undefined;
+ }
+ }
+
+ // Keep track of how many intersections there are going to be.
+ let intersectionCount = 0;
+
+ // Intersects an outer cylinder.
+ shaderDefines["CYLINDER_OUTER_INDEX"] = intersectionCount;
+ intersectionCount += 1;
+
+ if (!isDefaultOuterCylinder || hasInnerCylinder || !isDefaultHeight) {
+ shaderDefines["CYLINDER_OUTER_NON_DEFAULT"] = true;
+ const boundsScaleLocalToBounds = Cartesian3.fromElements(
+ 1.0 / maxRadius,
+ 1.0 / maxRadius,
+ 1.0 / (maxHeight === minHeight ? 1.0 : 0.5 * (maxHeight - minHeight)),
+ scratchBoundsScale
+ );
+
+ // -inverse(scale) * translation // affine inverse
+ // -inverse(scale) * 0.5 * (minHeight + maxHeight)
+ const boundsTranslateLocalToBounds = Cartesian3.fromElements(
+ 0.0,
+ 0.0,
+ -boundsScaleLocalToBounds.z * 0.5 * (minHeight + maxHeight),
+ scratchBoundsTranslation
+ );
+
+ const transformLocalToBounds = Matrix4.fromRotationTranslation(
+ Matrix3.fromScale(boundsScaleLocalToBounds),
+ boundsTranslateLocalToBounds,
+ scratchTransformLocalToBounds
+ );
+ const transformUvToBounds = Matrix4.multiplyTransformation(
+ transformLocalToBounds,
+ transformUvToLocal,
+ scratchTransformUvToBounds
+ );
+ shaderUniforms.cylinderScaleUvToBounds = Matrix4.getScale(
+ transformUvToBounds,
+ shaderUniforms.cylinderScaleUvToBounds
+ );
+ shaderUniforms.cylinderTranslateUvToBounds = Matrix4.getTranslation(
+ transformUvToBounds,
+ shaderUniforms.cylinderTranslateUvToBounds
+ );
+ }
+
+ // Intersects an inner cylinder for the min height.
+ if (hasInnerCylinder) {
+ shaderDefines["CYLINDER_INNER"] = true;
+
+ if (minRadius === maxRadius) {
+ shaderDefines["CYLINDER_INNER_OUTER_EQUAL"] = true;
+ } else {
+ shaderDefines["CYLINDER_INNER_INDEX"] = intersectionCount;
+ intersectionCount += 1;
+ }
+ }
+
+ if (!isDefaultOuterCylinder || hasInnerCylinder || !isDefaultHeight) {
+ const boundsScaleLocalToInnerBounds = Cartesian3.fromElements(
+ 1.0 / minRadius,
+ 1.0 / minRadius,
+ 1.0 / (maxHeight === minHeight ? 1.0 : 0.5 * (maxHeight - minHeight)),
+ scratchBoundsScale
+ );
+
+ // -inverse(scale) * translation // affine inverse
+ // -inverse(scale) * 0.5 * (minHeight + maxHeight)
+ const boundsTranslateLocalToInnerBounds = Cartesian3.fromElements(
+ 0.0,
+ 0.0,
+ -boundsScaleLocalToInnerBounds.z * 0.5 * (minHeight + maxHeight),
+ scratchBoundsTranslation
+ );
+
+ const transformLocalToBounds = Matrix4.fromRotationTranslation(
+ Matrix3.fromScale(boundsScaleLocalToInnerBounds),
+ boundsTranslateLocalToInnerBounds,
+ scratchTransformLocalToBounds
+ );
+ const transformUvToBounds = Matrix4.multiplyTransformation(
+ transformLocalToBounds,
+ transformUvToLocal,
+ scratchTransformUvToBounds
+ );
+ shaderUniforms.cylinderScaleUvToInnerBounds = Matrix4.getScale(
+ transformUvToBounds,
+ shaderUniforms.cylinderScaleUvToInnerBounds
+ );
+ shaderUniforms.cylinderTranslateUvToInnerBounds = Matrix4.getTranslation(
+ transformUvToBounds,
+ shaderUniforms.cylinderTranslateUvToInnerBounds
+ );
+
+ shaderUniforms.cylinderInnerRadiusUv = minRadius / defaultMaxRadius;
+ shaderUniforms.cylinderInverseRadiusRangeUv =
+ 1.0 / (maxRadius / defaultMaxRadius - minRadius / defaultMaxRadius);
+ }
+
+ if (!isDefaultHeight) {
+ shaderUniforms.cylinderMinHeightUv = minHeight * 0.5 + 0.5;
+ shaderUniforms.cylinderInverseHeightRangeUv = 2.0 / (maxHeight - minHeight);
+ shaderDefines["CYLINDER_HEIGHT_NON_DEFAULT"] = true;
+
+ if (minHeight === maxHeight) {
+ shaderDefines["CYLINDER_HEIGHT_ZERO"] = true;
+ }
+ }
+
+ if (hasWedge) {
+ shaderDefines["CYLINDER_WEDGE"] = true;
+ shaderDefines["CYLINDER_WEDGE_INDEX"] = intersectionCount;
+ shaderUniforms.minAngle = minAngle;
+ shaderUniforms.maxAngle = maxAngle;
+
+ const minAngleUv =
+ (minAngle - defaultMinAngle) / (defaultMaxAngle - defaultMinAngle);
+ const maxAngleUv =
+ (maxAngle - defaultMinAngle) / (defaultMaxAngle - defaultMinAngle);
+
+ shaderUniforms.cylinderMinAngleUv = minAngleUv;
+ shaderUniforms.cylinderInverseAngleRangeUv =
+ 1.0 / (maxAngleUv - minAngleUv);
+
+ if (hasWedgeRegular) {
+ // Intersects a wedge for the min and max longitude.
+ shaderDefines["CYLINDER_WEDGE_REGULAR"] = true;
+ intersectionCount += 1;
+ } else if (hasWedgeFlipped) {
+ shaderDefines["CYLINDER_WEDGE_FLIPPED"] = true;
+ intersectionCount += 2;
+ }
+ }
+
+ shaderDefines["CYLINDER_INTERSECTION_COUNT"] = intersectionCount;
return true;
};
@@ -494,6 +566,109 @@ VoxelCylinderShape.DefaultMinBounds = new Cartesian3(0.0, -1.0, -CesiumMath.PI);
*/
VoxelCylinderShape.DefaultMaxBounds = new Cartesian3(1.0, +1.0, +CesiumMath.PI);
+const scratchTestAngles = new Array(6);
+
+// Preallocated arrays for all of the possible test angle counts
+const scratchPositions = [
+ new Array(),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+ new Array(
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3(),
+ new Cartesian3()
+ ),
+];
+
/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
*
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index e08d31ac555..edd2a84d11e 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -85,7 +85,7 @@ function VoxelEllipsoidShape() {
this._translation = new Cartesian3();
/**
- * @type {Matrix3
+ * @type {Matrix3}
*/
this._rotation = new Matrix3();
@@ -245,15 +245,11 @@ VoxelEllipsoidShape.prototype.update = function (
const shaderDefines = this.shaderDefines;
// To keep things simple, clear the defines every time
- shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = undefined;
- shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = undefined;
- shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = undefined;
- shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = undefined;
- shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = undefined;
- shaderDefines["ELLIPSOID_OUTER"] = undefined;
- shaderDefines["ELLIPSOID_INNER"] = undefined;
+ for (const key in shaderDefines) {
+ if (shaderDefines.hasOwnProperty(key)) {
+ shaderDefines[key] = undefined;
+ }
+ }
shaderUniforms.ellipsoidRectangle = Cartesian4.fromElements(
west,
@@ -292,7 +288,7 @@ VoxelEllipsoidShape.prototype.update = function (
const hasBottomConeRegular = south <= 0.0 && south > -CesiumMath.PI_OVER_TWO;
const hasBottomConeFlipped = south > 0.0;
- // Determine how many intersections there are going to be.
+ // Keep track of how many intersections there are going to be.
let intersectionCount = 0;
// Intersects an outer ellipsoid for the max height.
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 268ba6580a7..7ded9eb7d57 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -215,29 +215,29 @@ uniform float u_stepSize;
#if defined(SHAPE_ELLIPSOID)
/* Ellipsoid defines:
+ #define ELLIPSOID_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
#define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
#define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
#define ELLIPSOID_CONE_BOTTOM_REGULAR ### // when there's a bottom cone
#define ELLIPSOID_CONE_BOTTOM_FLIPPED ### // when cone shape has two intersection intervals
#define ELLIPSOID_CONE_TOP_REGULAR ### // when there's a top cone
#define ELLIPSOID_CONE_TOP_FLIPPED ### // when cone shape has two intersection intervals
- #define ELLIPSOID_INNER ### // when there's an inner ellipsoid
#define ELLIPSOID_OUTER ### // outer ellipsoid - always defined
- #define ELLIPSOID_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
+ #define ELLIPSOID_INNER ### // when there's an inner ellipsoid
*/
// Ellipsoid uniforms
uniform vec3 u_ellipsoidRadiiUv; // [0,1]
uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
- #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED) || defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED) || defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
#endif
#if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
uniform float u_ellipsoidWestUv;
uniform float u_ellipsoidInverseLongitudeRangeUv;
#endif
- #if defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
uniform float u_ellipsoidSouthUv;
uniform float u_ellipsoidInverseLatitudeRangeUv;
#endif
@@ -250,25 +250,47 @@ uniform float u_stepSize;
#if defined(SHAPE_CYLINDER)
/* Cylinder defines:
- #define CYLINDER_OUTER ### // outer cylinder
- #define CYLINDER_INNER ### // when there's an inner cylinder
+ #define CYLINDER_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
+ #define CYLINDER_OUTER_INDEX ### // outer cylinder
+ #define CYLINDER_OUTER_NON_DEFAULT ### //
+ #define CYLINDER_INNER
#define CYLINDER_INNER_OUTER_EQUAL ### // when inner and outer cylinder have the same radius
+ #define CYLINDER_INNER_INDEX ### // when there's an inner cylinder
+ #define CYLINDER_HEIGHT_NON_DEFAULT ### //
+ #define CYLINDER_HEIGHT_ZERO // when the height is 0
+ #define CYLINDER_WEDGE_INDEX //
#define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
#define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
- #define CYLINDER_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
*/
// Cylinder uniforms
+ #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER) || defined(CYLINDER_HEIGHT_NON_DEFAULT)
+ uniform vec3 u_cylinderScaleUvToBounds;
+ uniform vec3 u_cylinderTranslateUvToBounds;
+ #endif
+
+ #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
+ uniform float u_cylinderInnerRadiusUv;
+ uniform float u_cylinderInverseRadiusRangeUv;
+ #endif
+
#if defined(CYLINDER_INNER)
- uniform float u_cylinderInnerRadiusUv;
+ uniform vec3 u_cylinderScaleUvToInnerBounds;
+ uniform vec3 u_cylinderTranslateUvToInnerBounds;
+ #endif
+ #if defined(CYLINDER_HEIGHT_NON_DEFAULT)
+ uniform float u_cylinderMinHeightUv;
+ uniform float u_cylinderInverseHeightRangeUv;
#endif
- #if defined(CYLINDER_WEDGE_REGULAR) || defined(CYLINDER_WEDGE_FLIPPED)
+ #if defined(CYLINDER_WEDGE)
uniform float u_cylinderMinAngle;
- uniform float u_cylinderInverseAngleRange;
+ uniform float u_cylinderMaxAngle;
+ uniform float u_cylinderMinAngleUv;
+ uniform float u_cylinderInverseAngleRangeUv;
#endif
#endif
-// Determine how many intersections there are going to be
+// Keep track of how many intersections there are going to be
#if defined(SHAPE_BOX)
#define SHAPE_INTERSECTION_COUNT BOX_INTERSECTION_COUNT
#elif defined(SHAPE_ELLIPSOID)
@@ -739,33 +761,33 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
#endif
// Bottom cone
- #if defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED)
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
// Flip the inputs because the intersection function expects a cone growing towards +Z.
- float flippedSouth = -u_ellipsoidRectangle.y;
+ float flippedSouth = -u_ellipsoidRectangle.y; // [-halfPi,+halfPi]
Ray flippedRay = ray;
flippedRay.dir.z *= -1.0;
flippedRay.pos.z *= -1.0;
- #if defined(ELLIPSOID_CONE_BOT_REGULAR)
- vec2 botConeIx = intersectRegularCone(flippedRay, flippedSouth);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOT_REGULAR, botConeIx);
- #elif defined(ELLIPSOID_CONE_BOT_FLIPPED)
- vec4 botConeIx = intersectFlippedCone(flippedRay, flippedSouth);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOT_FLIPPED + 0, botConeIx.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOT_FLIPPED + 1, botConeIx.zw);
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR)
+ vec2 bottomConeIntersection = intersectRegularCone(flippedRay, flippedSouth);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_REGULAR, bottomConeIntersection);
+ #elif defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
+ vec4 bottomConeIntersection = intersectFlippedCone(flippedRay, flippedSouth);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 0, bottomConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 1, bottomConeIntersection.zw);
#endif
#endif
// Top cone
#if defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
- float north = u_ellipsoidRectangle.w;
+ float north = u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
#if defined(ELLIPSOID_CONE_TOP_REGULAR)
- vec2 topConeIntersect = intersectRegularCone(ray, north);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersect);
+ vec2 topConeIntersection = intersectRegularCone(ray, north);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersection);
#elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
- vec4 topConeIntersect = intersectFlippedCone(ray, north);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersect.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersect.zw);
+ vec4 topConeIntersection = intersectFlippedCone(ray, north);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersection.zw);
#endif
#endif
@@ -843,7 +865,7 @@ vec2 intersectUnitCircle(Ray ray) {
}
#endif
-#if defined(SHAPE_CYLINDER) && defined(BOUNDS_0_MIN)
+#if defined(SHAPE_CYLINDER) && defined(CYLINDER_INNER) && !defined(CYLINDER_INNER_OUTER_EQUAL)
vec2 intersectInfiniteUnitCylinder(Ray ray)
{
vec3 o = ray.pos;
@@ -871,91 +893,61 @@ vec2 intersectInfiniteUnitCylinder(Ray ray)
#if defined(SHAPE_CYLINDER)
void intersectCylinderShape(Ray ray, inout Intersections ix)
{
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir *= 2.0;
+ #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER) || defined(CYLINDER_HEIGHT_NON_DEFAULT)
+ Ray outerRay = Ray(ray.pos * u_cylinderScaleUvToBounds + u_cylinderTranslateUvToBounds, ray.dir * u_cylinderScaleUvToBounds);
+ #else
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ Ray outerRay = Ray(ray.pos * 2.0 - 1.0, ray.dir * 2.0);
+ #endif
- #if !defined(BOUNDS)
- return intersectUnitCylinder(ray);
+ #if defined(CYLINDER_HEIGHT_ZERO)
+ vec2 outerIntersect = intersectUnitCircle(outerRay);
#else
- float minRadius = u_minBounds.x; // [0,1]
- float maxRadius = u_maxBounds.x; // [0,1]
- float minHeight = u_minBounds.y; // [-1,+1]
- float maxHeight = u_maxBounds.y; // [-1,+1]
- float minAngle = u_minBounds.z; // [-pi,+pi]
- float maxAngle = u_maxBounds.z; // [-pi,+pi]
-
- float posZ = 0.5 * (minHeight + maxHeight);
- vec3 pos = vec3(0.0, 0.0, posZ);
- float scaleZ = 0.5 * (maxHeight - minHeight);
-
- vec2 outerIntersect;
+ vec2 outerIntersect = intersectUnitCylinder(outerRay);
+ #endif
- // TODO: use define instead of branch
- if (scaleZ == 0.0) {
- vec3 outerScale = vec3(maxRadius, maxRadius, 1.0);
- Ray outerRay = Ray((ray.pos - pos) / outerScale, ray.dir / outerScale);
- outerIntersect = intersectUnitCircle(outerRay);
- } else {
- vec3 outerScale = vec3(maxRadius, maxRadius, scaleZ);
- Ray outerRay = Ray((ray.pos - pos) / outerScale, ray.dir / outerScale);
- outerIntersect = intersectUnitCylinder(outerRay);
- }
+ setIntersectionPair(ix, CYLINDER_OUTER_INDEX, outerIntersect);
- if (outerIntersect.x == NO_HIT) {
- return;
- }
+ if (outerIntersect.x == NO_HIT) {
+ return;
+ }
- setIntersectionPair(ix, CYLINDER_OUTER, outerIntersect);
+ #if defined(CYLINDER_INNER_OUTER_EQUAL)
+ // When the cylinder is perfectly thin it's necessary to sandwich the
+ // inner cylinder intersection inside the outer cylinder intersection.
- #if defined(CYLINDER_INNER_OUTER_EQUAL)
-
- #else
-
- #endif
-
- vec3 innerScale = vec3(minRadius, minRadius, 1.0);
- Ray innerRay = Ray((ray.pos - pos) / innerScale, ray.dir / innerScale);
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the cylinder to be invisible because it will think the ray
+ // is still inside the inner (negative) cylinder after exiting the
+ // outer (positive) cylinder.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If sortIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #elif defined(CYLINDER_INNER)
+ Ray innerRay = Ray(ray.pos * u_cylinderScaleUvToInnerBounds + u_cylinderTranslateUvToInnerBounds, ray.dir * u_cylinderScaleUvToInnerBounds);
vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
+ setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
+ #endif
- // TODO: use define instead of branch
- if (minRadius != maxRadius) {
- intersections[2] = vec2(float(2), innerIntersect.x);
- intersections[3] = vec2(float(3), innerIntersect.y);
- } else {
- // When the cylinder is perfectly thin it's necessary to sandwich the
- // inner cylinder intersection inside the outer cylinder intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the cylinder to be invisible because it will think the ray
- // is still inside the inner (negative) cylinder after exiting the
- // outer (positive) cylinder.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If sortIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
-
- setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
- setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
- setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
- setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- }
-
- #if defined(CYLINDER_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectWedge(ray, minAngle, maxAngle);
- setIntersectionPair(ix, CYLINDER_WEDGE_REGULAR, wedgeIntersect);
- #elif defined(CYLINDER_WEDGE_FLIPPED)
- vec2 planeIntersectMinAngle = intersectHalfSpace(ray, minAngle);
- vec2 planeIntersectMaxAngle = intersectHalfSpace(ray, maxAngle);
- setIntersectionPair(ix, CYLINDER_WEDGE_FLIPPED + 0, planeIntersectMinAngle);
- setIntersectionPair(ix, CYLINDER_WEDGE_FLIPPED + 1, planeIntersectMaxAngle);
- #endif
+ #if defined(CYLINDER_WEDGE_REGULAR)
+ vec2 wedgeIntersect = intersectWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
+ #elif defined(CYLINDER_WEDGE_FLIPPED)
+ vec2 planeIntersectMinAngle = intersectHalfSpace(outerRay, u_cylinderMinAngle);
+ vec2 planeIntersectMaxAngle = intersectHalfSpace(outerRay, u_cylinderMaxAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, planeIntersectMinAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, planeIntersectMaxAngle);
#endif
}
#endif
@@ -1017,7 +1009,7 @@ vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Int
return vec2(NO_HIT);
}
- // Set start to 0 when ray is inside the shape.
+ // Set start to 0.0 when ray is inside the shape.
entryExitT.x = max(entryExitT.x, 0.0);
return entryExitT;
@@ -1128,7 +1120,7 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
longitude = (longitude - u_ellipsoidWestUv) * u_ellipsoidInverseLongitudeRangeUv;
#endif
- #if (defined(ELLIPSOID_CONE_BOT_REGULAR) || defined(ELLIPSOID_CONE_BOT_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+ #if (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
latitude = (latitude - u_ellipsoidSouthUv) * u_ellipsoidInverseLatitudeRangeUv;
#endif
@@ -1147,11 +1139,22 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
float height = positionUv.z; // [0,1]
float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
- #if defined(BOUNDS)
- positionShape = (positionShape - u_minBoundsUv) * u_inverseBoundsUv; // [0,1]
- // TODO: This breaks down when minBounds == maxBounds. To fix it, this
- // function would have to know if ray is intersecting the front or back of the shape
- // and set the shape space position to 1 (front) or 0 (back) accordingly.
+ // TODO: This breaks down when minBounds == maxBounds. To fix it, this
+ // function would have to know if ray is intersecting the front or back of the shape
+ // and set the shape space position to 1 (front) or 0 (back) accordingly.
+
+ #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
+ radius = (radius - u_cylinderInnerRadiusUv) * u_cylinderInverseRadiusRangeUv;
+ #endif
+
+ #if defined(CYLINDER_HEIGHT_NON_DEFAULT)
+ height = (height - u_cylinderMinHeightUv) * u_cylinderInverseHeightRangeUv;
+ #endif
+
+ #if defined(CYLINDER_WEDGE_REGULAR)
+ angle = (angle - u_cylinderMinAngleUv) * u_cylinderInverseAngleRangeUv;
+ #elif defined(CYLINDER_WEDGE_FLIPPED)
+ angle = (angle - u_cylinderMinAngleUv) * u_cylinderInverseAngleRangeUv;
#endif
return vec3(radius, height, angle);
From 25775af3c10cc8e371c6d292404e525de14e641d Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 13 Apr 2022 17:26:54 -0400
Subject: [PATCH 023/679] change to multiply add for cylinder
---
Source/Scene/VoxelCylinderShape.js | 43 +++++++++++++++++++++++-------
Source/Shaders/VoxelFS.glsl | 12 ++++-----
2 files changed, 40 insertions(+), 15 deletions(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index c6d9d7833c0..62f72fae4d0 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -97,10 +97,10 @@ function VoxelCylinderShape() {
cylinderTranslateUvToBounds: new Cartesian3(),
cylinderScaleUvToInnerBounds: new Cartesian3(),
cylinderTranslateUvToInnerBounds: new Cartesian3(),
- cylinderInnerRadiusUv: 0.0,
- cylinderInverseRadiusRangeUv: 0.0,
- cylinderMinHeightUv: 0.0,
- cylinderInverseHeightRangeUv: 0.0,
+ cylinderScaleRadiusUvToBoundsRadiusUv: 0.0,
+ cylinderOffsetRadiusUvToBoundsRadiusUv: 0.0,
+ cylinderScaleHeightUvToBoundsHeightUv: 0.0,
+ cylinderOffsetHeightUvToBoundsHeightUv: 0.0,
cylinderMinAngle: 0.0,
cylinderMaxAngle: 0.0,
cylinderMinAngleUv: 0.0,
@@ -358,19 +358,44 @@ VoxelCylinderShape.prototype.update = function (
shaderUniforms.cylinderTranslateUvToInnerBounds
);
- shaderUniforms.cylinderInnerRadiusUv = minRadius / defaultMaxRadius;
- shaderUniforms.cylinderInverseRadiusRangeUv =
- 1.0 / (maxRadius / defaultMaxRadius - minRadius / defaultMaxRadius);
+ // delerp(radius, minRadius, maxRadius)
+ // (radius - minRadius) / (maxRadius - minRadius)
+ // radius / (maxRadius - minRadius) - minRadius / (maxRadius - minRadius)
+ // scale = 1.0 / (maxRadius - minRadius)
+ // offset = -minRadius / (maxRadius - minRadius)
+ // offset = minRadius / (minRadius - maxRadius)
+
+ const scale = 1.0 / (maxRadius - minRadius);
+ const offset = minRadius / (minRadius - maxRadius);
+
+ shaderUniforms.cylinderScaleRadiusUvToBoundsRadiusUv = scale;
+ shaderUniforms.cylinderOffsetRadiusUvToBoundsRadiusUv = offset;
}
if (!isDefaultHeight) {
- shaderUniforms.cylinderMinHeightUv = minHeight * 0.5 + 0.5;
- shaderUniforms.cylinderInverseHeightRangeUv = 2.0 / (maxHeight - minHeight);
shaderDefines["CYLINDER_HEIGHT_NON_DEFAULT"] = true;
if (minHeight === maxHeight) {
shaderDefines["CYLINDER_HEIGHT_ZERO"] = true;
}
+
+ // delerp(heightUv, minHeightUv, maxHeightUv)
+ // (heightUv - minHeightUv) / (maxHeightUv - minHeightUv)
+ // heightUv / (maxHeightUv - minHeightUv) - minHeightUv / (maxHeightUv - minHeightUv)
+ // scale = 1.0 / (maxHeightUv - minHeightUv)
+ // scale = 1.0 / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
+ // scale = 2.0 / (maxHeight - minHeight)
+ // offset = -minHeightUv / (maxHeightUv - minHeightUv)
+ // offset = -minHeightUv / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
+ // offset = -2.0 * (minHeight * 0.5 + 0.5) / (maxHeight - minHeight)
+ // offset = -(minHeight + 1.0) / (maxHeight - minHeight)
+ // offset = (minHeight + 1.0) / (minHeight - maxHeight)
+
+ const scale = 2.0 / (maxHeight - minHeight);
+ const offset = (minHeight + 1.0) / (minHeight - maxHeight);
+
+ shaderUniforms.cylinderScaleHeightUvToBoundsHeightUv = scale;
+ shaderUniforms.cylinderOffsetHeightUvToBoundsHeightUv = offset;
}
if (hasWedge) {
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 7ded9eb7d57..3225d9eb2ae 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -270,8 +270,8 @@ uniform float u_stepSize;
#endif
#if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
- uniform float u_cylinderInnerRadiusUv;
- uniform float u_cylinderInverseRadiusRangeUv;
+ uniform float u_cylinderScaleRadiusUvToBoundsRadiusUv;
+ uniform float u_cylinderOffsetRadiusUvToBoundsRadiusUv;
#endif
#if defined(CYLINDER_INNER)
@@ -279,8 +279,8 @@ uniform float u_stepSize;
uniform vec3 u_cylinderTranslateUvToInnerBounds;
#endif
#if defined(CYLINDER_HEIGHT_NON_DEFAULT)
- uniform float u_cylinderMinHeightUv;
- uniform float u_cylinderInverseHeightRangeUv;
+ uniform float u_cylinderScaleHeightUvToBoundsHeightUv;
+ uniform float u_cylinderOffsetHeightUvToBoundsHeightUv;
#endif
#if defined(CYLINDER_WEDGE)
uniform float u_cylinderMinAngle;
@@ -1144,11 +1144,11 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
// and set the shape space position to 1 (front) or 0 (back) accordingly.
#if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
- radius = (radius - u_cylinderInnerRadiusUv) * u_cylinderInverseRadiusRangeUv;
+ radius = radius * u_cylinderScaleRadiusUvToBoundsRadiusUv + u_cylinderOffsetRadiusUvToBoundsRadiusUv;
#endif
#if defined(CYLINDER_HEIGHT_NON_DEFAULT)
- height = (height - u_cylinderMinHeightUv) * u_cylinderInverseHeightRangeUv;
+ height = height * u_cylinderScaleHeightUvToBoundsHeightUv + u_cylinderOffsetHeightUvToBoundsHeightUv;
#endif
#if defined(CYLINDER_WEDGE_REGULAR)
From 9f0e8e7266b53a11e17c791f12a235ca80981390 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 13 Apr 2022 17:49:51 -0400
Subject: [PATCH 024/679] rearranged intersection functions by shape type
---
Source/Shaders/VoxelFS.glsl | 463 ++++++++++++++++++------------------
1 file changed, 231 insertions(+), 232 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 3225d9eb2ae..a49b7199cfe 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -538,31 +538,6 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
}
#endif
-#if defined(SHAPE_BOX)
-void intersectBoxShape(Ray ray, out Intersections ix)
-{
- #if !defined(BOX_BOUNDED)
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir = ray.dir * 2.0;
- vec2 entryExit = intersectUnitCube(ray);
- #elif defined(BOX_XY_PLANE) || defined(BOX_XZ_PLANE) || defined(BOX_YZ_PLANE)
- // Transform the ray into unit square space on Z plane
- ray.pos = vec3(u_boxTransformUvToBounds * vec4(ray.pos, 1.0));
- ray.dir = vec3(u_boxTransformUvToBounds * vec4(ray.dir, 0.0));
- vec2 entryExit = intersectUnitSquare(ray);
- #else
- // Transform the ray into unit cube space
- ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxTranslateUvToBounds;
- ray.dir *= u_boxScaleUvToBounds;
- vec2 entryExit = intersectUnitCube(ray);
- #endif
-
- setIntersectionPair(ix, BOX_INTERSECTION_INDEX, entryExit);
-}
-#endif
-
#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_REGULAR)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_REGULAR))
vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
{
@@ -736,78 +711,6 @@ vec2 intersectRegularCone(Ray ray, float latitude) {
}
#endif
-#if defined(SHAPE_ELLIPSOID)
-void intersectEllipsoidShape(in Ray ray, inout Intersections ix)
-{
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir *= 2.0;
-
- // Outer ellipsoid
- vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- setIntersectionPair(ix, ELLIPSOID_OUTER, outerIntersect);
-
- // Exit early if the outer ellipsoid was missed.
- if (outerIntersect.x == NO_HIT) {
- return;
- }
-
- // Inner ellipsoid
- #if defined(ELLIPSOID_INNER)
- Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
- vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- setIntersectionPair(ix, ELLIPSOID_INNER, innerIntersect);
- #endif
-
- // Bottom cone
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
- // Flip the inputs because the intersection function expects a cone growing towards +Z.
- float flippedSouth = -u_ellipsoidRectangle.y; // [-halfPi,+halfPi]
- Ray flippedRay = ray;
- flippedRay.dir.z *= -1.0;
- flippedRay.pos.z *= -1.0;
-
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR)
- vec2 bottomConeIntersection = intersectRegularCone(flippedRay, flippedSouth);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_REGULAR, bottomConeIntersection);
- #elif defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
- vec4 bottomConeIntersection = intersectFlippedCone(flippedRay, flippedSouth);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 0, bottomConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 1, bottomConeIntersection.zw);
- #endif
- #endif
-
- // Top cone
- #if defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
- float north = u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
- #if defined(ELLIPSOID_CONE_TOP_REGULAR)
- vec2 topConeIntersection = intersectRegularCone(ray, north);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersection);
- #elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
- vec4 topConeIntersection = intersectFlippedCone(ray, north);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersection.zw);
- #endif
- #endif
-
- // Wedge
- #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
- float west = u_ellipsoidRectangle.x; // [-pi,+pi]
- float east = u_ellipsoidRectangle.z; // [-pi,+pi]
- #if defined(ELLIPSOID_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectWedge(ray, west, east);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
- #elif defined(ELLIPSOID_WEDGE_FLIPPED)
- vec2 planeIntersectWest = intersectHalfSpace(ray, west);
- vec2 planeIntersectEast = intersectHalfSpace(ray, east);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 0, planeIntersectWest);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 1, planeIntersectEast);
- #endif
- #endif
-}
-#endif
-
#if defined(SHAPE_CYLINDER)
vec2 intersectUnitCylinder(Ray ray)
{
@@ -890,141 +793,6 @@ vec2 intersectInfiniteUnitCylinder(Ray ray)
}
#endif
-#if defined(SHAPE_CYLINDER)
-void intersectCylinderShape(Ray ray, inout Intersections ix)
-{
- #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER) || defined(CYLINDER_HEIGHT_NON_DEFAULT)
- Ray outerRay = Ray(ray.pos * u_cylinderScaleUvToBounds + u_cylinderTranslateUvToBounds, ray.dir * u_cylinderScaleUvToBounds);
- #else
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- Ray outerRay = Ray(ray.pos * 2.0 - 1.0, ray.dir * 2.0);
- #endif
-
- #if defined(CYLINDER_HEIGHT_ZERO)
- vec2 outerIntersect = intersectUnitCircle(outerRay);
- #else
- vec2 outerIntersect = intersectUnitCylinder(outerRay);
- #endif
-
- setIntersectionPair(ix, CYLINDER_OUTER_INDEX, outerIntersect);
-
- if (outerIntersect.x == NO_HIT) {
- return;
- }
-
- #if defined(CYLINDER_INNER_OUTER_EQUAL)
- // When the cylinder is perfectly thin it's necessary to sandwich the
- // inner cylinder intersection inside the outer cylinder intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the cylinder to be invisible because it will think the ray
- // is still inside the inner (negative) cylinder after exiting the
- // outer (positive) cylinder.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If sortIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
- setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
- setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
- setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
- setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #elif defined(CYLINDER_INNER)
- Ray innerRay = Ray(ray.pos * u_cylinderScaleUvToInnerBounds + u_cylinderTranslateUvToInnerBounds, ray.dir * u_cylinderScaleUvToInnerBounds);
- vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
- setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
- #endif
-
- #if defined(CYLINDER_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
- #elif defined(CYLINDER_WEDGE_FLIPPED)
- vec2 planeIntersectMinAngle = intersectHalfSpace(outerRay, u_cylinderMinAngle);
- vec2 planeIntersectMaxAngle = intersectHalfSpace(outerRay, u_cylinderMaxAngle);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, planeIntersectMinAngle);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, planeIntersectMaxAngle);
- #endif
-}
-#endif
-
-void intersectShape(Ray ray, out Intersections ix) {
- #if defined(SHAPE_BOX)
- intersectBoxShape(ray, ix);
- #elif defined(SHAPE_ELLIPSOID)
- intersectEllipsoidShape(ray, ix);
- #elif defined(SHAPE_CYLINDER)
- intersectCylinderShape(ray, ix);
- #endif
-}
-
-#if defined(DEPTH_TEST)
-float intersectDepth(vec2 screenCoord, Ray ray) {
- float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenCoord));
- if (logDepthOrDepth != 0.0) {
- // Calculate how far the ray must travel before it hits the depth buffer.
- vec4 eyeCoordinateDepth = czm_screenToEyeCoordinates(screenCoord, logDepthOrDepth);
- eyeCoordinateDepth /= eyeCoordinateDepth.w;
- vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
- return dot(depthPositionUv - ray.pos, ray.dir);
- } else {
- // There's no depth at this position so set it to some really far value.
- return +INF_HIT;
- }
-}
-#endif
-
-vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Intersections ix) {
- Ray ray = Ray(positionUv, directionUv);
-
- // Do a ray-shape intersection to find the exact starting and ending points.
- intersectShape(ray, ix);
-
- // Check if the positive shape was completely missed, and if so, exit early.
- float entryPositiveShapeT = getIntersection(ix, 0);
- if (entryPositiveShapeT == NO_HIT) {
- return vec2(NO_HIT);
- }
-
- // Intersect depth texture
- #if defined(DEPTH_TEST)
- float depthT = intersectDepth(screenCoord, ray);
- setIntersectionPair(ix, DEPTH_INTERSECTION_INDEX, vec2(depthT, +INF_HIT));
- #endif
-
- // Find the first intersection interval
- #if (SCENE_INTERSECTION_COUNT > 1)
- vec2 entryExitT = initializeIntersections(ix);
- #else
- float exitPositiveShapeT = getIntersection(ix, 1);
- vec2 entryExitT = vec2(entryPositiveShapeT, exitPositiveShapeT);
- #endif
-
- // Intersection is invalid when start and end are behind the ray.
- if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
- return vec2(NO_HIT);
- }
-
- // Set start to 0.0 when ray is inside the shape.
- entryExitT.x = max(entryExitT.x, 0.0);
-
- return entryExitT;
-}
-
-#if defined(SHAPE_BOX)
-vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
- #if defined(BOX_BOUNDED)
- return positionUv * u_boxScaleUvToBoundsUv + u_boxTranslateUvToBoundsUv;
- #else
- return positionUv;
- #endif
-}
-#endif
-
#if defined(SHAPE_ELLIPSOID)
// robust iterative solution without trig functions
// https://github.com/0xfaded/ellipse_demo/issues/1
@@ -1101,6 +869,112 @@ float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
}
#endif
+#if defined(SHAPE_BOX)
+void intersectBoxShape(Ray ray, out Intersections ix)
+{
+ #if !defined(BOX_BOUNDED)
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir = ray.dir * 2.0;
+ vec2 entryExit = intersectUnitCube(ray);
+ #elif defined(BOX_XY_PLANE) || defined(BOX_XZ_PLANE) || defined(BOX_YZ_PLANE)
+ // Transform the ray into unit square space on Z plane
+ ray.pos = vec3(u_boxTransformUvToBounds * vec4(ray.pos, 1.0));
+ ray.dir = vec3(u_boxTransformUvToBounds * vec4(ray.dir, 0.0));
+ vec2 entryExit = intersectUnitSquare(ray);
+ #else
+ // Transform the ray into unit cube space
+ ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxTranslateUvToBounds;
+ ray.dir *= u_boxScaleUvToBounds;
+ vec2 entryExit = intersectUnitCube(ray);
+ #endif
+
+ setIntersectionPair(ix, BOX_INTERSECTION_INDEX, entryExit);
+}
+#endif
+
+#if defined(SHAPE_BOX)
+vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
+ #if defined(BOX_BOUNDED)
+ return positionUv * u_boxScaleUvToBoundsUv + u_boxTranslateUvToBoundsUv;
+ #else
+ return positionUv;
+ #endif
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir *= 2.0;
+
+ // Outer ellipsoid
+ vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
+ setIntersectionPair(ix, ELLIPSOID_OUTER, outerIntersect);
+
+ // Exit early if the outer ellipsoid was missed.
+ if (outerIntersect.x == NO_HIT) {
+ return;
+ }
+
+ // Inner ellipsoid
+ #if defined(ELLIPSOID_INNER)
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ setIntersectionPair(ix, ELLIPSOID_INNER, innerIntersect);
+ #endif
+
+ // Bottom cone
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
+ // Flip the inputs because the intersection function expects a cone growing towards +Z.
+ float flippedSouth = -u_ellipsoidRectangle.y; // [-halfPi,+halfPi]
+ Ray flippedRay = ray;
+ flippedRay.dir.z *= -1.0;
+ flippedRay.pos.z *= -1.0;
+
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR)
+ vec2 bottomConeIntersection = intersectRegularCone(flippedRay, flippedSouth);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_REGULAR, bottomConeIntersection);
+ #elif defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
+ vec4 bottomConeIntersection = intersectFlippedCone(flippedRay, flippedSouth);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 0, bottomConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 1, bottomConeIntersection.zw);
+ #endif
+ #endif
+
+ // Top cone
+ #if defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ float north = u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
+ #if defined(ELLIPSOID_CONE_TOP_REGULAR)
+ vec2 topConeIntersection = intersectRegularCone(ray, north);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersection);
+ #elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ vec4 topConeIntersection = intersectFlippedCone(ray, north);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersection.zw);
+ #endif
+ #endif
+
+ // Wedge
+ #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
+ float west = u_ellipsoidRectangle.x; // [-pi,+pi]
+ float east = u_ellipsoidRectangle.z; // [-pi,+pi]
+ #if defined(ELLIPSOID_WEDGE_REGULAR)
+ vec2 wedgeIntersect = intersectWedge(ray, west, east);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
+ #elif defined(ELLIPSOID_WEDGE_FLIPPED)
+ vec2 planeIntersectWest = intersectHalfSpace(ray, west);
+ vec2 planeIntersectEast = intersectHalfSpace(ray, east);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 0, planeIntersectWest);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 1, planeIntersectEast);
+ #endif
+ #endif
+}
+#endif
+
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
// 1) Convert positionUv [0,1] to local space [-1,+1] to normalized cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height). A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
@@ -1132,6 +1006,68 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
}
#endif
+#if defined(SHAPE_CYLINDER)
+void intersectCylinderShape(Ray ray, inout Intersections ix)
+{
+ #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER) || defined(CYLINDER_HEIGHT_NON_DEFAULT)
+ Ray outerRay = Ray(ray.pos * u_cylinderScaleUvToBounds + u_cylinderTranslateUvToBounds, ray.dir * u_cylinderScaleUvToBounds);
+ #else
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ Ray outerRay = Ray(ray.pos * 2.0 - 1.0, ray.dir * 2.0);
+ #endif
+
+ #if defined(CYLINDER_HEIGHT_ZERO)
+ vec2 outerIntersect = intersectUnitCircle(outerRay);
+ #else
+ vec2 outerIntersect = intersectUnitCylinder(outerRay);
+ #endif
+
+ setIntersectionPair(ix, CYLINDER_OUTER_INDEX, outerIntersect);
+
+ if (outerIntersect.x == NO_HIT) {
+ return;
+ }
+
+ #if defined(CYLINDER_INNER_OUTER_EQUAL)
+ // When the cylinder is perfectly thin it's necessary to sandwich the
+ // inner cylinder intersection inside the outer cylinder intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the cylinder to be invisible because it will think the ray
+ // is still inside the inner (negative) cylinder after exiting the
+ // outer (positive) cylinder.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If sortIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #elif defined(CYLINDER_INNER)
+ Ray innerRay = Ray(ray.pos * u_cylinderScaleUvToInnerBounds + u_cylinderTranslateUvToInnerBounds, ray.dir * u_cylinderScaleUvToInnerBounds);
+ vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
+ setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
+ #endif
+
+ #if defined(CYLINDER_WEDGE_REGULAR)
+ vec2 wedgeIntersect = intersectWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
+ #elif defined(CYLINDER_WEDGE_FLIPPED)
+ vec2 planeIntersectMinAngle = intersectHalfSpace(outerRay, u_cylinderMinAngle);
+ vec2 planeIntersectMaxAngle = intersectHalfSpace(outerRay, u_cylinderMaxAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, planeIntersectMinAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, planeIntersectMaxAngle);
+ #endif
+}
+#endif
+
#if defined(SHAPE_CYLINDER)
vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
vec3 positionLocal = positionUv * 2.0 - 1.0; // [-1,+1]
@@ -1171,6 +1107,69 @@ vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
#endif
}
+void intersectShape(Ray ray, out Intersections ix) {
+ #if defined(SHAPE_BOX)
+ intersectBoxShape(ray, ix);
+ #elif defined(SHAPE_ELLIPSOID)
+ intersectEllipsoidShape(ray, ix);
+ #elif defined(SHAPE_CYLINDER)
+ intersectCylinderShape(ray, ix);
+ #endif
+}
+
+#if defined(DEPTH_TEST)
+float intersectDepth(vec2 screenCoord, Ray ray) {
+ float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenCoord));
+ if (logDepthOrDepth != 0.0) {
+ // Calculate how far the ray must travel before it hits the depth buffer.
+ vec4 eyeCoordinateDepth = czm_screenToEyeCoordinates(screenCoord, logDepthOrDepth);
+ eyeCoordinateDepth /= eyeCoordinateDepth.w;
+ vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
+ return dot(depthPositionUv - ray.pos, ray.dir);
+ } else {
+ // There's no depth at this position so set it to some really far value.
+ return +INF_HIT;
+ }
+}
+#endif
+
+vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Intersections ix) {
+ Ray ray = Ray(positionUv, directionUv);
+
+ // Do a ray-shape intersection to find the exact starting and ending points.
+ intersectShape(ray, ix);
+
+ // Check if the positive shape was completely missed, and if so, exit early.
+ float entryPositiveShapeT = getIntersection(ix, 0);
+ if (entryPositiveShapeT == NO_HIT) {
+ return vec2(NO_HIT);
+ }
+
+ // Add depth to the list of intersections
+ #if defined(DEPTH_TEST)
+ float depthT = intersectDepth(screenCoord, ray);
+ setIntersectionPair(ix, DEPTH_INTERSECTION_INDEX, vec2(depthT, +INF_HIT));
+ #endif
+
+ // Find the first intersection interval
+ #if (SCENE_INTERSECTION_COUNT > 1)
+ vec2 entryExitT = initializeIntersections(ix);
+ #else
+ float exitPositiveShapeT = getIntersection(ix, 1);
+ vec2 entryExitT = vec2(entryPositiveShapeT, exitPositiveShapeT);
+ #endif
+
+ // Intersection is invalid when start and end are behind the ray.
+ if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
+ return vec2(NO_HIT);
+ }
+
+ // Set start to 0.0 when ray is inside the shape.
+ entryExitT.x = max(entryExitT.x, 0.0);
+
+ return entryExitT;
+}
+
// --------------------------------------------------------
// Megatexture
// --------------------------------------------------------
From dfecff7e48ca3381c906c2ff03142a325d5e74a3 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 13 Apr 2022 18:22:48 -0400
Subject: [PATCH 025/679] fix for the flipped wedge
---
Source/Scene/VoxelCylinderShape.js | 4 ++--
Source/Shaders/VoxelFS.glsl | 11 +++++------
2 files changed, 7 insertions(+), 8 deletions(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index 62f72fae4d0..743d64748a7 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -401,8 +401,8 @@ VoxelCylinderShape.prototype.update = function (
if (hasWedge) {
shaderDefines["CYLINDER_WEDGE"] = true;
shaderDefines["CYLINDER_WEDGE_INDEX"] = intersectionCount;
- shaderUniforms.minAngle = minAngle;
- shaderUniforms.maxAngle = maxAngle;
+ shaderUniforms.cylinderMinAngle = minAngle;
+ shaderUniforms.cylinderMaxAngle = maxAngle;
const minAngleUv =
(minAngle - defaultMinAngle) / (defaultMaxAngle - defaultMinAngle);
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index a49b7199cfe..54305e580a7 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -315,14 +315,14 @@ struct Ray {
};
#if defined(JITTER)
-#define HASHSCALE 50.0
float hash(vec2 p)
{
- vec3 p3 = fract(vec3(p.xyx) * HASHSCALE);
+ vec3 p3 = fract(vec3(p.xyx) * 50.0); // magic number = hashscale
p3 += dot(p3, p3.yzx + 19.19);
return fract((p3.x + p3.y) * p3.z);
}
#endif
+
float signNoZero(float v) {
return (v < 0.0) ? -1.0 : 1.0;
}
@@ -499,8 +499,7 @@ vec2 initializeIntersections(inout Intersections ix) {
#endif
#if defined(SHAPE_BOX)
-// Unit cube from [-1, +1]
-vec2 intersectUnitCube(Ray ray)
+vec2 intersectUnitCube(Ray ray) // Unit cube from [-1, +1]
{
vec3 o = ray.pos;
vec3 d = ray.dir;
@@ -967,7 +966,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
setIntersectionPair(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
#elif defined(ELLIPSOID_WEDGE_FLIPPED)
vec2 planeIntersectWest = intersectHalfSpace(ray, west);
- vec2 planeIntersectEast = intersectHalfSpace(ray, east);
+ vec2 planeIntersectEast = intersectHalfSpace(ray, east + czm_pi);
setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 0, planeIntersectWest);
setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 1, planeIntersectEast);
#endif
@@ -1061,7 +1060,7 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
#elif defined(CYLINDER_WEDGE_FLIPPED)
vec2 planeIntersectMinAngle = intersectHalfSpace(outerRay, u_cylinderMinAngle);
- vec2 planeIntersectMaxAngle = intersectHalfSpace(outerRay, u_cylinderMaxAngle);
+ vec2 planeIntersectMaxAngle = intersectHalfSpace(outerRay, u_cylinderMaxAngle + czm_pi);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, planeIntersectMinAngle);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, planeIntersectMaxAngle);
#endif
From 34ddca7eb7ce582d6030812b4e4dbf05b981624a Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Wed, 13 Apr 2022 20:30:57 -0400
Subject: [PATCH 026/679] fixed flat cylinder and shell cylinder
---
Source/Scene/VoxelCylinderShape.js | 20 ++++++------
Source/Scene/VoxelPrimitive.js | 15 +++++----
Source/Shaders/VoxelFS.glsl | 51 ++++++++++++++++--------------
3 files changed, 46 insertions(+), 40 deletions(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index 743d64748a7..2250ec6c53c 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -191,22 +191,21 @@ VoxelCylinderShape.prototype.update = function (
const minAngle = CesiumMath.negativePiToPi(minBounds.z);
const maxAngle = CesiumMath.negativePiToPi(maxBounds.z);
- const outerExtent = Cartesian3.fromElements(
- scale.x * maxRadius,
- scale.y * maxRadius,
- scale.z * 0.5 * (maxHeight - minHeight)
- );
-
// Exit early if the shape is not visible.
// Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
+
+ // Cylinder is not visible if:
+ // - maxRadius is zero (line)
+ // - minHeight is greater than minHeight
+ // - scale is 0 for any component (too annoying to reconstruct rotation matrix)
const absEpsilon = CesiumMath.EPSILON10;
if (
maxRadius === 0.0 ||
minRadius > maxRadius ||
minHeight > maxHeight ||
- CesiumMath.equalsEpsilon(outerExtent.x, 0.0, undefined, absEpsilon) ||
- CesiumMath.equalsEpsilon(outerExtent.y, 0.0, undefined, absEpsilon) ||
- CesiumMath.equalsEpsilon(outerExtent.z, 0.0, undefined, absEpsilon)
+ CesiumMath.equalsEpsilon(scale.x, 0.0, undefined, absEpsilon) ||
+ CesiumMath.equalsEpsilon(scale.y, 0.0, undefined, absEpsilon) ||
+ CesiumMath.equalsEpsilon(scale.z, 0.0, undefined, absEpsilon)
) {
return false;
}
@@ -318,8 +317,9 @@ VoxelCylinderShape.prototype.update = function (
shaderDefines["CYLINDER_INNER_OUTER_EQUAL"] = true;
} else {
shaderDefines["CYLINDER_INNER_INDEX"] = intersectionCount;
- intersectionCount += 1;
}
+
+ intersectionCount += 1;
}
if (!isDefaultOuterCylinder || hasInnerCylinder || !isDefaultHeight) {
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 3a3e6fbdaed..3144afae67c 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1229,12 +1229,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
const maxBoundsDirty = !Cartesian3.equals(maxBounds, maxBoundsOld);
const shapeDirty = compoundTransformDirty || minBoundsDirty || maxBoundsDirty;
- // Update the shape on the first frame or if it's dirty.
- // If the shape is visible it will do some extra work.
- if (
- (!this._ready || shapeDirty) &&
- (this._shapeVisible = shape.update(compoundTransform, minBounds, maxBounds))
- ) {
+ if (shapeDirty) {
if (compoundTransformDirty) {
this._compoundModelMatrixOld = Matrix4.clone(
compoundTransform,
@@ -1247,7 +1242,14 @@ VoxelPrimitive.prototype.update = function (frameState) {
if (maxBoundsDirty) {
this._maxBoundsOld = Cartesian3.clone(maxBounds, this._maxBoundsOld);
}
+ }
+ // Update the shape on the first frame or if it's dirty.
+ // If the shape is visible it will need to do some extra work.
+ if (
+ (!this._ready || shapeDirty) &&
+ (this._shapeVisible = shape.update(compoundTransform, minBounds, maxBounds))
+ ) {
// Rebuild the shader if any of the shape defines changed.
const shapeDefines = shape.shaderDefines;
const shapeDefinesOld = this._shapeDefinesOld;
@@ -2282,6 +2284,7 @@ function buildDrawCommands(that, context) {
that._drawCommandPick = drawCommandPick;
// console.log(drawCommand.shaderProgram._fragmentShaderText);
+ console.log("recompile");
}
/**
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 54305e580a7..50e20e922ab 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -767,7 +767,7 @@ vec2 intersectUnitCircle(Ray ray) {
}
#endif
-#if defined(SHAPE_CYLINDER) && defined(CYLINDER_INNER) && !defined(CYLINDER_INNER_OUTER_EQUAL)
+#if defined(SHAPE_CYLINDER) && defined(CYLINDER_INNER)
vec2 intersectInfiniteUnitCylinder(Ray ray)
{
vec3 o = ray.pos;
@@ -1028,31 +1028,34 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
return;
}
- #if defined(CYLINDER_INNER_OUTER_EQUAL)
- // When the cylinder is perfectly thin it's necessary to sandwich the
- // inner cylinder intersection inside the outer cylinder intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the cylinder to be invisible because it will think the ray
- // is still inside the inner (negative) cylinder after exiting the
- // outer (positive) cylinder.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If sortIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
- setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
- setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
- setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
- setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #elif defined(CYLINDER_INNER)
+ #if defined(CYLINDER_INNER)
Ray innerRay = Ray(ray.pos * u_cylinderScaleUvToInnerBounds + u_cylinderTranslateUvToInnerBounds, ray.dir * u_cylinderScaleUvToInnerBounds);
vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
- setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
+
+ #if defined(CYLINDER_INNER_OUTER_EQUAL)
+ // When the cylinder is perfectly thin it's necessary to sandwich the
+ // inner cylinder intersection inside the outer cylinder intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the cylinder to be invisible because it will think the ray
+ // is still inside the inner (negative) cylinder after exiting the
+ // outer (positive) cylinder.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If sortIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #else
+ setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
+ #endif
#endif
#if defined(CYLINDER_WEDGE_REGULAR)
From 9d86ee5ccac1439a719a78f0a6d018848be6d203 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 14:22:37 -0400
Subject: [PATCH 027/679] fixed some flipped angle problems for cylinder
---
Apps/Sandcastle/gallery/Voxels.html | 2 +
Source/Scene/VoxelCylinderShape.js | 172 ++++++++++------------------
Source/Scene/VoxelPrimitive.js | 5 +-
Source/Shaders/VoxelFS.glsl | 47 +++++---
4 files changed, 91 insertions(+), 135 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index c8ce699bf5e..b8b1321c0cb 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -84,6 +84,8 @@
break;
}
case Cesium.VoxelShapeType.CYLINDER: {
+ this.minBounds = new Cesium.Cartesian3(0.0, -1.0, +0.2);
+ this.maxBounds = new Cesium.Cartesian3(1.0, +1.0, -0.2);
break;
}
}
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index 2250ec6c53c..d0edaebd1f9 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -104,7 +104,8 @@ function VoxelCylinderShape() {
cylinderMinAngle: 0.0,
cylinderMaxAngle: 0.0,
cylinderMinAngleUv: 0.0,
- cylinderInverseAngleRangeUv: 0.0,
+ cylinderScaleAngleUvToBoundsAngleUv: 0.0,
+ cylinderOffsetAngleUvToBoundsAngleUv: 0.0,
};
/**
@@ -120,6 +121,7 @@ function VoxelCylinderShape() {
CYLINDER_INNER_INDEX: undefined,
CYLINDER_HEIGHT_NON_DEFAULT: undefined,
CYLINDER_HEIGHT_ZERO: undefined,
+ CYLINDER_ANGLE_FLIPPED: undefined,
CYLINDER_WEDGE_INDEX: undefined,
CYLINDER_WEDGE_REGULAR: undefined,
CYLINDER_WEDGE_FLIPPED: undefined,
@@ -162,6 +164,7 @@ VoxelCylinderShape.prototype.update = function (
const defaultMaxHeight = VoxelCylinderShape.DefaultMaxBounds.y;
const defaultMinAngle = VoxelCylinderShape.DefaultMinBounds.z;
const defaultMaxAngle = VoxelCylinderShape.DefaultMaxBounds.z;
+ const defaultAngleWidth = defaultMaxAngle - defaultMinAngle;
// Clamp the radii to the valid range
const minRadius = CesiumMath.clamp(
@@ -196,7 +199,7 @@ VoxelCylinderShape.prototype.update = function (
// Cylinder is not visible if:
// - maxRadius is zero (line)
- // - minHeight is greater than minHeight
+ // - minHeight is greater than maxHeight
// - scale is 0 for any component (too annoying to reconstruct rotation matrix)
const absEpsilon = CesiumMath.EPSILON10;
if (
@@ -246,8 +249,8 @@ VoxelCylinderShape.prototype.update = function (
const isDefaultHeight =
minHeight === defaultMinHeight && maxHeight === defaultMaxHeight;
- const angleWidth =
- maxAngle - minAngle + (maxAngle < minAngle) * CesiumMath.TWO_PI;
+ const isAngleFlipped = maxAngle < minAngle;
+ const angleWidth = maxAngle - minAngle + isAngleFlipped * CesiumMath.TWO_PI;
const hasWedgeRegular =
angleWidth >= CesiumMath.PI &&
CesiumMath.lessThan(angleWidth, CesiumMath.TWO_PI, absEpsilon);
@@ -398,20 +401,34 @@ VoxelCylinderShape.prototype.update = function (
shaderUniforms.cylinderOffsetHeightUvToBoundsHeightUv = offset;
}
+ if (isAngleFlipped) {
+ shaderDefines["CYLINDER_ANGLE_FLIPPED"] = true;
+ }
+
if (hasWedge) {
shaderDefines["CYLINDER_WEDGE"] = true;
shaderDefines["CYLINDER_WEDGE_INDEX"] = intersectionCount;
+
shaderUniforms.cylinderMinAngle = minAngle;
shaderUniforms.cylinderMaxAngle = maxAngle;
- const minAngleUv =
- (minAngle - defaultMinAngle) / (defaultMaxAngle - defaultMinAngle);
- const maxAngleUv =
- (maxAngle - defaultMinAngle) / (defaultMaxAngle - defaultMinAngle);
+ // delerp(angleUv, minAngleUv, maxAngleUv)
+ // (angelUv - minAngleUv) / (maxAngleUv - minAngleUv)
+ // angleUv / (maxAngleUv - minAngleUv) - minAngleUv / (maxAngleUv - minAngleUv)
+ // scale = 1.0 / (maxAngleUv - minAngleUv)
+ // scale = 1.0 / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
+ // scale = 2.0 * pi / (maxAngle - minAngle)
+ // offset = -minAngleUv / (maxAngleUv - minAngleUv)
+ // offset = -((minAngle - pi) / (2.0 * pi)) / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
+ // offset = -(minAngle - pi) / (maxAngle - minAngle)
+
+ const minAngleUv = (minAngle - defaultMinAngle) / defaultAngleWidth;
+ const scale = defaultAngleWidth / angleWidth;
+ const offset = -(minAngle - defaultMinAngle) / angleWidth;
shaderUniforms.cylinderMinAngleUv = minAngleUv;
- shaderUniforms.cylinderInverseAngleRangeUv =
- 1.0 / (maxAngleUv - minAngleUv);
+ shaderUniforms.cylinderScaleAngleUvToBoundsAngleUv = scale;
+ shaderUniforms.cylinderOffsetAngleUvToBoundsAngleUv = offset;
if (hasWedgeRegular) {
// Intersects a wedge for the min and max longitude.
@@ -591,108 +608,27 @@ VoxelCylinderShape.DefaultMinBounds = new Cartesian3(0.0, -1.0, -CesiumMath.PI);
*/
VoxelCylinderShape.DefaultMaxBounds = new Cartesian3(1.0, +1.0, +CesiumMath.PI);
-const scratchTestAngles = new Array(6);
+const maxTestAngles = 7;
// Preallocated arrays for all of the possible test angle counts
-const scratchPositions = [
- new Array(),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
- new Array(
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3(),
- new Cartesian3()
- ),
-];
+/**
+ * @type {Number[]}
+ * @ignore
+ */
+const scratchTestAngles = new Array(maxTestAngles);
+
+/**
+ * @type {Cartesian3[][]}
+ * @ignore
+ */
+const scratchTestPositions = new Array(maxTestAngles + 1);
+for (let i = 0; i <= maxTestAngles; i++) {
+ const positionsLength = i * 4;
+ scratchTestPositions[i] = new Array(positionsLength);
+ for (let j = 0; j < positionsLength; j++) {
+ scratchTestPositions[i][j] = new Cartesian3();
+ }
+}
/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
@@ -744,6 +680,7 @@ function getCylinderChunkObb(
return result;
}
+ // TODO: this is not always working. See OrientedBoundingBox.fromRectangle for ideas to improve.
let testAngleCount = 0;
const testAngles = scratchTestAngles;
const halfPi = CesiumMath.PI_OVER_TWO;
@@ -752,13 +689,13 @@ function getCylinderChunkObb(
testAngles[testAngleCount++] = angleEnd;
if (angleStart > angleEnd) {
- if (angleStart > 0.0 && angleEnd > 0.0) {
+ if (angleStart <= 0.0 || angleEnd >= 0.0) {
testAngles[testAngleCount++] = 0.0;
}
- if (angleStart > +halfPi && angleEnd > +halfPi) {
+ if (angleStart <= +halfPi || angleEnd >= +halfPi) {
testAngles[testAngleCount++] = +halfPi;
}
- if (angleStart > -halfPi && angleEnd > -halfPi) {
+ if (angleStart <= -halfPi || angleEnd >= -halfPi) {
testAngles[testAngleCount++] = -halfPi;
}
// It will always cross the 180th meridian
@@ -775,7 +712,14 @@ function getCylinderChunkObb(
}
}
- const positions = scratchPositions[testAngleCount];
+ const isAngleFlipped = angleEnd < angleStart;
+ const angleWidth = angleEnd - angleStart + isAngleFlipped * CesiumMath.TWO_PI;
+
+ testAngles[testAngleCount++] = CesiumMath.negativePiToPi(
+ angleStart + angleWidth * 0.5
+ );
+
+ const positions = scratchTestPositions[testAngleCount];
for (let i = 0; i < testAngleCount; i++) {
const angle = testAngles[i];
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 3144afae67c..b63aa42b04e 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -2505,7 +2505,6 @@ function debugDraw(that, frameState) {
const frameNumber = frameState.frameNumber;
const traversal = that._traversal;
const polylines = that._debugPolylines;
- const shapeVisible = that._shape.isVisible;
polylines.removeAll();
function makePolylineLineSegment(startPos, endPos, color, thickness) {
@@ -2556,9 +2555,7 @@ function debugDraw(that, frameState) {
}
}
- if (shapeVisible) {
- drawTile(traversal.rootNode);
- }
+ drawTile(traversal.rootNode);
const axisThickness = 10.0;
makePolylineLineSegment(
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 50e20e922ab..c8ea7f19be9 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -216,6 +216,8 @@ uniform float u_stepSize;
#if defined(SHAPE_ELLIPSOID)
/* Ellipsoid defines:
#define ELLIPSOID_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
+ #define ELLIPSOID_WEDGE
+ #define ELLIPSOID_WEDGE_INDEX ###
#define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
#define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
#define ELLIPSOID_CONE_BOTTOM_REGULAR ### // when there's a bottom cone
@@ -258,6 +260,8 @@ uniform float u_stepSize;
#define CYLINDER_INNER_INDEX ### // when there's an inner cylinder
#define CYLINDER_HEIGHT_NON_DEFAULT ### //
#define CYLINDER_HEIGHT_ZERO // when the height is 0
+ #define CYLINDER_ANGLE_FLIPPED //
+ #define CYLINDER_WEDGE //
#define CYLINDER_WEDGE_INDEX //
#define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
#define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
@@ -286,7 +290,8 @@ uniform float u_stepSize;
uniform float u_cylinderMinAngle;
uniform float u_cylinderMaxAngle;
uniform float u_cylinderMinAngleUv;
- uniform float u_cylinderInverseAngleRangeUv;
+ uniform float u_cylinderScaleAngleUvToBoundsAngleUv;
+ uniform float u_cylinderOffsetAngleUvToBoundsAngleUv;
#endif
#endif
@@ -538,7 +543,7 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
#endif
#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_REGULAR)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_REGULAR))
-vec2 intersectWedge(Ray ray, float minAngle, float maxAngle)
+vec2 intersectRegularWedge(Ray ray, float minAngle, float maxAngle)
{
vec2 o = ray.pos.xy;
vec2 d = ray.dir.xy;
@@ -589,6 +594,15 @@ vec2 intersectHalfSpace(Ray ray, float angle)
}
#endif
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_FLIPPED)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_FLIPPED))
+vec4 intersectFlippedWedge(Ray ray, float minAngle, float maxAngle)
+{
+ vec2 planeIntersectMin = intersectHalfSpace(ray, minAngle);
+ vec2 planeIntersectMax = intersectHalfSpace(ray, maxAngle + czm_pi);
+ return vec4(planeIntersectMin, planeIntersectMax);
+}
+#endif
+
#if defined(SHAPE_ELLIPSOID)
vec2 intersectUnitSphere(Ray ray)
{
@@ -962,13 +976,12 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
float west = u_ellipsoidRectangle.x; // [-pi,+pi]
float east = u_ellipsoidRectangle.z; // [-pi,+pi]
#if defined(ELLIPSOID_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectWedge(ray, west, east);
+ vec2 wedgeIntersect = intersectRegularWedge(ray, west, east);
setIntersectionPair(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
#elif defined(ELLIPSOID_WEDGE_FLIPPED)
- vec2 planeIntersectWest = intersectHalfSpace(ray, west);
- vec2 planeIntersectEast = intersectHalfSpace(ray, east + czm_pi);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 0, planeIntersectWest);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 1, planeIntersectEast);
+ vec4 wedgeIntersect = intersectFlippedWedge(ray, west, east);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 1, wedgeIntersect.zw);
#endif
#endif
}
@@ -1047,7 +1060,7 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
// [outerMin, innerMin, innerMax, outerMax] will bubble sort to
// [outerMin, innerMin, innerMax, outerMax] which will work correctly.
- // Note: If sortIntersections() changes its sorting function
+ // Note: If initializeIntersections() changes its sorting function
// from bubble sort to something else, this code may need to change.
setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
@@ -1059,13 +1072,12 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
#endif
#if defined(CYLINDER_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
+ vec2 wedgeIntersect = intersectRegularWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
#elif defined(CYLINDER_WEDGE_FLIPPED)
- vec2 planeIntersectMinAngle = intersectHalfSpace(outerRay, u_cylinderMinAngle);
- vec2 planeIntersectMaxAngle = intersectHalfSpace(outerRay, u_cylinderMaxAngle + czm_pi);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, planeIntersectMinAngle);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, planeIntersectMaxAngle);
+ vec4 wedgeIntersect = intersectFlippedWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, wedgeIntersect.zw);
#endif
}
#endif
@@ -1089,10 +1101,11 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
height = height * u_cylinderScaleHeightUvToBoundsHeightUv + u_cylinderOffsetHeightUvToBoundsHeightUv;
#endif
- #if defined(CYLINDER_WEDGE_REGULAR)
- angle = (angle - u_cylinderMinAngleUv) * u_cylinderInverseAngleRangeUv;
- #elif defined(CYLINDER_WEDGE_FLIPPED)
- angle = (angle - u_cylinderMinAngleUv) * u_cylinderInverseAngleRangeUv;
+ #if defined(CYLINDER_WEDGE)
+ #if defined(CYLINDER_ANGLE_FLIPPED)
+ angle += float(angle <= u_cylinderMinAngleUv);
+ #endif
+ angle = angle * u_cylinderScaleAngleUvToBoundsAngleUv + u_cylinderOffsetAngleUvToBoundsAngleUv;
#endif
return vec3(radius, height, angle);
From 4c070d8e99fb9f8790714f287eb614eb46e46b4a Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 15:14:19 -0400
Subject: [PATCH 028/679] flipped longitude fix for ellipsoid
---
Source/Scene/VoxelEllipsoidShape.js | 122 +++++++++++++++++++++-------
Source/Shaders/VoxelFS.glsl | 59 ++++++++------
2 files changed, 127 insertions(+), 54 deletions(-)
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index edd2a84d11e..141aeeeef7e 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -99,7 +99,8 @@ function VoxelEllipsoidShape() {
ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
// Wedge uniforms
ellipsoidWestUv: 0.0,
- ellipsoidInverseLongitudeRangeUv: 0.0,
+ ellipsoidScaleLongitudeUvToBoundsLongitudeUv: 0.0,
+ ellipsoidOffsetLongitudeUvToBoundsLongitudeUv: 0.0,
// Cone uniforms
ellipsoidSouthUv: 0.0,
ellipsoidInverseLatitudeRangeUv: 0.0,
@@ -115,14 +116,23 @@ function VoxelEllipsoidShape() {
*/
this.shaderDefines = {
ELLIPSOID_INTERSECTION_COUNT: undefined,
+ ELLIPSOID_WEDGE: undefined,
ELLIPSOID_WEDGE_REGULAR: undefined,
ELLIPSOID_WEDGE_FLIPPED: undefined,
+ ELLIPSOID_WEDGE_INDEX: undefined,
+ ELLIPSOID_ANGLE_FLIPPED: undefined,
+ ELLIPSOID_CONE_BOTTOM: undefined,
ELLIPSOID_CONE_BOTTOM_REGULAR: undefined,
ELLIPSOID_CONE_BOTTOM_FLIPPED: undefined,
+ ELLIPSOID_CONE_BOTTOM_INDEX: undefined,
+ ELLIPSOID_CONE_TOP: undefined,
ELLIPSOID_CONE_TOP_REGULAR: undefined,
ELLIPSOID_CONE_TOP_FLIPPED: undefined,
+ ELLIPSOID_CONE_TOP_INDEX: undefined,
ELLIPSOID_OUTER: undefined,
+ ELLIPSOID_OUTER_INDEX: undefined,
ELLIPSOID_INNER: undefined,
+ ELLIPSOID_INNER_INDEX: undefined,
};
}
@@ -150,29 +160,33 @@ VoxelEllipsoidShape.prototype.update = function (
Check.typeOf.object("maxBounds", maxBounds);
//>>includeEnd('debug');
- const defaultMinBounds = VoxelEllipsoidShape.DefaultMinBounds;
- const defaultMaxBounds = VoxelEllipsoidShape.DefaultMaxBounds;
+ const defaultMinLongitude = VoxelEllipsoidShape.DefaultMinBounds.x;
+ const defaultMaxLongitude = VoxelEllipsoidShape.DefaultMaxBounds.x;
+ const defaultLongitudeLength = defaultMaxLongitude - defaultMinLongitude;
+ const defaultMinLatitude = VoxelEllipsoidShape.DefaultMinBounds.y;
+ const defaultMaxLatitude = VoxelEllipsoidShape.DefaultMaxBounds.y;
+ const defaultLatitudeLength = defaultMaxLatitude - defaultMinLatitude;
// Clamp the longitude / latitude to the valid range
const west = CesiumMath.clamp(
minBounds.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
+ defaultMinLongitude,
+ defaultMaxLongitude
);
const east = CesiumMath.clamp(
maxBounds.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
+ defaultMinLongitude,
+ defaultMaxLongitude
);
const south = CesiumMath.clamp(
minBounds.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
+ defaultMinLatitude,
+ defaultMaxLatitude
);
const north = CesiumMath.clamp(
maxBounds.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
+ defaultMinLatitude,
+ defaultMaxLatitude
);
// Don't let the height go below the center of the ellipsoid.
@@ -277,27 +291,40 @@ VoxelEllipsoidShape.prototype.update = function (
shaderUniforms.ellipsoidInverseRadiiSquaredUv
);
+ const isAngleFlipped = east < west;
const rectangleWidth = Rectangle.computeWidth(this._rectangle);
const hasInnerEllipsoid = !Cartesian3.equals(innerExtent, Cartesian3.ZERO);
+
const hasWedgeRegular =
rectangleWidth >= CesiumMath.PI &&
CesiumMath.lessThan(rectangleWidth, CesiumMath.TWO_PI, absEpsilon);
const hasWedgeFlipped = rectangleWidth < CesiumMath.PI;
+ const hasWedge = hasWedgeRegular || hasWedgeFlipped;
+
const hasTopConeRegular = north >= 0.0 && north < +CesiumMath.PI_OVER_TWO;
const hasTopConeFlipped = north < 0.0;
+ const hasTopCone = hasTopConeRegular || hasTopConeFlipped;
+
const hasBottomConeRegular = south <= 0.0 && south > -CesiumMath.PI_OVER_TWO;
const hasBottomConeFlipped = south > 0.0;
+ const hasBottomCone = hasBottomConeRegular || hasBottomConeFlipped;
+
+ if (isAngleFlipped) {
+ shaderDefines["ELLIPSOID_ANGLE_FLIPPED"] = true;
+ }
// Keep track of how many intersections there are going to be.
let intersectionCount = 0;
// Intersects an outer ellipsoid for the max height.
- shaderDefines["ELLIPSOID_OUTER"] = intersectionCount;
+ shaderDefines["ELLIPSOID_OUTER"] = true;
+ shaderDefines["ELLIPSOID_OUTER_INDEX"] = intersectionCount;
intersectionCount += 1;
// Intersects an inner ellipsoid for the min height.
if (hasInnerEllipsoid) {
- shaderDefines["ELLIPSOID_INNER"] = intersectionCount;
+ shaderDefines["ELLIPSOID_INNER"] = true;
+ shaderDefines["ELLIPSOID_INNER_INDEX"] = intersectionCount;
intersectionCount += 1;
// The percent of space that is between the inner and outer ellipsoid.
@@ -317,30 +344,63 @@ VoxelEllipsoidShape.prototype.update = function (
}
// Intersects a wedge for the min and max longitude.
- if (hasWedgeRegular) {
- shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = intersectionCount;
- intersectionCount += 1;
- } else if (hasWedgeFlipped) {
- shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = intersectionCount;
- intersectionCount += 2;
+ if (hasWedge) {
+ shaderDefines["ELLIPSOID_WEDGE"] = true;
+ shaderDefines["ELLIPSOID_WEDGE_INDEX"] = intersectionCount;
+
+ if (hasWedgeRegular) {
+ shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = true;
+ intersectionCount += 1;
+ } else if (hasWedgeFlipped) {
+ shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = true;
+ intersectionCount += 2;
+ }
+
+ // delerp(longitudeUv, minLongitudeUv, maxLongitudeUv)
+ // (longitudeUv - minLongitudeUv) / (maxLongitudeUv - minLongitudeUv)
+ // longitudeUv / (maxLongitudeUv - minLongitudeUv) - minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
+ // scale = 1.0 / (maxLongitudeUv - minLongitudeUv)
+ // scale = 1.0 / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
+ // scale = 2.0 * pi / (maxLongitude - minLongitude)
+ // offset = -minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
+ // offset = -((minLongitude - pi) / (2.0 * pi)) / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
+ // offset = -(minLongitude - pi) / (maxLongitude - minLongitude)
+
+ const westUv = (west - defaultMinLongitude) / defaultLongitudeLength;
+ const scale = defaultLongitudeLength / rectangleWidth;
+ const offset = -(west - defaultMinLongitude) / rectangleWidth;
+
+ shaderUniforms.ellipsoidWestUv = westUv;
+ shaderUniforms.ellipsoidScaleLongitudeUvToBoundsLongitudeUv = scale;
+ shaderUniforms.ellipsoidOffsetLongitudeUvToBoundsLongitudeUv = offset;
}
// Intersects a cone for min latitude
- if (hasBottomConeRegular) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = intersectionCount;
- intersectionCount += 1;
- } else if (hasBottomConeFlipped) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = intersectionCount;
- intersectionCount += 2;
+ if (hasBottomCone) {
+ shaderDefines["ELLIPSOID_CONE_BOTTOM"] = true;
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_INDEX"] = intersectionCount;
+
+ if (hasBottomConeRegular) {
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = true;
+ intersectionCount += 1;
+ } else if (hasBottomConeFlipped) {
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = true;
+ intersectionCount += 2;
+ }
}
// Intersects a cone for max latitude
- if (hasTopConeRegular) {
- shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = intersectionCount;
- intersectionCount += 1;
- } else if (hasTopConeFlipped) {
- shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = intersectionCount;
- intersectionCount += 2;
+ if (hasTopCone) {
+ shaderDefines["ELLIPSOID_CONE_TOP"] = true;
+ shaderDefines["ELLIPSOID_CONE_TOP_INDEX"] = intersectionCount;
+
+ if (hasTopConeRegular) {
+ shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = true;
+ intersectionCount += 1;
+ } else if (hasTopConeFlipped) {
+ shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = true;
+ intersectionCount += 2;
+ }
}
shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = intersectionCount;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index c8ea7f19be9..43999c11793 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -217,29 +217,37 @@ uniform float u_stepSize;
/* Ellipsoid defines:
#define ELLIPSOID_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
#define ELLIPSOID_WEDGE
- #define ELLIPSOID_WEDGE_INDEX ###
#define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
#define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
+ #define ELLIPSOID_WEDGE_INDEX ###
+ #define ELLIPSOID_ANGLE_FLIPPED //
+ #define ELLIPSOID_CONE_BOTTOM
#define ELLIPSOID_CONE_BOTTOM_REGULAR ### // when there's a bottom cone
#define ELLIPSOID_CONE_BOTTOM_FLIPPED ### // when cone shape has two intersection intervals
+ #define ELLIPSOID_CONE_BOTTOM_INDEX ###
+ #define ELLIPSOID_CONE_TOP
#define ELLIPSOID_CONE_TOP_REGULAR ### // when there's a top cone
#define ELLIPSOID_CONE_TOP_FLIPPED ### // when cone shape has two intersection intervals
+ #define ELLIPSOID_CONE_TOP_INDEX ###
#define ELLIPSOID_OUTER ### // outer ellipsoid - always defined
+ #define ELLIPSOID_OUTER_INDEX ###
#define ELLIPSOID_INNER ### // when there's an inner ellipsoid
+ #define ELLIPSOID_INNER_INDEX ###
*/
// Ellipsoid uniforms
uniform vec3 u_ellipsoidRadiiUv; // [0,1]
uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
- #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED) || defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ #if defined(ELLIPSOID_WEDGE) || defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
#endif
- #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
+ #if defined(ELLIPSOID_WEDGE)
uniform float u_ellipsoidWestUv;
- uniform float u_ellipsoidInverseLongitudeRangeUv;
+ uniform float u_ellipsoidScaleLongitudeUvToBoundsLongitudeUv;
+ uniform float u_ellipsoidOffsetLongitudeUvToBoundsLongitudeUv;
#endif
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ #if defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
uniform float u_ellipsoidSouthUv;
uniform float u_ellipsoidInverseLatitudeRangeUv;
#endif
@@ -652,7 +660,7 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP))
vec2 intersectDoubleEndedCone(Ray ray, float latitude)
{
vec3 o = ray.pos;
@@ -926,7 +934,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
// Outer ellipsoid
vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- setIntersectionPair(ix, ELLIPSOID_OUTER, outerIntersect);
+ setIntersectionPair(ix, ELLIPSOID_OUTER_INDEX, outerIntersect);
// Exit early if the outer ellipsoid was missed.
if (outerIntersect.x == NO_HIT) {
@@ -937,11 +945,11 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
#if defined(ELLIPSOID_INNER)
Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- setIntersectionPair(ix, ELLIPSOID_INNER, innerIntersect);
+ setIntersectionPair(ix, ELLIPSOID_INNER_INDEX, innerIntersect);
#endif
// Bottom cone
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
+ #if defined(ELLIPSOID_CONE_BOTTOM)
// Flip the inputs because the intersection function expects a cone growing towards +Z.
float flippedSouth = -u_ellipsoidRectangle.y; // [-halfPi,+halfPi]
Ray flippedRay = ray;
@@ -950,38 +958,38 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
#if defined(ELLIPSOID_CONE_BOTTOM_REGULAR)
vec2 bottomConeIntersection = intersectRegularCone(flippedRay, flippedSouth);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_REGULAR, bottomConeIntersection);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX, bottomConeIntersection);
#elif defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
vec4 bottomConeIntersection = intersectFlippedCone(flippedRay, flippedSouth);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 0, bottomConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_FLIPPED + 1, bottomConeIntersection.zw);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 0, bottomConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 1, bottomConeIntersection.zw);
#endif
#endif
// Top cone
- #if defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ #if defined(ELLIPSOID_CONE_TOP)
float north = u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
#if defined(ELLIPSOID_CONE_TOP_REGULAR)
vec2 topConeIntersection = intersectRegularCone(ray, north);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_REGULAR, topConeIntersection);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX, topConeIntersection);
#elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
vec4 topConeIntersection = intersectFlippedCone(ray, north);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 0, topConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_FLIPPED + 1, topConeIntersection.zw);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 0, topConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 1, topConeIntersection.zw);
#endif
#endif
// Wedge
- #if defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED)
+ #if defined(ELLIPSOID_WEDGE)
float west = u_ellipsoidRectangle.x; // [-pi,+pi]
float east = u_ellipsoidRectangle.z; // [-pi,+pi]
#if defined(ELLIPSOID_WEDGE_REGULAR)
vec2 wedgeIntersect = intersectRegularWedge(ray, west, east);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_REGULAR, wedgeIntersect);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX, wedgeIntersect);
#elif defined(ELLIPSOID_WEDGE_FLIPPED)
vec4 wedgeIntersect = intersectFlippedWedge(ray, west, east);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_FLIPPED + 1, wedgeIntersect.zw);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 1, wedgeIntersect.zw);
#endif
#endif
}
@@ -1002,11 +1010,14 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
float longitude = (atan(geodeticSurfaceNormal.y, geodeticSurfaceNormal.x) + czm_pi) / czm_twoPi; // 5
float latitude = (asin(geodeticSurfaceNormal.z) + czm_piOverTwo) / czm_pi; // 6
- #if (defined(ELLIPSOID_WEDGE_REGULAR) || defined(ELLIPSOID_WEDGE_FLIPPED))
- longitude = (longitude - u_ellipsoidWestUv) * u_ellipsoidInverseLongitudeRangeUv;
+ #if defined(ELLIPSOID_WEDGE)
+ #if defined(ELLIPSOID_ANGLE_FLIPPED)
+ longitude += float(longitude <= u_ellipsoidWestUv);
+ #endif
+ longitude = longitude * u_ellipsoidScaleLongitudeUvToBoundsLongitudeUv + u_ellipsoidOffsetLongitudeUvToBoundsLongitudeUv;
#endif
- #if (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+ #if (defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP))
latitude = (latitude - u_ellipsoidSouthUv) * u_ellipsoidInverseLatitudeRangeUv;
#endif
@@ -1492,9 +1503,11 @@ void main()
discard;
}
+
float currT = entryExitT.x;
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
+ gl_FragColor = vec4(transformFromUvToShapeSpace(positionUv).xxx, 1.0); return;
vec4 colorAccum = vec4(0.0);
From 22f3f663ebe25985e36db21be10e4795227995c2 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 16:21:39 -0400
Subject: [PATCH 029/679] fixed some cone problems for ellipsoid shape
---
Source/Scene/VoxelEllipsoidShape.js | 33 ++++++++++---
Source/Shaders/VoxelFS.glsl | 77 +++++++++++++++--------------
2 files changed, 65 insertions(+), 45 deletions(-)
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 141aeeeef7e..33a784be979 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -102,12 +102,12 @@ function VoxelEllipsoidShape() {
ellipsoidScaleLongitudeUvToBoundsLongitudeUv: 0.0,
ellipsoidOffsetLongitudeUvToBoundsLongitudeUv: 0.0,
// Cone uniforms
- ellipsoidSouthUv: 0.0,
- ellipsoidInverseLatitudeRangeUv: 0.0,
+ ellipsoidScaleLatitudeUvToBoundsLatitudeUv: 0.0,
+ ellipsoidOffsetLatitudeUvToBoundsLatitudeUv: 0.0,
// Inner ellipsoid uniforms
ellipsoidInverseHeightDifferenceUv: 0.0,
ellipsoidInverseInnerScaleUv: 0.0,
- ellipsoidInnerRadiiUv: new Cartesian3(),
+ ellipseInnerRadiiUv: new Cartesian2(),
};
/**
@@ -293,6 +293,7 @@ VoxelEllipsoidShape.prototype.update = function (
const isAngleFlipped = east < west;
const rectangleWidth = Rectangle.computeWidth(this._rectangle);
+ const rectangleHeight = Rectangle.computeHeight(this._rectangle);
const hasInnerEllipsoid = !Cartesian3.equals(innerExtent, Cartesian3.ZERO);
const hasWedgeRegular =
@@ -336,10 +337,10 @@ VoxelEllipsoidShape.prototype.update = function (
shaderUniforms.ellipsoidInverseInnerScaleUv = 1.0 / innerScale;
// The inner ellipsoid radii scaled to [0,innerScale]. The max inner ellipsoid radius will equal innerScale and others will be less.
- shaderUniforms.ellipsoidInnerRadiiUv = Cartesian3.multiplyByScalar(
- shaderUniforms.ellipsoidRadiiUv,
- innerScale,
- shaderUniforms.ellipsoidInnerRadiiUv
+ shaderUniforms.ellipseInnerRadiiUv = Cartesian2.fromElements(
+ shaderUniforms.ellipsoidRadiiUv.x * innerScale,
+ shaderUniforms.ellipsoidRadiiUv.z * innerScale,
+ shaderUniforms.ellipseInnerRadiiUv
);
}
@@ -375,6 +376,24 @@ VoxelEllipsoidShape.prototype.update = function (
shaderUniforms.ellipsoidOffsetLongitudeUvToBoundsLongitudeUv = offset;
}
+ if (hasBottomCone || hasTopCone) {
+ // delerp(latitudeUv, minLatitudeUv, maxLatitudeUv)
+ // (latitudeUv - minLatitudeUv) / (maxLatitudeUv - minLatitudeUv)
+ // latitudeUv / (maxLatitudeUv - minLatitudeUv) - minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
+ // scale = 1.0 / (maxLatitudeUv - minLatitudeUv)
+ // scale = 1.0 / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
+ // scale = 2.0 * pi / (maxLatitude - minLatitude)
+ // offset = -minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
+ // offset = -((minLatitude - pi) / (2.0 * pi)) / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
+ // offset = -(minLatitude - pi) / (maxLatitude - minLatitude)
+
+ const scale = defaultLatitudeLength / rectangleHeight;
+ const offset = -(south - defaultMinLatitude) / rectangleHeight;
+
+ shaderUniforms.ellipsoidScaleLatitudeUvToBoundsLatitudeUv = scale;
+ shaderUniforms.ellipsoidOffsetLatitudeUvToBoundsLatitudeUv = offset;
+ }
+
// Intersects a cone for min latitude
if (hasBottomCone) {
shaderDefines["ELLIPSOID_CONE_BOTTOM"] = true;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 43999c11793..0b93956f7bc 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -242,19 +242,21 @@ uniform float u_stepSize;
#if defined(ELLIPSOID_WEDGE) || defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
#endif
- #if defined(ELLIPSOID_WEDGE)
+ #if defined(ELLIPSOID_WEDGE) && defined(ELLIPSOID_ANGLE_FLIPPED)
uniform float u_ellipsoidWestUv;
+ #endif
+ #if defined(ELLIPSOID_WEDGE)
uniform float u_ellipsoidScaleLongitudeUvToBoundsLongitudeUv;
uniform float u_ellipsoidOffsetLongitudeUvToBoundsLongitudeUv;
#endif
#if defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
- uniform float u_ellipsoidSouthUv;
- uniform float u_ellipsoidInverseLatitudeRangeUv;
+ uniform float u_ellipsoidScaleLatitudeUvToBoundsLatitudeUv;
+ uniform float u_ellipsoidOffsetLatitudeUvToBoundsLatitudeUv;
#endif
#if defined(ELLIPSOID_INNER)
uniform float u_ellipsoidInverseHeightDifferenceUv;
uniform float u_ellipsoidInverseInnerScaleUv;
- uniform vec3 u_ellipsoidInnerRadiiUv; // [0,1]
+ uniform vec2 u_ellipseInnerRadiiUv; // [0,1]
#endif
#endif
@@ -692,19 +694,18 @@ vec4 intersectFlippedCone(Ray ray, float latitude) {
vec2 intersect = intersectDoubleEndedCone(ray, latitude);
if (intersect.x == NO_HIT) {
- return vec4(NO_HIT);
+ return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
}
float tmin = intersect.x;
float tmax = intersect.y;
- float h1 = o.z + tmin * d.z;
- float h2 = o.z + tmax * d.z;
+ float zmin = o.z + tmin * d.z;
+ float zmax = o.z + tmax * d.z;
// One interval
- if (h1 < 0.0 && h2 < 0.0) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
- else if (h1 < 0.0) return vec4(-INF_HIT, tmax, NO_HIT, NO_HIT);
- else if (h2 < 0.0) return vec4(tmin, +INF_HIT, NO_HIT, NO_HIT);
- else if (tmin < 0.0) return vec4(tmax, +INF_HIT, NO_HIT, NO_HIT);
+ if (zmin < 0.0 && zmax < 0.0) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
+ else if (zmin < 0.0) return vec4(-INF_HIT, tmax, NO_HIT, NO_HIT);
+ else if (zmax < 0.0) return vec4(tmin, +INF_HIT, NO_HIT, NO_HIT);
// Two intervals
else return vec4(-INF_HIT, tmin, tmax, +INF_HIT);
}
@@ -722,12 +723,12 @@ vec2 intersectRegularCone(Ray ray, float latitude) {
float tmin = intersect.x;
float tmax = intersect.y;
- float h1 = o.z + tmin * d.z;
- float h2 = o.z + tmax * d.z;
+ float zmin = o.z + tmin * d.z;
+ float zmax = o.z + tmax * d.z;
- if (h1 < 0.0 && h2 < 0.0) return vec2(NO_HIT);
- else if (h1 < 0.0) return vec2(tmax, +INF_HIT);
- else if (h2 < 0.0) return vec2(-INF_HIT, tmin);
+ if (zmin < 0.0 && zmax < 0.0) return vec2(NO_HIT);
+ else if (zmin < 0.0) return vec2(tmax, +INF_HIT);
+ else if (zmax < 0.0) return vec2(-INF_HIT, tmin);
else return vec2(tmin, tmax);
}
#endif
@@ -948,19 +949,22 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
setIntersectionPair(ix, ELLIPSOID_INNER_INDEX, innerIntersect);
#endif
- // Bottom cone
- #if defined(ELLIPSOID_CONE_BOTTOM)
- // Flip the inputs because the intersection function expects a cone growing towards +Z.
- float flippedSouth = -u_ellipsoidRectangle.y; // [-halfPi,+halfPi]
+ // Flip the ray because the intersection function expects a cone growing towards +Z.
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
Ray flippedRay = ray;
flippedRay.dir.z *= -1.0;
flippedRay.pos.z *= -1.0;
-
+ #endif
+
+ // Bottom cone
+ #if defined(ELLIPSOID_CONE_BOTTOM)
#if defined(ELLIPSOID_CONE_BOTTOM_REGULAR)
+ float flippedSouth = -u_ellipsoidRectangle.y; // [-halfPi,+halfPi]
vec2 bottomConeIntersection = intersectRegularCone(flippedRay, flippedSouth);
setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX, bottomConeIntersection);
#elif defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
- vec4 bottomConeIntersection = intersectFlippedCone(flippedRay, flippedSouth);
+ float south = u_ellipsoidRectangle.y;
+ vec4 bottomConeIntersection = intersectFlippedCone(ray, south);
setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 0, bottomConeIntersection.xy);
setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 1, bottomConeIntersection.zw);
#endif
@@ -968,12 +972,13 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
// Top cone
#if defined(ELLIPSOID_CONE_TOP)
- float north = u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
#if defined(ELLIPSOID_CONE_TOP_REGULAR)
+ float north = u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
vec2 topConeIntersection = intersectRegularCone(ray, north);
setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX, topConeIntersection);
#elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
- vec4 topConeIntersection = intersectFlippedCone(ray, north);
+ float flippedNorth = -u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
+ vec4 topConeIntersection = intersectFlippedCone(flippedRay, flippedNorth);
setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 0, topConeIntersection.xy);
setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 1, topConeIntersection.zw);
#endif
@@ -997,18 +1002,15 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
- // 1) Convert positionUv [0,1] to local space [-1,+1] to normalized cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height). A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
- // 2) Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84). This is an optimization so that math can be done with ellipses instead of ellipsoids.
- // 3) Compute height from inner ellipse.
- // 4) Compute geodetic surface normal.
- // 5) Compute longitude from geodetic surface normal.
- // 6) Compute latitude from geodetic surface normal.
- vec3 pos3D = (positionUv * 2.0 - 1.0) * u_ellipsoidRadiiUv; // 1
- vec2 pos2D = vec2(length(pos3D.xy), pos3D.z); // 2
- float height = ellipseDistanceIterative(pos2D, u_ellipsoidInnerRadiiUv.xz); // 3
- vec3 geodeticSurfaceNormal = normalize(pos3D * u_ellipsoidInverseRadiiSquaredUv); // 4
- float longitude = (atan(geodeticSurfaceNormal.y, geodeticSurfaceNormal.x) + czm_pi) / czm_twoPi; // 5
- float latitude = (asin(geodeticSurfaceNormal.z) + czm_piOverTwo) / czm_pi; // 6
+ // Convert positionUv [0,1] to local space [-1,+1] to "normalized" cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height). A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
+ // Then convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84). This is an optimization so that math can be done with ellipses instead of ellipsoids.
+ vec3 posEllipsoid = (positionUv * 2.0 - 1.0) * u_ellipsoidRadiiUv;
+ vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
+
+ vec3 geodeticSurfaceNormal = normalize(posEllipsoid * u_ellipsoidInverseRadiiSquaredUv);
+ float longitude = (atan(geodeticSurfaceNormal.y, geodeticSurfaceNormal.x) + czm_pi) / czm_twoPi;
+ float latitude = (asin(geodeticSurfaceNormal.z) + czm_piOverTwo) / czm_pi;
+ float height = ellipseDistanceIterative(posEllipse, u_ellipseInnerRadiiUv);
#if defined(ELLIPSOID_WEDGE)
#if defined(ELLIPSOID_ANGLE_FLIPPED)
@@ -1018,7 +1020,7 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
#endif
#if (defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP))
- latitude = (latitude - u_ellipsoidSouthUv) * u_ellipsoidInverseLatitudeRangeUv;
+ latitude = latitude * u_ellipsoidScaleLatitudeUvToBoundsLatitudeUv + u_ellipsoidOffsetLatitudeUvToBoundsLatitudeUv;
#endif
#if (defined(ELLIPSOID_INNER))
@@ -1507,7 +1509,6 @@ void main()
float currT = entryExitT.x;
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
- gl_FragColor = vec4(transformFromUvToShapeSpace(positionUv).xxx, 1.0); return;
vec4 colorAccum = vec4(0.0);
From 1e7dad60d7078a70424a2eda90ec57a6c4a43fae Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 17:04:01 -0400
Subject: [PATCH 030/679] added fast path for when ellipsoid is sphere
---
Source/Scene/VoxelEllipsoidShape.js | 7 +++
Source/Shaders/VoxelFS.glsl | 47 ++++++++++++++-----
.../Widgets/VoxelInspector/VoxelInspector.js | 3 +-
3 files changed, 43 insertions(+), 14 deletions(-)
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 33a784be979..66f1aa260e3 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -133,6 +133,7 @@ function VoxelEllipsoidShape() {
ELLIPSOID_OUTER_INDEX: undefined,
ELLIPSOID_INNER: undefined,
ELLIPSOID_INNER_INDEX: undefined,
+ ELLIPSOID_IS_SPHERE: undefined,
};
}
@@ -310,10 +311,16 @@ VoxelEllipsoidShape.prototype.update = function (
const hasBottomConeFlipped = south > 0.0;
const hasBottomCone = hasBottomConeRegular || hasBottomConeFlipped;
+ const isSphere = radii.x === radii.y && radii.y === radii.z;
+
if (isAngleFlipped) {
shaderDefines["ELLIPSOID_ANGLE_FLIPPED"] = true;
}
+ if (isSphere) {
+ shaderDefines["ELLIPSOID_IS_SPHERE"] = true;
+ }
+
// Keep track of how many intersections there are going to be.
let intersectionCount = 0;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 0b93956f7bc..b031eaf2ed5 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -233,6 +233,7 @@ uniform float u_stepSize;
#define ELLIPSOID_OUTER_INDEX ###
#define ELLIPSOID_INNER ### // when there's an inner ellipsoid
#define ELLIPSOID_INNER_INDEX ###
+ #define ELLIPSOID_IS_SPHERE
*/
// Ellipsoid uniforms
@@ -1002,16 +1003,19 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
- // Convert positionUv [0,1] to local space [-1,+1] to "normalized" cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height). A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
- // Then convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84). This is an optimization so that math can be done with ellipses instead of ellipsoids.
- vec3 posEllipsoid = (positionUv * 2.0 - 1.0) * u_ellipsoidRadiiUv;
- vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
-
- vec3 geodeticSurfaceNormal = normalize(posEllipsoid * u_ellipsoidInverseRadiiSquaredUv);
- float longitude = (atan(geodeticSurfaceNormal.y, geodeticSurfaceNormal.x) + czm_pi) / czm_twoPi;
- float latitude = (asin(geodeticSurfaceNormal.z) + czm_piOverTwo) / czm_pi;
- float height = ellipseDistanceIterative(posEllipse, u_ellipseInnerRadiiUv);
-
+ // Convert positionUv [0,1] to local space [-1,+1] to "normalized" cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height).
+ // A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
+ vec3 positionLocal = positionUv * 2.0 - 1.0;
+ #if defined(ELLIPSOID_IS_SPHERE)
+ vec3 posEllipsoid = positionLocal * u_ellipsoidRadiiUv.x;
+ vec3 normal = normalize(posEllipsoid);
+ #else
+ vec3 posEllipsoid = positionLocal * u_ellipsoidRadiiUv;
+ vec3 normal = normalize(posEllipsoid * u_ellipsoidInverseRadiiSquaredUv); // geodetic surface normal
+ #endif
+
+ // Compute longitude
+ float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
#if defined(ELLIPSOID_WEDGE)
#if defined(ELLIPSOID_ANGLE_FLIPPED)
longitude += float(longitude <= u_ellipsoidWestUv);
@@ -1019,12 +1023,29 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
longitude = longitude * u_ellipsoidScaleLongitudeUvToBoundsLongitudeUv + u_ellipsoidOffsetLongitudeUvToBoundsLongitudeUv;
#endif
+ // Compute latitude
+ float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi;
#if (defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP))
latitude = latitude * u_ellipsoidScaleLatitudeUvToBoundsLatitudeUv + u_ellipsoidOffsetLatitudeUvToBoundsLatitudeUv;
#endif
-
- #if (defined(ELLIPSOID_INNER))
- height *= u_ellipsoidInverseHeightDifferenceUv;
+
+ // Compute height
+ #if defined(ELLIPSOID_IS_SPHERE)
+ #if defined(ELLIPSOID_INNER)
+ float height = (length(posEllipsoid) - u_ellipseInnerRadiiUv.x) * u_ellipsoidInverseHeightDifferenceUv;
+ #else
+ float height = length(posEllipsoid);
+ #endif
+ #else
+ #if defined(ELLIPSOID_INNER)
+ // Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84).
+ // This is an optimization so that math can be done with ellipses instead of ellipsoids.
+ vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
+ float height = ellipseDistanceIterative(posEllipse, u_ellipseInnerRadiiUv) * u_ellipsoidInverseHeightDifferenceUv;
+ #else
+ // TODO: this is probably not correct
+ float height = length(posEllipsoid);
+ #endif
#endif
return vec3(longitude, latitude, height);
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
index a72f841a50a..7c39a62fbec 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspector.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -4,6 +4,7 @@ import Check from "../../Core/Check.js";
import defaultValue from "../../Core/defaultValue.js";
import defined from "../../Core/defined.js";
import destroyObject from "../../Core/destroyObject.js";
+import Ellipsoid from "../../Core/Ellipsoid.js";
import knockout from "../../ThirdParty/knockout.js";
import getElement from "../getElement.js";
import InspectorShared from "../InspectorShared.js";
@@ -134,7 +135,7 @@ function VoxelInspector(container, scene) {
const ellipsoidMinBounds = Cartesian3.fromElements(
VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID).x,
VoxelShapeType.getMinBounds(VoxelShapeType.ELLIPSOID).y,
- -6356752.3142451793, // The deepest height for WGS84
+ -Ellipsoid.WGS84.maximumRadius,
new Cartesian3()
);
const ellipsoidMaxBounds = Cartesian3.fromElements(
From b7f48d5ed9373224d385a7d52259d551dbd17708 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 17:33:42 -0400
Subject: [PATCH 031/679] sort of handling thin ellipsoid
---
Source/Scene/VoxelEllipsoidShape.js | 23 +++--
Source/Shaders/VoxelFS.glsl | 150 +++++++++++++++++-----------
2 files changed, 107 insertions(+), 66 deletions(-)
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 66f1aa260e3..865bf65538c 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -120,7 +120,7 @@ function VoxelEllipsoidShape() {
ELLIPSOID_WEDGE_REGULAR: undefined,
ELLIPSOID_WEDGE_FLIPPED: undefined,
ELLIPSOID_WEDGE_INDEX: undefined,
- ELLIPSOID_ANGLE_FLIPPED: undefined,
+ ELLIPSOID_WEDGE_ANGLE_FLIPPED: undefined,
ELLIPSOID_CONE_BOTTOM: undefined,
ELLIPSOID_CONE_BOTTOM_REGULAR: undefined,
ELLIPSOID_CONE_BOTTOM_FLIPPED: undefined,
@@ -132,6 +132,7 @@ function VoxelEllipsoidShape() {
ELLIPSOID_OUTER: undefined,
ELLIPSOID_OUTER_INDEX: undefined,
ELLIPSOID_INNER: undefined,
+ ELLIPSOID_INNER_OUTER_EQUAL: undefined,
ELLIPSOID_INNER_INDEX: undefined,
ELLIPSOID_IS_SPHERE: undefined,
};
@@ -313,14 +314,6 @@ VoxelEllipsoidShape.prototype.update = function (
const isSphere = radii.x === radii.y && radii.y === radii.z;
- if (isAngleFlipped) {
- shaderDefines["ELLIPSOID_ANGLE_FLIPPED"] = true;
- }
-
- if (isSphere) {
- shaderDefines["ELLIPSOID_IS_SPHERE"] = true;
- }
-
// Keep track of how many intersections there are going to be.
let intersectionCount = 0;
@@ -351,6 +344,14 @@ VoxelEllipsoidShape.prototype.update = function (
);
}
+ if (isSphere) {
+ shaderDefines["ELLIPSOID_IS_SPHERE"] = true;
+ }
+
+ if (minHeight === maxHeight) {
+ shaderDefines["ELLIPSOID_INNER_OUTER_EQUAL"] = true;
+ }
+
// Intersects a wedge for the min and max longitude.
if (hasWedge) {
shaderDefines["ELLIPSOID_WEDGE"] = true;
@@ -383,6 +384,10 @@ VoxelEllipsoidShape.prototype.update = function (
shaderUniforms.ellipsoidOffsetLongitudeUvToBoundsLongitudeUv = offset;
}
+ if (isAngleFlipped) {
+ shaderDefines["ELLIPSOID_WEDGE_ANGLE_FLIPPED"] = true;
+ }
+
if (hasBottomCone || hasTopCone) {
// delerp(latitudeUv, minLatitudeUv, maxLatitudeUv)
// (latitudeUv - minLatitudeUv) / (maxLatitudeUv - minLatitudeUv)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index b031eaf2ed5..d46cd7ae817 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -220,7 +220,7 @@ uniform float u_stepSize;
#define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
#define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
#define ELLIPSOID_WEDGE_INDEX ###
- #define ELLIPSOID_ANGLE_FLIPPED //
+ #define ELLIPSOID_WEDGE_ANGLE_FLIPPED //
#define ELLIPSOID_CONE_BOTTOM
#define ELLIPSOID_CONE_BOTTOM_REGULAR ### // when there's a bottom cone
#define ELLIPSOID_CONE_BOTTOM_FLIPPED ### // when cone shape has two intersection intervals
@@ -233,6 +233,7 @@ uniform float u_stepSize;
#define ELLIPSOID_OUTER_INDEX ###
#define ELLIPSOID_INNER ### // when there's an inner ellipsoid
#define ELLIPSOID_INNER_INDEX ###
+ #define ELLIPSOID_INNER_OUTER_EQUAL
#define ELLIPSOID_IS_SPHERE
*/
@@ -243,7 +244,7 @@ uniform float u_stepSize;
#if defined(ELLIPSOID_WEDGE) || defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
#endif
- #if defined(ELLIPSOID_WEDGE) && defined(ELLIPSOID_ANGLE_FLIPPED)
+ #if defined(ELLIPSOID_WEDGE) && defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
uniform float u_ellipsoidWestUv;
#endif
#if defined(ELLIPSOID_WEDGE)
@@ -254,7 +255,7 @@ uniform float u_stepSize;
uniform float u_ellipsoidScaleLatitudeUvToBoundsLatitudeUv;
uniform float u_ellipsoidOffsetLatitudeUvToBoundsLatitudeUv;
#endif
- #if defined(ELLIPSOID_INNER)
+ #if defined(ELLIPSOID_INNER) && !defined(ELLIPSOID_INNER_OUTER_EQUAL)
uniform float u_ellipsoidInverseHeightDifferenceUv;
uniform float u_ellipsoidInverseInnerScaleUv;
uniform vec2 u_ellipseInnerRadiiUv; // [0,1]
@@ -289,7 +290,7 @@ uniform float u_stepSize;
uniform float u_cylinderOffsetRadiusUvToBoundsRadiusUv;
#endif
- #if defined(CYLINDER_INNER)
+ #if defined(CYLINDER_INNER) && !defined(CYLINDER_INNER_OUTER_EQUAL)
uniform vec3 u_cylinderScaleUvToInnerBounds;
uniform vec3 u_cylinderTranslateUvToInnerBounds;
#endif
@@ -945,9 +946,32 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
// Inner ellipsoid
#if defined(ELLIPSOID_INNER)
- Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
- vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- setIntersectionPair(ix, ELLIPSOID_INNER_INDEX, innerIntersect);
+ #if defined(ELLIPSOID_INNER_OUTER_EQUAL)
+ // When the ellipsoid is perfectly thin it's necessary to sandwich the
+ // inner ellipsoid intersection inside the outer ellipsoid intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the ellipsoid to be invisible because it will think the ray
+ // is still inside the inner (negative) ellipsoid after exiting the
+ // outer (positive) ellipsoid.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If initializeIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #else
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ setIntersectionPair(ix, ELLIPSOID_INNER_INDEX, innerIntersect);
+ #endif
#endif
// Flip the ray because the intersection function expects a cone growing towards +Z.
@@ -1003,6 +1027,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
#if defined(SHAPE_ELLIPSOID)
vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
+ // Compute position and normal.
// Convert positionUv [0,1] to local space [-1,+1] to "normalized" cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height).
// A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
vec3 positionLocal = positionUv * 2.0 - 1.0;
@@ -1017,7 +1042,7 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
// Compute longitude
float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
#if defined(ELLIPSOID_WEDGE)
- #if defined(ELLIPSOID_ANGLE_FLIPPED)
+ #if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
longitude += float(longitude <= u_ellipsoidWestUv);
#endif
longitude = longitude * u_ellipsoidScaleLongitudeUvToBoundsLongitudeUv + u_ellipsoidOffsetLongitudeUvToBoundsLongitudeUv;
@@ -1030,21 +1055,28 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
#endif
// Compute height
- #if defined(ELLIPSOID_IS_SPHERE)
- #if defined(ELLIPSOID_INNER)
- float height = (length(posEllipsoid) - u_ellipseInnerRadiiUv.x) * u_ellipsoidInverseHeightDifferenceUv;
- #else
- float height = length(posEllipsoid);
- #endif
+ #if defined(ELLIPSOID_INNER_OUTER_EQUAL)
+ // TODO: This breaks down when minBounds == maxBounds. To fix it, this
+ // function would have to know if ray is intersecting the front or back of the shape
+ // and set the shape space position to 1 (front) or 0 (back) accordingly.
+ float height = 1.0;
#else
- #if defined(ELLIPSOID_INNER)
- // Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84).
- // This is an optimization so that math can be done with ellipses instead of ellipsoids.
- vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
- float height = ellipseDistanceIterative(posEllipse, u_ellipseInnerRadiiUv) * u_ellipsoidInverseHeightDifferenceUv;
+ #if defined(ELLIPSOID_IS_SPHERE)
+ #if defined(ELLIPSOID_INNER)
+ float height = (length(posEllipsoid) - u_ellipseInnerRadiiUv.x) * u_ellipsoidInverseHeightDifferenceUv;
+ #else
+ float height = length(posEllipsoid);
+ #endif
#else
- // TODO: this is probably not correct
- float height = length(posEllipsoid);
+ #if defined(ELLIPSOID_INNER)
+ // Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84).
+ // This is an optimization so that math can be done with ellipses instead of ellipsoids.
+ vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
+ float height = ellipseDistanceIterative(posEllipse, u_ellipseInnerRadiiUv) * u_ellipsoidInverseHeightDifferenceUv;
+ #else
+ // TODO: this is probably not correct
+ float height = length(posEllipsoid);
+ #endif
#endif
#endif
@@ -1075,34 +1107,32 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
return;
}
- #if defined(CYLINDER_INNER)
+ #if defined(CYLINDER_INNER_OUTER_EQUAL)
+ // When the cylinder is perfectly thin it's necessary to sandwich the
+ // inner cylinder intersection inside the outer cylinder intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the cylinder to be invisible because it will think the ray
+ // is still inside the inner (negative) cylinder after exiting the
+ // outer (positive) cylinder.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If initializeIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ vec2 innerIntersect = intersectInfiniteUnitCylinder(outerRay);
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #elif defined(CYLINDER_INNER)
Ray innerRay = Ray(ray.pos * u_cylinderScaleUvToInnerBounds + u_cylinderTranslateUvToInnerBounds, ray.dir * u_cylinderScaleUvToInnerBounds);
vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
-
- #if defined(CYLINDER_INNER_OUTER_EQUAL)
- // When the cylinder is perfectly thin it's necessary to sandwich the
- // inner cylinder intersection inside the outer cylinder intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the cylinder to be invisible because it will think the ray
- // is still inside the inner (negative) cylinder after exiting the
- // outer (positive) cylinder.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If initializeIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
- setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
- setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
- setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
- setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #else
- setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
- #endif
+ setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
#endif
#if defined(CYLINDER_WEDGE_REGULAR)
@@ -1119,22 +1149,28 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
#if defined(SHAPE_CYLINDER)
vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
vec3 positionLocal = positionUv * 2.0 - 1.0; // [-1,+1]
- float radius = length(positionLocal.xy); // [0,1]
- float height = positionUv.z; // [0,1]
- float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
-
- // TODO: This breaks down when minBounds == maxBounds. To fix it, this
- // function would have to know if ray is intersecting the front or back of the shape
- // and set the shape space position to 1 (front) or 0 (back) accordingly.
- #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
- radius = radius * u_cylinderScaleRadiusUvToBoundsRadiusUv + u_cylinderOffsetRadiusUvToBoundsRadiusUv;
+ // Compute radius
+ #if defined(CYLINDER_INNER_OUTER_EQUAL)
+ // TODO: This breaks down when minBounds == maxBounds. To fix it, this
+ // function would have to know if ray is intersecting the front or back of the shape
+ // and set the shape space position to 1 (front) or 0 (back) accordingly.
+ float radius = 1.0;
+ #else
+ float radius = length(positionLocal.xy); // [0,1]
+ #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
+ radius = radius * u_cylinderScaleRadiusUvToBoundsRadiusUv + u_cylinderOffsetRadiusUvToBoundsRadiusUv;
+ #endif
#endif
-
+
+ // Compute height
+ float height = positionUv.z; // [0,1]
#if defined(CYLINDER_HEIGHT_NON_DEFAULT)
height = height * u_cylinderScaleHeightUvToBoundsHeightUv + u_cylinderOffsetHeightUvToBoundsHeightUv;
#endif
+ // Compute angle
+ float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
#if defined(CYLINDER_WEDGE)
#if defined(CYLINDER_ANGLE_FLIPPED)
angle += float(angle <= u_cylinderMinAngleUv);
From f730221618b02d6d1928c655d808e176b72442e8 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 19:19:12 -0400
Subject: [PATCH 032/679] trying to reduce the number of shape uniforms
---
Source/Scene/VoxelBoxShape.js | 33 +++---
Source/Scene/VoxelCylinderShape.js | 108 ++++++++-----------
Source/Scene/VoxelEllipsoidShape.js | 24 +++--
Source/Shaders/VoxelFS.glsl | 154 +++++++++++++---------------
4 files changed, 145 insertions(+), 174 deletions(-)
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index cbd62a05358..301524456b1 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -75,11 +75,11 @@ function VoxelBoxShape() {
* @readonly
*/
this.shaderUniforms = {
- boxScaleUvToBoundsUv: new Cartesian3(),
- boxTranslateUvToBoundsUv: new Cartesian3(),
boxScaleUvToBounds: new Cartesian3(),
- boxTranslateUvToBounds: new Cartesian3(),
+ boxOffsetUvToBounds: new Cartesian3(),
boxTransformUvToBounds: new Matrix4(),
+ boxScaleUvToBoundsUv: new Cartesian3(),
+ boxOffsetUvToBoundsUv: new Cartesian3(),
};
/**
@@ -89,10 +89,8 @@ function VoxelBoxShape() {
this.shaderDefines = {
BOX_INTERSECTION_COUNT: undefined,
BOX_INTERSECTION_INDEX: undefined,
- BOX_BOUNDED: undefined,
- BOX_XY_PLANE: undefined,
- BOX_XZ_PLANE: undefined,
- BOX_YZ_PLANE: undefined,
+ BOX_IS_BOUNDED: undefined,
+ BOX_IS_RECTANGLE: undefined,
};
}
@@ -222,7 +220,7 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
maxBounds.y !== defaultMaxBounds.y ||
maxBounds.z !== defaultMaxBounds.z
) {
- shaderDefines["BOX_BOUNDED"] = true;
+ shaderDefines["BOX_IS_BOUNDED"] = true;
// inverse(scale)
// inverse(maxBoundsUv - minBoundsUv)
@@ -256,15 +254,14 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
minBounds.y === maxBounds.y ||
minBounds.z === maxBounds.z
) {
+ shaderDefines["BOX_IS_RECTANGLE"] = true;
+
let transformAxisConversion;
if (minBounds.x === maxBounds.x) {
- shaderDefines["BOX_YZ_PLANE"] = true;
transformAxisConversion = transformXYZToZYX;
} else if (minBounds.y === maxBounds.y) {
- shaderDefines["BOX_XZ_PLANE"] = true;
transformAxisConversion = transformXYZToXZY;
} else if (minBounds.z === maxBounds.z) {
- shaderDefines["BOX_XY_PLANE"] = true;
transformAxisConversion = Matrix4.IDENTITY;
}
transformLocalToBounds = Matrix4.multiply(
@@ -283,9 +280,9 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
shaderUniforms.boxTransformUvToBounds,
shaderUniforms.boxScaleUvToBounds
);
- shaderUniforms.boxTranslateUvToBounds = Matrix4.getTranslation(
+ shaderUniforms.boxOffsetUvToBounds = Matrix4.getTranslation(
shaderUniforms.boxTransformUvToBounds,
- shaderUniforms.boxTranslateUvToBounds
+ shaderUniforms.boxOffsetUvToBounds
);
// Go from UV space to bounded UV space:
@@ -295,18 +292,18 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
// scale = 1.0 / (maxBoundsUv - minBoundsUv)
// scale = 1.0 / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
// scale = 2.0 / (maxBounds - minBounds)
- // translation = -minBoundsUv / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
- // translation = -2.0 * (minBounds * 0.5 + 0.5) / (maxBounds - minBounds)
- // translation = -scale * (minBounds * 0.5 + 0.5)
+ // offset = -minBoundsUv / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
+ // offset = -2.0 * (minBounds * 0.5 + 0.5) / (maxBounds - minBounds)
+ // offset = -scale * (minBounds * 0.5 + 0.5)
shaderUniforms.boxScaleUvToBoundsUv = Cartesian3.clone(
boundsScaleLocalToBounds,
shaderUniforms.boxScaleUvToBoundsUv
);
- shaderUniforms.boxTranslateUvToBoundsUv = Cartesian3.fromElements(
+ shaderUniforms.boxOffsetUvToBoundsUv = Cartesian3.fromElements(
-boundsScaleLocalToBounds.x * (minBounds.x * 0.5 + 0.5),
-boundsScaleLocalToBounds.y * (minBounds.y * 0.5 + 0.5),
-boundsScaleLocalToBounds.z * (minBounds.z * 0.5 + 0.5),
- shaderUniforms.boxTranslateUvToBoundsUv
+ shaderUniforms.boxOffsetUvToBoundsUv
);
}
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index d0edaebd1f9..0b36456e389 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -1,4 +1,5 @@
import BoundingSphere from "../Core/BoundingSphere.js";
+import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
import Check from "../Core/Check.js";
import CesiumMath from "../Core/Math.js";
@@ -95,17 +96,12 @@ function VoxelCylinderShape() {
this.shaderUniforms = {
cylinderScaleUvToBounds: new Cartesian3(),
cylinderTranslateUvToBounds: new Cartesian3(),
- cylinderScaleUvToInnerBounds: new Cartesian3(),
- cylinderTranslateUvToInnerBounds: new Cartesian3(),
- cylinderScaleRadiusUvToBoundsRadiusUv: 0.0,
- cylinderOffsetRadiusUvToBoundsRadiusUv: 0.0,
- cylinderScaleHeightUvToBoundsHeightUv: 0.0,
- cylinderOffsetHeightUvToBoundsHeightUv: 0.0,
- cylinderMinAngle: 0.0,
- cylinderMaxAngle: 0.0,
+ cylinderInverseInnerRadiusUv: 0.0,
+ cylinderAngleMinMax: new Cartesian2(),
+ cylinderRadiusUvScaleAndOffset: new Cartesian2(),
+ cylinderHeightUvScaleAndOffset: new Cartesian2(),
+ cylinderAngleUvScaleAndOffset: new Cartesian2(),
cylinderMinAngleUv: 0.0,
- cylinderScaleAngleUvToBoundsAngleUv: 0.0,
- cylinderOffsetAngleUvToBoundsAngleUv: 0.0,
};
/**
@@ -121,7 +117,7 @@ function VoxelCylinderShape() {
CYLINDER_INNER_INDEX: undefined,
CYLINDER_HEIGHT_NON_DEFAULT: undefined,
CYLINDER_HEIGHT_ZERO: undefined,
- CYLINDER_ANGLE_FLIPPED: undefined,
+ CYLINDER_WEDGE_ANGLE_FLIPPED: undefined,
CYLINDER_WEDGE_INDEX: undefined,
CYLINDER_WEDGE_REGULAR: undefined,
CYLINDER_WEDGE_FLIPPED: undefined,
@@ -326,40 +322,7 @@ VoxelCylinderShape.prototype.update = function (
}
if (!isDefaultOuterCylinder || hasInnerCylinder || !isDefaultHeight) {
- const boundsScaleLocalToInnerBounds = Cartesian3.fromElements(
- 1.0 / minRadius,
- 1.0 / minRadius,
- 1.0 / (maxHeight === minHeight ? 1.0 : 0.5 * (maxHeight - minHeight)),
- scratchBoundsScale
- );
-
- // -inverse(scale) * translation // affine inverse
- // -inverse(scale) * 0.5 * (minHeight + maxHeight)
- const boundsTranslateLocalToInnerBounds = Cartesian3.fromElements(
- 0.0,
- 0.0,
- -boundsScaleLocalToInnerBounds.z * 0.5 * (minHeight + maxHeight),
- scratchBoundsTranslation
- );
-
- const transformLocalToBounds = Matrix4.fromRotationTranslation(
- Matrix3.fromScale(boundsScaleLocalToInnerBounds),
- boundsTranslateLocalToInnerBounds,
- scratchTransformLocalToBounds
- );
- const transformUvToBounds = Matrix4.multiplyTransformation(
- transformLocalToBounds,
- transformUvToLocal,
- scratchTransformUvToBounds
- );
- shaderUniforms.cylinderScaleUvToInnerBounds = Matrix4.getScale(
- transformUvToBounds,
- shaderUniforms.cylinderScaleUvToInnerBounds
- );
- shaderUniforms.cylinderTranslateUvToInnerBounds = Matrix4.getTranslation(
- transformUvToBounds,
- shaderUniforms.cylinderTranslateUvToInnerBounds
- );
+ shaderUniforms.cylinderInverseInnerRadiusUv = maxRadius / minRadius;
// delerp(radius, minRadius, maxRadius)
// (radius - minRadius) / (maxRadius - minRadius)
@@ -371,8 +334,11 @@ VoxelCylinderShape.prototype.update = function (
const scale = 1.0 / (maxRadius - minRadius);
const offset = minRadius / (minRadius - maxRadius);
- shaderUniforms.cylinderScaleRadiusUvToBoundsRadiusUv = scale;
- shaderUniforms.cylinderOffsetRadiusUvToBoundsRadiusUv = offset;
+ shaderUniforms.cylinderRadiusUvScaleAndOffset = Cartesian2.fromElements(
+ scale,
+ offset,
+ shaderUniforms.cylinderRadiusUvScaleAndOffset
+ );
}
if (!isDefaultHeight) {
@@ -397,20 +363,40 @@ VoxelCylinderShape.prototype.update = function (
const scale = 2.0 / (maxHeight - minHeight);
const offset = (minHeight + 1.0) / (minHeight - maxHeight);
- shaderUniforms.cylinderScaleHeightUvToBoundsHeightUv = scale;
- shaderUniforms.cylinderOffsetHeightUvToBoundsHeightUv = offset;
+ shaderUniforms.cylinderHeightUvScaleAndOffset = Cartesian2.fromElements(
+ scale,
+ offset,
+ shaderUniforms.cylinderHeightUvScaleAndOffset
+ );
}
if (isAngleFlipped) {
- shaderDefines["CYLINDER_ANGLE_FLIPPED"] = true;
+ shaderDefines["CYLINDER_WEDGE_ANGLE_FLIPPED"] = true;
}
+ // Intersects a wedge for the min and max longitude.
if (hasWedge) {
shaderDefines["CYLINDER_WEDGE"] = true;
shaderDefines["CYLINDER_WEDGE_INDEX"] = intersectionCount;
- shaderUniforms.cylinderMinAngle = minAngle;
- shaderUniforms.cylinderMaxAngle = maxAngle;
+ if (hasWedgeRegular) {
+ shaderDefines["CYLINDER_WEDGE_REGULAR"] = true;
+ intersectionCount += 1;
+ } else if (hasWedgeFlipped) {
+ shaderDefines["CYLINDER_WEDGE_FLIPPED"] = true;
+ intersectionCount += 2;
+ }
+
+ shaderUniforms.cylinderAngleMinMax = Cartesian2.fromElements(
+ minAngle,
+ maxAngle,
+ shaderUniforms.cylinderAngleMinMax
+ );
+
+ if (isAngleFlipped) {
+ const minAngleUv = (minAngle - defaultMinAngle) / defaultAngleWidth;
+ shaderUniforms.cylinderMinAngleUv = minAngleUv;
+ }
// delerp(angleUv, minAngleUv, maxAngleUv)
// (angelUv - minAngleUv) / (maxAngleUv - minAngleUv)
@@ -422,22 +408,14 @@ VoxelCylinderShape.prototype.update = function (
// offset = -((minAngle - pi) / (2.0 * pi)) / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
// offset = -(minAngle - pi) / (maxAngle - minAngle)
- const minAngleUv = (minAngle - defaultMinAngle) / defaultAngleWidth;
const scale = defaultAngleWidth / angleWidth;
const offset = -(minAngle - defaultMinAngle) / angleWidth;
- shaderUniforms.cylinderMinAngleUv = minAngleUv;
- shaderUniforms.cylinderScaleAngleUvToBoundsAngleUv = scale;
- shaderUniforms.cylinderOffsetAngleUvToBoundsAngleUv = offset;
-
- if (hasWedgeRegular) {
- // Intersects a wedge for the min and max longitude.
- shaderDefines["CYLINDER_WEDGE_REGULAR"] = true;
- intersectionCount += 1;
- } else if (hasWedgeFlipped) {
- shaderDefines["CYLINDER_WEDGE_FLIPPED"] = true;
- intersectionCount += 2;
- }
+ shaderUniforms.cylinderAngleUvScaleAndOffset = Cartesian2.fromElements(
+ scale,
+ offset,
+ shaderUniforms.cylinderAngleUvScaleAndOffset
+ );
}
shaderDefines["CYLINDER_INTERSECTION_COUNT"] = intersectionCount;
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 865bf65538c..bb1f382da20 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -98,12 +98,10 @@ function VoxelEllipsoidShape() {
ellipsoidRadiiUv: new Cartesian3(),
ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
// Wedge uniforms
- ellipsoidWestUv: 0.0,
- ellipsoidScaleLongitudeUvToBoundsLongitudeUv: 0.0,
- ellipsoidOffsetLongitudeUvToBoundsLongitudeUv: 0.0,
+ ellipsoidMinLongitudeUv: 0.0,
+ ellipsoidLongitudeUvScaleAndOffset: new Cartesian2(),
// Cone uniforms
- ellipsoidScaleLatitudeUvToBoundsLatitudeUv: 0.0,
- ellipsoidOffsetLatitudeUvToBoundsLatitudeUv: 0.0,
+ ellipsoidLatitudeUvScaleAndOffset: new Cartesian2(),
// Inner ellipsoid uniforms
ellipsoidInverseHeightDifferenceUv: 0.0,
ellipsoidInverseInnerScaleUv: 0.0,
@@ -379,9 +377,12 @@ VoxelEllipsoidShape.prototype.update = function (
const scale = defaultLongitudeLength / rectangleWidth;
const offset = -(west - defaultMinLongitude) / rectangleWidth;
- shaderUniforms.ellipsoidWestUv = westUv;
- shaderUniforms.ellipsoidScaleLongitudeUvToBoundsLongitudeUv = scale;
- shaderUniforms.ellipsoidOffsetLongitudeUvToBoundsLongitudeUv = offset;
+ shaderUniforms.ellipsoidMinLongitudeUv = westUv;
+ shaderUniforms.ellipsoidLongitudeUvScaleAndOffset = Cartesian2.fromElements(
+ scale,
+ offset,
+ shaderUniforms.ellipsoidLongitudeUvScaleAndOffset
+ );
}
if (isAngleFlipped) {
@@ -402,8 +403,11 @@ VoxelEllipsoidShape.prototype.update = function (
const scale = defaultLatitudeLength / rectangleHeight;
const offset = -(south - defaultMinLatitude) / rectangleHeight;
- shaderUniforms.ellipsoidScaleLatitudeUvToBoundsLatitudeUv = scale;
- shaderUniforms.ellipsoidOffsetLatitudeUvToBoundsLatitudeUv = offset;
+ shaderUniforms.ellipsoidLatitudeUvScaleAndOffset = Cartesian2.fromElements(
+ scale,
+ offset,
+ shaderUniforms.ellipsoidLatitudeUvScaleAndOffset
+ );
}
// Intersects a cone for min latitude
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index d46cd7ae817..b5ba60e7bcd 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -193,22 +193,21 @@ uniform float u_stepSize;
/* Box defines:
#define BOX_INTERSECTION_COUNT ### // always 1
#define BOX_INTERSECTION_INDEX ### // always 0
- #define BOX_BOUNDED
- #define BOX_XY_PLANE
- #define BOX_XZ_PLANE
- #define BOX_YZ_PLANE
+ #define BOX_IS_BOUNDED
+ #define BOX_IS_RECTANGLE
*/
// Box uniforms:
- #if defined(BOX_BOUNDED)
+ #if defined(BOX_IS_BOUNDED)
uniform vec3 u_boxScaleUvToBoundsUv;
- uniform vec3 u_boxTranslateUvToBoundsUv;
- #if defined(BOX_XY_PLANE) || defined(BOX_XZ_PLANE) || defined(BOX_YZ_PLANE)
+ uniform vec3 u_boxOffsetUvToBoundsUv;
+ #if defined(BOX_IS_RECTANGLE)
+ // This matrix bakes in an axis conversion so that the math works for XY plane.
uniform mat4 u_boxTransformUvToBounds;
#else
// Similar to u_boxTransformUvToBounds but fewer instructions needed.
uniform vec3 u_boxScaleUvToBounds;
- uniform vec3 u_boxTranslateUvToBounds;
+ uniform vec3 u_boxOffsetUvToBounds;
#endif
#endif
#endif
@@ -239,21 +238,20 @@ uniform float u_stepSize;
// Ellipsoid uniforms
uniform vec3 u_ellipsoidRadiiUv; // [0,1]
- uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
-
+ #if !defined(ELLIPSOID_IS_SPHERE)
+ uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
+ #endif
#if defined(ELLIPSOID_WEDGE) || defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
#endif
- #if defined(ELLIPSOID_WEDGE) && defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
- uniform float u_ellipsoidWestUv;
- #endif
#if defined(ELLIPSOID_WEDGE)
- uniform float u_ellipsoidScaleLongitudeUvToBoundsLongitudeUv;
- uniform float u_ellipsoidOffsetLongitudeUvToBoundsLongitudeUv;
+ uniform vec2 u_ellipsoidLongitudeUvScaleAndOffset;
+ #if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
+ uniform float u_ellipsoidMinLongitudeUv;
+ #endif
#endif
#if defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
- uniform float u_ellipsoidScaleLatitudeUvToBoundsLatitudeUv;
- uniform float u_ellipsoidOffsetLatitudeUvToBoundsLatitudeUv;
+ uniform vec2 u_ellipsoidLatitudeUvScaleAndOffset;
#endif
#if defined(ELLIPSOID_INNER) && !defined(ELLIPSOID_INNER_OUTER_EQUAL)
uniform float u_ellipsoidInverseHeightDifferenceUv;
@@ -272,11 +270,11 @@ uniform float u_stepSize;
#define CYLINDER_INNER_INDEX ### // when there's an inner cylinder
#define CYLINDER_HEIGHT_NON_DEFAULT ### //
#define CYLINDER_HEIGHT_ZERO // when the height is 0
- #define CYLINDER_ANGLE_FLIPPED //
#define CYLINDER_WEDGE //
#define CYLINDER_WEDGE_INDEX //
#define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
#define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
+ #define CYLINDER_WEDGE_ANGLE_FLIPPED //
*/
// Cylinder uniforms
@@ -284,26 +282,21 @@ uniform float u_stepSize;
uniform vec3 u_cylinderScaleUvToBounds;
uniform vec3 u_cylinderTranslateUvToBounds;
#endif
-
- #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
- uniform float u_cylinderScaleRadiusUvToBoundsRadiusUv;
- uniform float u_cylinderOffsetRadiusUvToBoundsRadiusUv;
- #endif
-
#if defined(CYLINDER_INNER) && !defined(CYLINDER_INNER_OUTER_EQUAL)
- uniform vec3 u_cylinderScaleUvToInnerBounds;
- uniform vec3 u_cylinderTranslateUvToInnerBounds;
+ uniform float u_cylinderInverseInnerRadiusUv;
+ #endif
+ #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
+ uniform vec2 u_cylinderRadiusUvScaleAndOffset;
#endif
#if defined(CYLINDER_HEIGHT_NON_DEFAULT)
- uniform float u_cylinderScaleHeightUvToBoundsHeightUv;
- uniform float u_cylinderOffsetHeightUvToBoundsHeightUv;
+ uniform vec2 u_cylinderHeightUvScaleAndOffset;
#endif
#if defined(CYLINDER_WEDGE)
- uniform float u_cylinderMinAngle;
- uniform float u_cylinderMaxAngle;
- uniform float u_cylinderMinAngleUv;
- uniform float u_cylinderScaleAngleUvToBoundsAngleUv;
- uniform float u_cylinderOffsetAngleUvToBoundsAngleUv;
+ uniform vec2 u_cylinderAngleMinMax;
+ uniform vec2 u_cylinderAngleUvScaleAndOffset;
+ #if defined(CYLINDER_WEDGE_ANGLE_FLIPPED)
+ uniform float u_cylinderMinAngleUv;
+ #endif
#endif
#endif
@@ -896,21 +889,22 @@ float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
#if defined(SHAPE_BOX)
void intersectBoxShape(Ray ray, out Intersections ix)
{
- #if !defined(BOX_BOUNDED)
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir = ray.dir * 2.0;
+ #if defined(BOX_IS_BOUNDED)
+ // Transform the ray into unit cube space
+ ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxOffsetUvToBounds;
+ ray.dir *= u_boxScaleUvToBounds;
vec2 entryExit = intersectUnitCube(ray);
- #elif defined(BOX_XY_PLANE) || defined(BOX_XZ_PLANE) || defined(BOX_YZ_PLANE)
+ #elif defined(BOX_IS_RECTANGLE)
// Transform the ray into unit square space on Z plane
+ // This matrix bakes in an axis conversion so that the math works for XY plane.
ray.pos = vec3(u_boxTransformUvToBounds * vec4(ray.pos, 1.0));
ray.dir = vec3(u_boxTransformUvToBounds * vec4(ray.dir, 0.0));
vec2 entryExit = intersectUnitSquare(ray);
#else
- // Transform the ray into unit cube space
- ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxTranslateUvToBounds;
- ray.dir *= u_boxScaleUvToBounds;
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir = ray.dir * 2.0;
vec2 entryExit = intersectUnitCube(ray);
#endif
@@ -920,8 +914,8 @@ void intersectBoxShape(Ray ray, out Intersections ix)
#if defined(SHAPE_BOX)
vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
- #if defined(BOX_BOUNDED)
- return positionUv * u_boxScaleUvToBoundsUv + u_boxTranslateUvToBoundsUv;
+ #if defined(BOX_IS_BOUNDED)
+ return positionUv * u_boxScaleUvToBoundsUv + u_boxOffsetUvToBoundsUv;
#else
return positionUv;
#endif
@@ -945,33 +939,31 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
}
// Inner ellipsoid
- #if defined(ELLIPSOID_INNER)
- #if defined(ELLIPSOID_INNER_OUTER_EQUAL)
- // When the ellipsoid is perfectly thin it's necessary to sandwich the
- // inner ellipsoid intersection inside the outer ellipsoid intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the ellipsoid to be invisible because it will think the ray
- // is still inside the inner (negative) ellipsoid after exiting the
- // outer (positive) ellipsoid.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If initializeIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
- setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
- setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
- setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
- setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #else
- Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
- vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- setIntersectionPair(ix, ELLIPSOID_INNER_INDEX, innerIntersect);
- #endif
+ #if defined(ELLIPSOID_INNER_OUTER_EQUAL)
+ // When the ellipsoid is perfectly thin it's necessary to sandwich the
+ // inner ellipsoid intersection inside the outer ellipsoid intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the ellipsoid to be invisible because it will think the ray
+ // is still inside the inner (negative) ellipsoid after exiting the
+ // outer (positive) ellipsoid.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If initializeIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #elif defined(ELLIPSOID_INNER)
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ setIntersectionPair(ix, ELLIPSOID_INNER_INDEX, innerIntersect);
#endif
// Flip the ray because the intersection function expects a cone growing towards +Z.
@@ -1043,15 +1035,15 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
#if defined(ELLIPSOID_WEDGE)
#if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
- longitude += float(longitude <= u_ellipsoidWestUv);
+ longitude += float(longitude <= u_ellipsoidMinLongitudeUv);
#endif
- longitude = longitude * u_ellipsoidScaleLongitudeUvToBoundsLongitudeUv + u_ellipsoidOffsetLongitudeUvToBoundsLongitudeUv;
+ longitude = longitude * u_ellipsoidLongitudeUvScaleAndOffset.x + u_ellipsoidLongitudeUvScaleAndOffset.y;
#endif
// Compute latitude
float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi;
#if (defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP))
- latitude = latitude * u_ellipsoidScaleLatitudeUvToBoundsLatitudeUv + u_ellipsoidOffsetLatitudeUvToBoundsLatitudeUv;
+ latitude = latitude * u_ellipsoidLatitudeUvScaleAndOffset.x + u_ellipsoidLatitudeUvScaleAndOffset.y;
#endif
// Compute height
@@ -1130,16 +1122,16 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
#elif defined(CYLINDER_INNER)
- Ray innerRay = Ray(ray.pos * u_cylinderScaleUvToInnerBounds + u_cylinderTranslateUvToInnerBounds, ray.dir * u_cylinderScaleUvToInnerBounds);
+ Ray innerRay = Ray(outerRay.pos * u_cylinderInverseInnerRadiusUv, outerRay.dir * u_cylinderInverseInnerRadiusUv);
vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
#endif
#if defined(CYLINDER_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectRegularWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
+ vec2 wedgeIntersect = intersectRegularWedge(outerRay, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
#elif defined(CYLINDER_WEDGE_FLIPPED)
- vec4 wedgeIntersect = intersectFlippedWedge(outerRay, u_cylinderMinAngle, u_cylinderMaxAngle);
+ vec4 wedgeIntersect = intersectFlippedWedge(outerRay, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, wedgeIntersect.xy);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, wedgeIntersect.zw);
#endif
@@ -1159,23 +1151,23 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
#else
float radius = length(positionLocal.xy); // [0,1]
#if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
- radius = radius * u_cylinderScaleRadiusUvToBoundsRadiusUv + u_cylinderOffsetRadiusUvToBoundsRadiusUv;
+ radius = radius * u_cylinderRadiusUvScaleAndOffset.x + u_cylinderRadiusUvScaleAndOffset.y;
#endif
#endif
// Compute height
float height = positionUv.z; // [0,1]
#if defined(CYLINDER_HEIGHT_NON_DEFAULT)
- height = height * u_cylinderScaleHeightUvToBoundsHeightUv + u_cylinderOffsetHeightUvToBoundsHeightUv;
+ height = height * u_cylinderHeightUvScaleAndOffset.x + u_cylinderHeightUvScaleAndOffset.y;
#endif
// Compute angle
float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
#if defined(CYLINDER_WEDGE)
- #if defined(CYLINDER_ANGLE_FLIPPED)
+ #if defined(CYLINDER_WEDGE_ANGLE_FLIPPED)
angle += float(angle <= u_cylinderMinAngleUv);
#endif
- angle = angle * u_cylinderScaleAngleUvToBoundsAngleUv + u_cylinderOffsetAngleUvToBoundsAngleUv;
+ angle = angle * u_cylinderAngleUvScaleAndOffset.x + u_cylinderAngleUvScaleAndOffset.y;
#endif
return vec3(radius, height, angle);
From a19e9b9c4e3c7c68e6c7e8c3396522cd51ddd821 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 20:05:40 -0400
Subject: [PATCH 033/679] handling multiple intersection intervals behind the
ray
---
Source/Shaders/VoxelFS.glsl | 43 +++++++++++++++++++------------------
1 file changed, 22 insertions(+), 21 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index b5ba60e7bcd..791ca2573f8 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -407,6 +407,9 @@ struct Intersections {
#define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)]
#endif
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#define getIntersectionPair(/*inout Intersections*/ ix, /*int*/ index) vec2(getIntersection(ix, (index) * 2 + 0), getIntersection(ix, (index) * 2 + 1))
+
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
#if (SCENE_INTERSECTION_COUNT > 1)
#define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = vec2((t), float(!positive) * 2.0 + float(!enter))
@@ -470,13 +473,13 @@ vec2 nextIntersection(inout Intersections ix) {
#endif
#if (SCENE_INTERSECTION_COUNT > 1)
-vec2 initializeIntersections(inout Intersections ix) {
+void initializeIntersections(inout Intersections ix) {
// Sort the intersections from min T to max T with bubble sort.
// Note: If this sorting function changes, some of the intersection test may
// need to be updated. Search for "bubble sort" to find those areas.
- const int passes = SCENE_INTERSECTION_COUNT * 2 - 1;
- for (int n = passes; n > 0; --n) {
- for (int i = 0; i < passes; ++i) {
+ const int sortPasses = SCENE_INTERSECTION_COUNT * 2 - 1;
+ for (int n = sortPasses; n > 0; --n) {
+ for (int i = 0; i < sortPasses; ++i) {
// The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
// loop with non-constant condition, so it has to break early instead
if (i >= n) { break; }
@@ -503,8 +506,6 @@ vec2 initializeIntersections(inout Intersections ix) {
ix.index = 0;
ix.surroundCount = 0;
ix.surroundIsPositive = false;
-
- return nextIntersection(ix);
}
#endif
@@ -1216,9 +1217,9 @@ vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Int
// Do a ray-shape intersection to find the exact starting and ending points.
intersectShape(ray, ix);
- // Check if the positive shape was completely missed, and if so, exit early.
- float entryPositiveShapeT = getIntersection(ix, 0);
- if (entryPositiveShapeT == NO_HIT) {
+ // Exit early if the positive shape was completely missed or behind the ray.
+ vec2 entryExitT = getIntersectionPair(ix, 0);
+ if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
return vec2(NO_HIT);
}
@@ -1228,22 +1229,22 @@ vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Int
setIntersectionPair(ix, DEPTH_INTERSECTION_INDEX, vec2(depthT, +INF_HIT));
#endif
- // Find the first intersection interval
+ // Find the first intersection that's in front of the ray
#if (SCENE_INTERSECTION_COUNT > 1)
- vec2 entryExitT = initializeIntersections(ix);
+ initializeIntersections(ix);
+ for (int i = 0; i < SCENE_INTERSECTION_COUNT; i++) {
+ entryExitT = nextIntersection(ix);
+ if (entryExitT.y > 0.0) {
+ // Set start to 0.0 when ray is inside the shape.
+ entryExitT.x = max(entryExitT.x, 0.0);
+ break;
+ }
+ }
#else
- float exitPositiveShapeT = getIntersection(ix, 1);
- vec2 entryExitT = vec2(entryPositiveShapeT, exitPositiveShapeT);
+ // Set start to 0.0 when ray is inside the shape.
+ entryExitT.x = max(entryExitT.x, 0.0);
#endif
- // Intersection is invalid when start and end are behind the ray.
- if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
- return vec2(NO_HIT);
- }
-
- // Set start to 0.0 when ray is inside the shape.
- entryExitT.x = max(entryExitT.x, 0.0);
-
return entryExitT;
}
From f3e90f0411bb4631d6af66e9bcbd11142387b76c Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 14 Apr 2022 20:08:15 -0400
Subject: [PATCH 034/679] forgot to wrap macro param in parenthesis
---
Source/Shaders/VoxelFS.glsl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 791ca2573f8..4bd1872941a 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -408,7 +408,7 @@ struct Intersections {
#endif
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#define getIntersectionPair(/*inout Intersections*/ ix, /*int*/ index) vec2(getIntersection(ix, (index) * 2 + 0), getIntersection(ix, (index) * 2 + 1))
+#define getIntersectionPair(/*inout Intersections*/ ix, /*int*/ index) vec2(getIntersection((ix), (index) * 2 + 0), getIntersection((ix), (index) * 2 + 1))
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
#if (SCENE_INTERSECTION_COUNT > 1)
From 24aa97bcefe2cc710912a50191d64ca6e13ea66b Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 15 Apr 2022 09:22:31 -0400
Subject: [PATCH 035/679] square fix
---
Source/Shaders/VoxelFS.glsl | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 4bd1872941a..21be3c48a50 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -890,17 +890,17 @@ float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
#if defined(SHAPE_BOX)
void intersectBoxShape(Ray ray, out Intersections ix)
{
- #if defined(BOX_IS_BOUNDED)
- // Transform the ray into unit cube space
- ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxOffsetUvToBounds;
- ray.dir *= u_boxScaleUvToBounds;
- vec2 entryExit = intersectUnitCube(ray);
- #elif defined(BOX_IS_RECTANGLE)
+ #if defined(BOX_IS_RECTANGLE)
// Transform the ray into unit square space on Z plane
// This matrix bakes in an axis conversion so that the math works for XY plane.
ray.pos = vec3(u_boxTransformUvToBounds * vec4(ray.pos, 1.0));
ray.dir = vec3(u_boxTransformUvToBounds * vec4(ray.dir, 0.0));
vec2 entryExit = intersectUnitSquare(ray);
+ #elif defined(BOX_IS_BOUNDED)
+ // Transform the ray into unit cube space
+ ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxOffsetUvToBounds;
+ ray.dir *= u_boxScaleUvToBounds;
+ vec2 entryExit = intersectUnitCube(ray);
#else
// Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
// Direction is scaled as well to be in sync with position.
From 306002b6400de542b5ce0cb28206379b69880410 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 15 Apr 2022 10:36:37 -0400
Subject: [PATCH 036/679] handling flat cone
---
Source/Scene/VoxelEllipsoidShape.js | 41 +++++++++++++---
Source/Shaders/VoxelFS.glsl | 73 +++++++++++++++++++----------
2 files changed, 82 insertions(+), 32 deletions(-)
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index bb1f382da20..2cbab24949a 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -102,6 +102,8 @@ function VoxelEllipsoidShape() {
ellipsoidLongitudeUvScaleAndOffset: new Cartesian2(),
// Cone uniforms
ellipsoidLatitudeUvScaleAndOffset: new Cartesian2(),
+ ellipsoidMinLatitudeCosSqrHalfAngle: 0.0,
+ ellipsoidMaxLatitudeCosSqrHalfAngle: 0.0,
// Inner ellipsoid uniforms
ellipsoidInverseHeightDifferenceUv: 0.0,
ellipsoidInverseInnerScaleUv: 0.0,
@@ -122,10 +124,12 @@ function VoxelEllipsoidShape() {
ELLIPSOID_CONE_BOTTOM: undefined,
ELLIPSOID_CONE_BOTTOM_REGULAR: undefined,
ELLIPSOID_CONE_BOTTOM_FLIPPED: undefined,
+ ELLIPSOID_CONE_BOTTOM_FLAT: undefined,
ELLIPSOID_CONE_BOTTOM_INDEX: undefined,
ELLIPSOID_CONE_TOP: undefined,
ELLIPSOID_CONE_TOP_REGULAR: undefined,
ELLIPSOID_CONE_TOP_FLIPPED: undefined,
+ ELLIPSOID_CONE_TOP_FLAT: undefined,
ELLIPSOID_CONE_TOP_INDEX: undefined,
ELLIPSOID_OUTER: undefined,
ELLIPSOID_OUTER_INDEX: undefined,
@@ -302,13 +306,19 @@ VoxelEllipsoidShape.prototype.update = function (
const hasWedgeFlipped = rectangleWidth < CesiumMath.PI;
const hasWedge = hasWedgeRegular || hasWedgeFlipped;
- const hasTopConeRegular = north >= 0.0 && north < +CesiumMath.PI_OVER_TWO;
- const hasTopConeFlipped = north < 0.0;
- const hasTopCone = hasTopConeRegular || hasTopConeFlipped;
+ const flatConeEpsilon = CesiumMath.EPSILON4; // 0.0001 radians = 0.00573 degrees
+ const hasTopConeRegular =
+ north > +flatConeEpsilon && north < defaultMaxLatitude;
+ const hasTopConeFlipped = north < -flatConeEpsilon;
+ const hasTopConeFlat = north >= -flatConeEpsilon && north <= +flatConeEpsilon;
+ const hasTopCone = hasTopConeRegular || hasTopConeFlipped || hasTopConeFlat;
- const hasBottomConeRegular = south <= 0.0 && south > -CesiumMath.PI_OVER_TWO;
- const hasBottomConeFlipped = south > 0.0;
- const hasBottomCone = hasBottomConeRegular || hasBottomConeFlipped;
+ const hasBottomConeRegular =
+ south > defaultMinLatitude && south < -flatConeEpsilon;
+ const hasBottomConeFlipped = south > +flatConeEpsilon;
+ const hasBottomConeFlat = south === 0.0;
+ const hasBottomCone =
+ hasBottomConeRegular || hasBottomConeFlipped || hasBottomConeFlat;
const isSphere = radii.x === radii.y && radii.y === radii.z;
@@ -408,6 +418,19 @@ VoxelEllipsoidShape.prototype.update = function (
offset,
shaderUniforms.ellipsoidLatitudeUvScaleAndOffset
);
+
+ const minAngle = hasBottomConeRegular ? -south : south;
+ const maxAngle = hasTopConeFlipped ? -north : north;
+ const minCosHalfAngle = Math.cos(
+ CesiumMath.PI_OVER_TWO - Math.abs(minAngle)
+ );
+ const maxCosHalfAngle = Math.cos(
+ CesiumMath.PI_OVER_TWO - Math.abs(maxAngle)
+ );
+ shaderUniforms.ellipsoidMinLatitudeCosSqrHalfAngle =
+ minCosHalfAngle * minCosHalfAngle;
+ shaderUniforms.ellipsoidMaxLatitudeCosSqrHalfAngle =
+ maxCosHalfAngle * maxCosHalfAngle;
}
// Intersects a cone for min latitude
@@ -421,6 +444,9 @@ VoxelEllipsoidShape.prototype.update = function (
} else if (hasBottomConeFlipped) {
shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = true;
intersectionCount += 2;
+ } else if (hasBottomConeFlat) {
+ shaderDefines["ELLIPSOID_CONE_BOTTOM_FLAT"] = true;
+ intersectionCount += 1;
}
}
@@ -435,6 +461,9 @@ VoxelEllipsoidShape.prototype.update = function (
} else if (hasTopConeFlipped) {
shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = true;
intersectionCount += 2;
+ } else if (hasTopConeFlat) {
+ shaderDefines["ELLIPSOID_CONE_TOP_FLAT"] = true;
+ intersectionCount += 1;
}
}
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 21be3c48a50..a7d86882cd7 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -221,12 +221,14 @@ uniform float u_stepSize;
#define ELLIPSOID_WEDGE_INDEX ###
#define ELLIPSOID_WEDGE_ANGLE_FLIPPED //
#define ELLIPSOID_CONE_BOTTOM
- #define ELLIPSOID_CONE_BOTTOM_REGULAR ### // when there's a bottom cone
- #define ELLIPSOID_CONE_BOTTOM_FLIPPED ### // when cone shape has two intersection intervals
+ #define ELLIPSOID_CONE_BOTTOM_REGULAR // when there's a bottom cone
+ #define ELLIPSOID_CONE_BOTTOM_FLIPPED // when cone shape has two intersection intervals
+ #define ELLIPSOID_CONE_BOTTOM_FLAT
#define ELLIPSOID_CONE_BOTTOM_INDEX ###
#define ELLIPSOID_CONE_TOP
#define ELLIPSOID_CONE_TOP_REGULAR ### // when there's a top cone
#define ELLIPSOID_CONE_TOP_FLIPPED ### // when cone shape has two intersection intervals
+ #define ELLIPSOID_CONE_TOP_FLAT
#define ELLIPSOID_CONE_TOP_INDEX ###
#define ELLIPSOID_OUTER ### // outer ellipsoid - always defined
#define ELLIPSOID_OUTER_INDEX ###
@@ -253,6 +255,12 @@ uniform float u_stepSize;
#if defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
uniform vec2 u_ellipsoidLatitudeUvScaleAndOffset;
#endif
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
+ uniform float u_ellipsoidMinLatitudeCosSqrHalfAngle;
+ #endif
+ #if defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ uniform float u_ellipsoidMaxLatitudeCosSqrHalfAngle;
+ #endif
#if defined(ELLIPSOID_INNER) && !defined(ELLIPSOID_INNER_OUTER_EQUAL)
uniform float u_ellipsoidInverseHeightDifferenceUv;
uniform float u_ellipsoidInverseInnerScaleUv;
@@ -600,6 +608,19 @@ vec2 intersectHalfSpace(Ray ray, float angle)
}
#endif
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_FLAT) || defined(ELLIPSOID_CONE_TOP_FLAT))
+vec2 intersectZPlane(Ray ray)
+{
+ float o = ray.pos.z;
+ float d = ray.dir.z;
+ float t = -o / d;
+ float s = sign(o);
+
+ if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
+ else return vec2(-INF_HIT, t);
+}
+#endif
+
#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_FLIPPED)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_FLIPPED))
vec4 intersectFlippedWedge(Ray ray, float minAngle, float maxAngle)
{
@@ -658,16 +679,14 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP))
-vec2 intersectDoubleEndedCone(Ray ray, float latitude)
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+vec2 intersectDoubleEndedCone(Ray ray, float cosSqrHalfAngle)
{
vec3 o = ray.pos;
vec3 d = ray.dir;
- float h = cos(czm_piOverTwo - abs(latitude));
- float hh = h * h;
- float a = d.z * d.z - dot(d, d) * hh;
- float b = d.z * o.z - dot(o, d) * hh;
- float c = o.z * o.z - dot(o, o) * hh;
+ float a = d.z * d.z - dot(d, d) * cosSqrHalfAngle;
+ float b = d.z * o.z - dot(o, d) * cosSqrHalfAngle;
+ float c = o.z * o.z - dot(o, o) * cosSqrHalfAngle;
float det = b * b - a * c;
if (det < 0.0) {
@@ -684,15 +703,15 @@ vec2 intersectDoubleEndedCone(Ray ray, float latitude)
#endif
#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
-vec4 intersectFlippedCone(Ray ray, float latitude) {
- vec3 o = ray.pos;
- vec3 d = ray.dir;
- vec2 intersect = intersectDoubleEndedCone(ray, latitude);
+vec4 intersectFlippedCone(Ray ray, float cosSqrHalfAngle) {
+ vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
if (intersect.x == NO_HIT) {
return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
}
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
float tmin = intersect.x;
float tmax = intersect.y;
float zmin = o.z + tmin * d.z;
@@ -708,15 +727,15 @@ vec4 intersectFlippedCone(Ray ray, float latitude) {
#endif
#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_TOP_REGULAR))
-vec2 intersectRegularCone(Ray ray, float latitude) {
- vec3 o = ray.pos;
- vec3 d = ray.dir;
- vec2 intersect = intersectDoubleEndedCone(ray, latitude);
+vec2 intersectRegularCone(Ray ray, float cosSqrHalfAngle) {
+ vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
if (intersect.x == NO_HIT) {
return vec2(NO_HIT);
}
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
float tmin = intersect.x;
float tmax = intersect.y;
float zmin = o.z + tmin * d.z;
@@ -968,7 +987,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
#endif
// Flip the ray because the intersection function expects a cone growing towards +Z.
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLAT) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
Ray flippedRay = ray;
flippedRay.dir.z *= -1.0;
flippedRay.pos.z *= -1.0;
@@ -977,28 +996,30 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
// Bottom cone
#if defined(ELLIPSOID_CONE_BOTTOM)
#if defined(ELLIPSOID_CONE_BOTTOM_REGULAR)
- float flippedSouth = -u_ellipsoidRectangle.y; // [-halfPi,+halfPi]
- vec2 bottomConeIntersection = intersectRegularCone(flippedRay, flippedSouth);
+ vec2 bottomConeIntersection = intersectRegularCone(flippedRay, u_ellipsoidMinLatitudeCosSqrHalfAngle);
setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX, bottomConeIntersection);
#elif defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
- float south = u_ellipsoidRectangle.y;
- vec4 bottomConeIntersection = intersectFlippedCone(ray, south);
+ vec4 bottomConeIntersection = intersectFlippedCone(ray, u_ellipsoidMinLatitudeCosSqrHalfAngle);
setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 0, bottomConeIntersection.xy);
setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 1, bottomConeIntersection.zw);
+ #elif defined(ELLIPSOID_CONE_BOTTOM_FLAT)
+ vec2 bottomConeIntersection = intersectZPlane(flippedRay);
+ setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX, bottomConeIntersection);
#endif
#endif
// Top cone
#if defined(ELLIPSOID_CONE_TOP)
#if defined(ELLIPSOID_CONE_TOP_REGULAR)
- float north = u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
- vec2 topConeIntersection = intersectRegularCone(ray, north);
+ vec2 topConeIntersection = intersectRegularCone(ray, u_ellipsoidMaxLatitudeCosSqrHalfAngle);
setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX, topConeIntersection);
#elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
- float flippedNorth = -u_ellipsoidRectangle.w; // [-halfPi,+halfPi]
- vec4 topConeIntersection = intersectFlippedCone(flippedRay, flippedNorth);
+ vec4 topConeIntersection = intersectFlippedCone(flippedRay, u_ellipsoidMaxLatitudeCosSqrHalfAngle);
setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 0, topConeIntersection.xy);
setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 1, topConeIntersection.zw);
+ #elif defined(ELLIPSOID_CONE_TOP_FLAT)
+ vec2 topConeIntersection = intersectZPlane(ray);
+ setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX, topConeIntersection);
#endif
#endif
From 456680aa9c1b50786964cfe91665ffb56e190d1f Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 15 Apr 2022 14:20:17 -0400
Subject: [PATCH 037/679] fixed wedge edge artifacts
---
Source/Scene/VoxelCylinderShape.js | 55 ++++++++++++++++++++++++------
Source/Shaders/VoxelFS.glsl | 24 +++++++++++--
2 files changed, 66 insertions(+), 13 deletions(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index 0b36456e389..b1a3d577020 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -102,6 +102,8 @@ function VoxelCylinderShape() {
cylinderHeightUvScaleAndOffset: new Cartesian2(),
cylinderAngleUvScaleAndOffset: new Cartesian2(),
cylinderMinAngleUv: 0.0,
+ cylinderMaxAngleUv: 0.0,
+ cylinderEmptyMidpointAngleUv: 0.0,
};
/**
@@ -121,6 +123,8 @@ function VoxelCylinderShape() {
CYLINDER_WEDGE_INDEX: undefined,
CYLINDER_WEDGE_REGULAR: undefined,
CYLINDER_WEDGE_FLIPPED: undefined,
+ CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY: undefined,
+ CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY: undefined,
};
}
@@ -190,6 +194,10 @@ VoxelCylinderShape.prototype.update = function (
const minAngle = CesiumMath.negativePiToPi(minBounds.z);
const maxAngle = CesiumMath.negativePiToPi(maxBounds.z);
+ const zeroScaleEpsilon = CesiumMath.EPSILON10;
+ const angleDiscontinuityEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
+ const wedgeDetectionEpsilon = CesiumMath.EPSILON10;
+
// Exit early if the shape is not visible.
// Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
@@ -197,14 +205,14 @@ VoxelCylinderShape.prototype.update = function (
// - maxRadius is zero (line)
// - minHeight is greater than maxHeight
// - scale is 0 for any component (too annoying to reconstruct rotation matrix)
- const absEpsilon = CesiumMath.EPSILON10;
+
if (
maxRadius === 0.0 ||
minRadius > maxRadius ||
minHeight > maxHeight ||
- CesiumMath.equalsEpsilon(scale.x, 0.0, undefined, absEpsilon) ||
- CesiumMath.equalsEpsilon(scale.y, 0.0, undefined, absEpsilon) ||
- CesiumMath.equalsEpsilon(scale.z, 0.0, undefined, absEpsilon)
+ CesiumMath.equalsEpsilon(scale.x, 0.0, undefined, zeroScaleEpsilon) ||
+ CesiumMath.equalsEpsilon(scale.y, 0.0, undefined, zeroScaleEpsilon) ||
+ CesiumMath.equalsEpsilon(scale.z, 0.0, undefined, zeroScaleEpsilon)
) {
return false;
}
@@ -246,13 +254,26 @@ VoxelCylinderShape.prototype.update = function (
minHeight === defaultMinHeight && maxHeight === defaultMaxHeight;
const isAngleFlipped = maxAngle < minAngle;
- const angleWidth = maxAngle - minAngle + isAngleFlipped * CesiumMath.TWO_PI;
+ const angleWidth = maxAngle - minAngle + isAngleFlipped * defaultAngleWidth;
const hasWedgeRegular =
angleWidth >= CesiumMath.PI &&
- CesiumMath.lessThan(angleWidth, CesiumMath.TWO_PI, absEpsilon);
+ CesiumMath.lessThan(angleWidth, defaultAngleWidth, wedgeDetectionEpsilon);
const hasWedgeFlipped = angleWidth < CesiumMath.PI;
const hasWedge = hasWedgeRegular || hasWedgeFlipped;
+ const isMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
+ minAngle,
+ defaultMinAngle,
+ undefined,
+ angleDiscontinuityEpsilon
+ );
+ const isMaxAngleDiscontinuity = CesiumMath.equalsEpsilon(
+ maxAngle,
+ defaultMaxAngle,
+ undefined,
+ angleDiscontinuityEpsilon
+ );
+
const shaderUniforms = this.shaderUniforms;
const shaderDefines = this.shaderDefines;
@@ -393,10 +414,14 @@ VoxelCylinderShape.prototype.update = function (
shaderUniforms.cylinderAngleMinMax
);
- if (isAngleFlipped) {
- const minAngleUv = (minAngle - defaultMinAngle) / defaultAngleWidth;
- shaderUniforms.cylinderMinAngleUv = minAngleUv;
- }
+ const minAngleUv = (minAngle - defaultMinAngle) / defaultAngleWidth;
+ const maxAngleUv = (maxAngle - defaultMinAngle) / defaultAngleWidth;
+ const emptyAngleWidthUv = 1.0 - angleWidth / defaultAngleWidth;
+
+ shaderUniforms.cylinderMinAngleUv = minAngleUv;
+ shaderUniforms.cylinderMaxAngleUv = maxAngleUv;
+ shaderUniforms.cylinderEmptyMidpointAngleUv =
+ (maxAngleUv + 0.5 * emptyAngleWidthUv) % 1.0;
// delerp(angleUv, minAngleUv, maxAngleUv)
// (angelUv - minAngleUv) / (maxAngleUv - minAngleUv)
@@ -420,6 +445,13 @@ VoxelCylinderShape.prototype.update = function (
shaderDefines["CYLINDER_INTERSECTION_COUNT"] = intersectionCount;
+ if (isMinAngleDiscontinuity) {
+ shaderDefines["CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY"] = true;
+ }
+ if (isMaxAngleDiscontinuity) {
+ shaderDefines["CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY"] = true;
+ }
+
return true;
};
@@ -691,7 +723,8 @@ function getCylinderChunkObb(
}
const isAngleFlipped = angleEnd < angleStart;
- const angleWidth = angleEnd - angleStart + isAngleFlipped * CesiumMath.TWO_PI;
+ const defaultAngleWidth = defaultMaxAngle - defaultMinAngle;
+ const angleWidth = angleEnd - angleStart + isAngleFlipped * defaultAngleWidth;
testAngles[testAngleCount++] = CesiumMath.negativePiToPi(
angleStart + angleWidth * 0.5
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index a7d86882cd7..59327ad6cc7 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -283,6 +283,8 @@ uniform float u_stepSize;
#define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
#define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
#define CYLINDER_WEDGE_ANGLE_FLIPPED //
+ #define CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY
+ #define CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY
*/
// Cylinder uniforms
@@ -302,9 +304,15 @@ uniform float u_stepSize;
#if defined(CYLINDER_WEDGE)
uniform vec2 u_cylinderAngleMinMax;
uniform vec2 u_cylinderAngleUvScaleAndOffset;
- #if defined(CYLINDER_WEDGE_ANGLE_FLIPPED)
+ #if defined(CYLINDER_WEDGE_ANGLE_FLIPPED) || defined(CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY) || defined(CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
+ uniform float u_cylinderEmptyMidpointAngleUv;
+ #endif
+ #if defined(CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
uniform float u_cylinderMinAngleUv;
#endif
+ #if defined(CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
+ uniform float u_cylinderMaxAngleUv;
+ #endif
#endif
#endif
@@ -1187,8 +1195,17 @@ vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
#if defined(CYLINDER_WEDGE)
#if defined(CYLINDER_WEDGE_ANGLE_FLIPPED)
- angle += float(angle <= u_cylinderMinAngleUv);
+ // Comparing against u_cylinderMinAngleUv has precision problems. u_cylinderEmptyMidpointAngleUv is more conservative.
+ angle += float(angle < u_cylinderEmptyMidpointAngleUv);
#endif
+
+ // When the min or max angle is near the -pi/+pi discontinuity there may be flickering as both sides of the voxel data are read.
+ #if defined(CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
+ angle = angle > u_cylinderEmptyMidpointAngleUv ? u_cylinderMinAngleUv : angle;
+ #elif defined(CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
+ angle = angle < u_cylinderEmptyMidpointAngleUv ? u_cylinderMaxAngleUv : angle;
+ #endif
+
angle = angle * u_cylinderAngleUvScaleAndOffset.x + u_cylinderAngleUvScaleAndOffset.y;
#endif
@@ -1581,6 +1598,9 @@ void main()
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
+ // gl_FragColor = vec4(positionUv, 1.0); return;
+ // gl_FragColor = vec4(transformFromUvToShapeSpace(positionUv).zzz, 1.0); return;
+
vec4 colorAccum = vec4(0.0);
#if defined(DESPECKLE)
From 285f3a6a23ee733c59bd853b3cf802de2e1a3388 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 15 Apr 2022 15:38:38 -0400
Subject: [PATCH 038/679] wedge artifact fixes for ellipsoid
---
Source/Scene/VoxelCylinderShape.js | 34 ++++++-----
Source/Scene/VoxelEllipsoidShape.js | 93 +++++++++++++++++++++--------
Source/Shaders/VoxelFS.glsl | 93 ++++++++++++++++++-----------
3 files changed, 145 insertions(+), 75 deletions(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index b1a3d577020..64582b30c22 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -123,6 +123,7 @@ function VoxelCylinderShape() {
CYLINDER_WEDGE_INDEX: undefined,
CYLINDER_WEDGE_REGULAR: undefined,
CYLINDER_WEDGE_FLIPPED: undefined,
+ CYLINDER_WEDGE_FLAT: undefined,
CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY: undefined,
CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY: undefined,
};
@@ -165,6 +166,7 @@ VoxelCylinderShape.prototype.update = function (
const defaultMinAngle = VoxelCylinderShape.DefaultMinBounds.z;
const defaultMaxAngle = VoxelCylinderShape.DefaultMaxBounds.z;
const defaultAngleWidth = defaultMaxAngle - defaultMinAngle;
+ const defaultHalfAngleWidth = 0.5 * defaultAngleWidth;
// Clamp the radii to the valid range
const minRadius = CesiumMath.clamp(
@@ -196,7 +198,7 @@ VoxelCylinderShape.prototype.update = function (
const zeroScaleEpsilon = CesiumMath.EPSILON10;
const angleDiscontinuityEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
- const wedgeDetectionEpsilon = CesiumMath.EPSILON10;
+ const wedgeEpsilon = CesiumMath.EPSILON10;
// Exit early if the shape is not visible.
// Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
@@ -256,10 +258,13 @@ VoxelCylinderShape.prototype.update = function (
const isAngleFlipped = maxAngle < minAngle;
const angleWidth = maxAngle - minAngle + isAngleFlipped * defaultAngleWidth;
const hasWedgeRegular =
- angleWidth >= CesiumMath.PI &&
- CesiumMath.lessThan(angleWidth, defaultAngleWidth, wedgeDetectionEpsilon);
- const hasWedgeFlipped = angleWidth < CesiumMath.PI;
- const hasWedge = hasWedgeRegular || hasWedgeFlipped;
+ angleWidth > defaultHalfAngleWidth + wedgeEpsilon &&
+ angleWidth < defaultAngleWidth - wedgeEpsilon;
+ const hasWedgeFlipped = angleWidth < defaultHalfAngleWidth - wedgeEpsilon;
+ const hasWedgeFlat =
+ angleWidth >= defaultHalfAngleWidth - wedgeEpsilon &&
+ angleWidth <= defaultHalfAngleWidth + wedgeEpsilon;
+ const hasWedge = hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat;
const isMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
minAngle,
@@ -380,10 +385,8 @@ VoxelCylinderShape.prototype.update = function (
// offset = -2.0 * (minHeight * 0.5 + 0.5) / (maxHeight - minHeight)
// offset = -(minHeight + 1.0) / (maxHeight - minHeight)
// offset = (minHeight + 1.0) / (minHeight - maxHeight)
-
const scale = 2.0 / (maxHeight - minHeight);
const offset = (minHeight + 1.0) / (minHeight - maxHeight);
-
shaderUniforms.cylinderHeightUvScaleAndOffset = Cartesian2.fromElements(
scale,
offset,
@@ -406,6 +409,16 @@ VoxelCylinderShape.prototype.update = function (
} else if (hasWedgeFlipped) {
shaderDefines["CYLINDER_WEDGE_FLIPPED"] = true;
intersectionCount += 2;
+ } else if (hasWedgeFlat) {
+ shaderDefines["CYLINDER_WEDGE_FLAT"] = true;
+ intersectionCount += 1;
+ }
+
+ if (isMinAngleDiscontinuity) {
+ shaderDefines["CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY"] = true;
+ }
+ if (isMaxAngleDiscontinuity) {
+ shaderDefines["CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY"] = true;
}
shaderUniforms.cylinderAngleMinMax = Cartesian2.fromElements(
@@ -445,13 +458,6 @@ VoxelCylinderShape.prototype.update = function (
shaderDefines["CYLINDER_INTERSECTION_COUNT"] = intersectionCount;
- if (isMinAngleDiscontinuity) {
- shaderDefines["CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY"] = true;
- }
- if (isMaxAngleDiscontinuity) {
- shaderDefines["CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY"] = true;
- }
-
return true;
};
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 2cbab24949a..e3f9abd145f 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -99,6 +99,8 @@ function VoxelEllipsoidShape() {
ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
// Wedge uniforms
ellipsoidMinLongitudeUv: 0.0,
+ ellipsoidMaxLongitudeUv: 0.0,
+ ellipsoidEmptyMidpointLongitudeUv: 0.0,
ellipsoidLongitudeUvScaleAndOffset: new Cartesian2(),
// Cone uniforms
ellipsoidLatitudeUvScaleAndOffset: new Cartesian2(),
@@ -117,10 +119,13 @@ function VoxelEllipsoidShape() {
this.shaderDefines = {
ELLIPSOID_INTERSECTION_COUNT: undefined,
ELLIPSOID_WEDGE: undefined,
+ ELLIPSOID_WEDGE_INDEX: undefined,
ELLIPSOID_WEDGE_REGULAR: undefined,
ELLIPSOID_WEDGE_FLIPPED: undefined,
- ELLIPSOID_WEDGE_INDEX: undefined,
+ ELLIPSOID_WEDGE_FLAT: undefined,
ELLIPSOID_WEDGE_ANGLE_FLIPPED: undefined,
+ ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY: undefined,
+ ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY: undefined,
ELLIPSOID_CONE_BOTTOM: undefined,
ELLIPSOID_CONE_BOTTOM_REGULAR: undefined,
ELLIPSOID_CONE_BOTTOM_FLIPPED: undefined,
@@ -167,6 +172,7 @@ VoxelEllipsoidShape.prototype.update = function (
const defaultMinLongitude = VoxelEllipsoidShape.DefaultMinBounds.x;
const defaultMaxLongitude = VoxelEllipsoidShape.DefaultMaxBounds.x;
const defaultLongitudeLength = defaultMaxLongitude - defaultMinLongitude;
+ const defaultLongitudeHalfLength = 0.5 * defaultLongitudeLength;
const defaultMinLatitude = VoxelEllipsoidShape.DefaultMinBounds.y;
const defaultMaxLatitude = VoxelEllipsoidShape.DefaultMaxBounds.y;
const defaultLatitudeLength = defaultMaxLatitude - defaultMinLatitude;
@@ -212,15 +218,20 @@ VoxelEllipsoidShape.prototype.update = function (
);
const maxExtent = Cartesian3.maximumComponent(outerExtent);
+ const zeroScaleEpsilon = CesiumMath.EPSILON10;
+ const angleDiscontinuityEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
+ const wedgeEpsilon = CesiumMath.EPSILON10;
+ const coneEpsilon = CesiumMath.EPSILON10;
+ const flatConeEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
+
// Exit early if the shape is not visible.
// Note that west may be greater than east when crossing the 180th meridian.
- const absEpsilon = CesiumMath.EPSILON10;
if (
south > north ||
minHeight > maxHeight ||
- CesiumMath.equalsEpsilon(outerExtent.x, 0.0, undefined, absEpsilon) ||
- CesiumMath.equalsEpsilon(outerExtent.y, 0.0, undefined, absEpsilon) ||
- CesiumMath.equalsEpsilon(outerExtent.z, 0.0, undefined, absEpsilon)
+ CesiumMath.equalsEpsilon(outerExtent.x, 0.0, undefined, zeroScaleEpsilon) ||
+ CesiumMath.equalsEpsilon(outerExtent.y, 0.0, undefined, zeroScaleEpsilon) ||
+ CesiumMath.equalsEpsilon(outerExtent.z, 0.0, undefined, zeroScaleEpsilon)
) {
return false;
}
@@ -295,28 +306,32 @@ VoxelEllipsoidShape.prototype.update = function (
shaderUniforms.ellipsoidInverseRadiiSquaredUv
);
- const isAngleFlipped = east < west;
- const rectangleWidth = Rectangle.computeWidth(this._rectangle);
const rectangleHeight = Rectangle.computeHeight(this._rectangle);
const hasInnerEllipsoid = !Cartesian3.equals(innerExtent, Cartesian3.ZERO);
+ const isAngleFlipped = east < west;
+ const angleWidth = east - west + isAngleFlipped * defaultLongitudeLength;
const hasWedgeRegular =
- rectangleWidth >= CesiumMath.PI &&
- CesiumMath.lessThan(rectangleWidth, CesiumMath.TWO_PI, absEpsilon);
- const hasWedgeFlipped = rectangleWidth < CesiumMath.PI;
- const hasWedge = hasWedgeRegular || hasWedgeFlipped;
+ angleWidth > defaultLongitudeHalfLength + wedgeEpsilon &&
+ angleWidth < defaultLongitudeLength - wedgeEpsilon;
+ const hasWedgeFlipped =
+ angleWidth < defaultLongitudeHalfLength - wedgeEpsilon;
+ const hasWedgeFlat =
+ angleWidth >= defaultLongitudeHalfLength - wedgeEpsilon &&
+ angleWidth <= defaultLongitudeHalfLength + wedgeEpsilon;
+ const hasWedge = hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat;
- const flatConeEpsilon = CesiumMath.EPSILON4; // 0.0001 radians = 0.00573 degrees
const hasTopConeRegular =
- north > +flatConeEpsilon && north < defaultMaxLatitude;
+ north > +flatConeEpsilon && north < defaultMaxLatitude - coneEpsilon;
const hasTopConeFlipped = north < -flatConeEpsilon;
const hasTopConeFlat = north >= -flatConeEpsilon && north <= +flatConeEpsilon;
const hasTopCone = hasTopConeRegular || hasTopConeFlipped || hasTopConeFlat;
const hasBottomConeRegular =
- south > defaultMinLatitude && south < -flatConeEpsilon;
+ south > defaultMinLatitude + coneEpsilon && south < -flatConeEpsilon;
const hasBottomConeFlipped = south > +flatConeEpsilon;
- const hasBottomConeFlat = south === 0.0;
+ const hasBottomConeFlat =
+ south >= -flatConeEpsilon && south <= +flatConeEpsilon;
const hasBottomCone =
hasBottomConeRegular || hasBottomConeFlipped || hasBottomConeFlat;
@@ -334,6 +349,10 @@ VoxelEllipsoidShape.prototype.update = function (
if (hasInnerEllipsoid) {
shaderDefines["ELLIPSOID_INNER"] = true;
shaderDefines["ELLIPSOID_INNER_INDEX"] = intersectionCount;
+ if (minHeight === maxHeight) {
+ shaderDefines["ELLIPSOID_INNER_OUTER_EQUAL"] = true;
+ }
+
intersectionCount += 1;
// The percent of space that is between the inner and outer ellipsoid.
@@ -356,10 +375,6 @@ VoxelEllipsoidShape.prototype.update = function (
shaderDefines["ELLIPSOID_IS_SPHERE"] = true;
}
- if (minHeight === maxHeight) {
- shaderDefines["ELLIPSOID_INNER_OUTER_EQUAL"] = true;
- }
-
// Intersects a wedge for the min and max longitude.
if (hasWedge) {
shaderDefines["ELLIPSOID_WEDGE"] = true;
@@ -371,7 +386,39 @@ VoxelEllipsoidShape.prototype.update = function (
} else if (hasWedgeFlipped) {
shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = true;
intersectionCount += 2;
+ } else if (hasWedgeFlat) {
+ shaderDefines["ELLIPSOID_WEDGE_FLAT"] = true;
+ intersectionCount += 1;
+ }
+
+ const isMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
+ west,
+ defaultMinLongitude,
+ undefined,
+ angleDiscontinuityEpsilon
+ );
+ const isMaxAngleDiscontinuity = CesiumMath.equalsEpsilon(
+ east,
+ defaultMaxLongitude,
+ undefined,
+ angleDiscontinuityEpsilon
+ );
+
+ if (isMinAngleDiscontinuity) {
+ shaderDefines["ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY"] = true;
}
+ if (isMaxAngleDiscontinuity) {
+ shaderDefines["ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY"] = true;
+ }
+
+ const westUv = (west - defaultMinLongitude) / defaultLongitudeLength;
+ const eastUv = (east - defaultMinLongitude) / defaultLongitudeLength;
+ const emptyAngleWidthUv = 1.0 - angleWidth / defaultLongitudeLength;
+
+ shaderUniforms.ellipsoidMinLongitudeUv = westUv;
+ shaderUniforms.ellipsoidMaxLongitudeUv = eastUv;
+ shaderUniforms.ellipsoidEmptyMidpointLongitudeUv =
+ (eastUv + 0.5 * emptyAngleWidthUv) % 1.0;
// delerp(longitudeUv, minLongitudeUv, maxLongitudeUv)
// (longitudeUv - minLongitudeUv) / (maxLongitudeUv - minLongitudeUv)
@@ -382,12 +429,8 @@ VoxelEllipsoidShape.prototype.update = function (
// offset = -minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
// offset = -((minLongitude - pi) / (2.0 * pi)) / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
// offset = -(minLongitude - pi) / (maxLongitude - minLongitude)
-
- const westUv = (west - defaultMinLongitude) / defaultLongitudeLength;
- const scale = defaultLongitudeLength / rectangleWidth;
- const offset = -(west - defaultMinLongitude) / rectangleWidth;
-
- shaderUniforms.ellipsoidMinLongitudeUv = westUv;
+ const scale = defaultLongitudeLength / angleWidth;
+ const offset = -(west - defaultMinLongitude) / angleWidth;
shaderUniforms.ellipsoidLongitudeUvScaleAndOffset = Cartesian2.fromElements(
scale,
offset,
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 59327ad6cc7..a35d70983bb 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -218,6 +218,7 @@ uniform float u_stepSize;
#define ELLIPSOID_WEDGE
#define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
#define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
+ #define ELLIPSOID_WEDGE_FLAT ###
#define ELLIPSOID_WEDGE_INDEX ###
#define ELLIPSOID_WEDGE_ANGLE_FLIPPED //
#define ELLIPSOID_CONE_BOTTOM
@@ -247,11 +248,18 @@ uniform float u_stepSize;
uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
#endif
#if defined(ELLIPSOID_WEDGE)
- uniform vec2 u_ellipsoidLongitudeUvScaleAndOffset;
- #if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
+ #if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED) || defined(ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY) || defined(ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
+ uniform float u_ellipsoidEmptyMidpointLongitudeUv;
+ #endif
+ #if defined(ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
uniform float u_ellipsoidMinLongitudeUv;
#endif
+ #if defined(ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
+ uniform float u_ellipsoidMaxLongitudeUv;
+ #endif
+ uniform vec2 u_ellipsoidLongitudeUvScaleAndOffset;
#endif
+
#if defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
uniform vec2 u_ellipsoidLatitudeUvScaleAndOffset;
#endif
@@ -282,6 +290,7 @@ uniform float u_stepSize;
#define CYLINDER_WEDGE_INDEX //
#define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
#define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
+ #define CYLINDER_WEDGE_FLAT
#define CYLINDER_WEDGE_ANGLE_FLIPPED //
#define CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY
#define CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY
@@ -564,6 +573,36 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
}
#endif
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_FLAT) || defined(ELLIPSOID_CONE_TOP_FLAT))
+vec2 intersectZPlane(Ray ray)
+{
+ float o = ray.pos.z;
+ float d = ray.dir.z;
+ float t = -o / d;
+ float s = sign(o);
+
+ if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
+ else return vec2(-INF_HIT, t);
+}
+#endif
+
+#if (defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_WEDGE_FLIPPED) || defined(ELLIPSOID_WEDGE_FLAT))) || (defined(SHAPE_CYLINDER) && (defined(CYLINDER_WEDGE_FLIPPED) || defined(CYLINDER_WEDGE_FLAT)))
+vec2 intersectHalfSpace(Ray ray, float angle)
+{
+ vec2 o = ray.pos.xy;
+ vec2 d = ray.dir.xy;
+ vec2 n = vec2(sin(angle), -cos(angle));
+
+ float a = dot(o, n);
+ float b = dot(d, n);
+ float t = -a / b;
+ float s = sign(a);
+
+ if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
+ else return vec2(-INF_HIT, t);
+}
+#endif
+
#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_REGULAR)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_REGULAR))
vec2 intersectRegularWedge(Ray ray, float minAngle, float maxAngle)
{
@@ -599,36 +638,6 @@ vec2 intersectRegularWedge(Ray ray, float minAngle, float maxAngle)
}
#endif
-#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_FLIPPED)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_FLIPPED))
-vec2 intersectHalfSpace(Ray ray, float angle)
-{
- vec2 o = ray.pos.xy;
- vec2 d = ray.dir.xy;
- vec2 n = vec2(sin(angle), -cos(angle));
-
- float a = dot(o, n);
- float b = dot(d, n);
- float t = -a / b;
- float s = sign(a);
-
- if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
- else return vec2(-INF_HIT, t);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_FLAT) || defined(ELLIPSOID_CONE_TOP_FLAT))
-vec2 intersectZPlane(Ray ray)
-{
- float o = ray.pos.z;
- float d = ray.dir.z;
- float t = -o / d;
- float s = sign(o);
-
- if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
- else return vec2(-INF_HIT, t);
-}
-#endif
-
#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_FLIPPED)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_FLIPPED))
vec4 intersectFlippedWedge(Ray ray, float minAngle, float maxAngle)
{
@@ -1042,6 +1051,9 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
vec4 wedgeIntersect = intersectFlippedWedge(ray, west, east);
setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 0, wedgeIntersect.xy);
setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 1, wedgeIntersect.zw);
+ #elif defined(ELLIPSOID_WEDGE_FLAT)
+ vec2 wedgeIntersect = intersectHalfSpace(ray, west);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX, wedgeIntersect);
#endif
#endif
}
@@ -1065,8 +1077,17 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
#if defined(ELLIPSOID_WEDGE)
#if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
- longitude += float(longitude <= u_ellipsoidMinLongitudeUv);
+ // Comparing against u_ellipsoidMinAngleUv has precision problems. u_ellipsoidEmptyMidpointAngleUv is more conservative.
+ longitude += float(longitude < u_ellipsoidEmptyMidpointAngleUv);
#endif
+
+ // When the min or max angle is near the -pi/+pi discontinuity there may be flickering as both sides of the voxel data are read.
+ #if defined(ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
+ longitude = longitude > u_ellipsoidEmptyMidpointLongitudeUv ? u_ellipsoidMinLongitudeUv : longitude;
+ #elif defined(ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
+ longitude = longitude < u_ellipsoidEmptyMidpointLongitudeUv ? u_ellipsoidMaxLongitudeUv : longitude;
+ #endif
+
longitude = longitude * u_ellipsoidLongitudeUvScaleAndOffset.x + u_ellipsoidLongitudeUvScaleAndOffset.y;
#endif
@@ -1164,6 +1185,9 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
vec4 wedgeIntersect = intersectFlippedWedge(outerRay, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, wedgeIntersect.xy);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, wedgeIntersect.zw);
+ #elif defined(CYLINDER_WEDGE_FLAT)
+ vec2 wedgeIntersect = intersectHalfSpace(ray, u_cylinderAngleMinMax.x);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
#endif
}
#endif
@@ -1598,9 +1622,6 @@ void main()
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
- // gl_FragColor = vec4(positionUv, 1.0); return;
- // gl_FragColor = vec4(transformFromUvToShapeSpace(positionUv).zzz, 1.0); return;
-
vec4 colorAccum = vec4(0.0);
#if defined(DESPECKLE)
From b8ae8fb27afe8d7654f0b89927bcb35f00e8f741 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Fri, 15 Apr 2022 16:45:48 -0400
Subject: [PATCH 039/679] fixed empty wedge for cylinder and ellipsoid
---
Source/Scene/VoxelCylinderShape.js | 16 +++++----
Source/Scene/VoxelEllipsoidShape.js | 11 +++++--
Source/Shaders/VoxelFS.glsl | 51 +++++++++++++++++++++++------
3 files changed, 59 insertions(+), 19 deletions(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index 64582b30c22..df561591a43 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -124,6 +124,7 @@ function VoxelCylinderShape() {
CYLINDER_WEDGE_REGULAR: undefined,
CYLINDER_WEDGE_FLIPPED: undefined,
CYLINDER_WEDGE_FLAT: undefined,
+ CYLINDER_WEDGE_EMPTY: undefined,
CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY: undefined,
CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY: undefined,
};
@@ -260,11 +261,15 @@ VoxelCylinderShape.prototype.update = function (
const hasWedgeRegular =
angleWidth > defaultHalfAngleWidth + wedgeEpsilon &&
angleWidth < defaultAngleWidth - wedgeEpsilon;
- const hasWedgeFlipped = angleWidth < defaultHalfAngleWidth - wedgeEpsilon;
+ const hasWedgeFlipped =
+ angleWidth > wedgeEpsilon &&
+ angleWidth < defaultHalfAngleWidth - wedgeEpsilon;
const hasWedgeFlat =
angleWidth >= defaultHalfAngleWidth - wedgeEpsilon &&
angleWidth <= defaultHalfAngleWidth + wedgeEpsilon;
- const hasWedge = hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat;
+ const hasWedgeEmpty = angleWidth <= wedgeEpsilon;
+ const hasWedge =
+ hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat || hasWedgeEmpty;
const isMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
minAngle,
@@ -356,10 +361,8 @@ VoxelCylinderShape.prototype.update = function (
// scale = 1.0 / (maxRadius - minRadius)
// offset = -minRadius / (maxRadius - minRadius)
// offset = minRadius / (minRadius - maxRadius)
-
const scale = 1.0 / (maxRadius - minRadius);
const offset = minRadius / (minRadius - maxRadius);
-
shaderUniforms.cylinderRadiusUvScaleAndOffset = Cartesian2.fromElements(
scale,
offset,
@@ -412,6 +415,9 @@ VoxelCylinderShape.prototype.update = function (
} else if (hasWedgeFlat) {
shaderDefines["CYLINDER_WEDGE_FLAT"] = true;
intersectionCount += 1;
+ } else if (hasWedgeEmpty) {
+ shaderDefines["CYLINDER_WEDGE_EMPTY"] = true;
+ intersectionCount += 2;
}
if (isMinAngleDiscontinuity) {
@@ -445,10 +451,8 @@ VoxelCylinderShape.prototype.update = function (
// offset = -minAngleUv / (maxAngleUv - minAngleUv)
// offset = -((minAngle - pi) / (2.0 * pi)) / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
// offset = -(minAngle - pi) / (maxAngle - minAngle)
-
const scale = defaultAngleWidth / angleWidth;
const offset = -(minAngle - defaultMinAngle) / angleWidth;
-
shaderUniforms.cylinderAngleUvScaleAndOffset = Cartesian2.fromElements(
scale,
offset,
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index e3f9abd145f..03d1a847c98 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -123,6 +123,7 @@ function VoxelEllipsoidShape() {
ELLIPSOID_WEDGE_REGULAR: undefined,
ELLIPSOID_WEDGE_FLIPPED: undefined,
ELLIPSOID_WEDGE_FLAT: undefined,
+ ELLIPSOID_WEDGE_EMPTY: undefined,
ELLIPSOID_WEDGE_ANGLE_FLIPPED: undefined,
ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY: undefined,
ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY: undefined,
@@ -315,11 +316,14 @@ VoxelEllipsoidShape.prototype.update = function (
angleWidth > defaultLongitudeHalfLength + wedgeEpsilon &&
angleWidth < defaultLongitudeLength - wedgeEpsilon;
const hasWedgeFlipped =
+ angleWidth > wedgeEpsilon &&
angleWidth < defaultLongitudeHalfLength - wedgeEpsilon;
const hasWedgeFlat =
angleWidth >= defaultLongitudeHalfLength - wedgeEpsilon &&
angleWidth <= defaultLongitudeHalfLength + wedgeEpsilon;
- const hasWedge = hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat;
+ const hasWedgeEmpty = angleWidth <= wedgeEpsilon;
+ const hasWedge =
+ hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat || hasWedgeEmpty;
const hasTopConeRegular =
north > +flatConeEpsilon && north < defaultMaxLatitude - coneEpsilon;
@@ -389,6 +393,9 @@ VoxelEllipsoidShape.prototype.update = function (
} else if (hasWedgeFlat) {
shaderDefines["ELLIPSOID_WEDGE_FLAT"] = true;
intersectionCount += 1;
+ } else if (hasWedgeEmpty) {
+ shaderDefines["ELLIPSOID_WEDGE_EMPTY"] = true;
+ intersectionCount += 2;
}
const isMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
@@ -452,10 +459,8 @@ VoxelEllipsoidShape.prototype.update = function (
// offset = -minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
// offset = -((minLatitude - pi) / (2.0 * pi)) / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
// offset = -(minLatitude - pi) / (maxLatitude - minLatitude)
-
const scale = defaultLatitudeLength / rectangleHeight;
const offset = -(south - defaultMinLatitude) / rectangleHeight;
-
shaderUniforms.ellipsoidLatitudeUvScaleAndOffset = Cartesian2.fromElements(
scale,
offset,
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index a35d70983bb..60243c57f9b 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -216,10 +216,11 @@ uniform float u_stepSize;
/* Ellipsoid defines:
#define ELLIPSOID_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
#define ELLIPSOID_WEDGE
+ #define ELLIPSOID_WEDGE_INDEX ###
#define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
#define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
#define ELLIPSOID_WEDGE_FLAT ###
- #define ELLIPSOID_WEDGE_INDEX ###
+ #define ELLIPSOID_WEDGE_EMPTY
#define ELLIPSOID_WEDGE_ANGLE_FLIPPED //
#define ELLIPSOID_CONE_BOTTOM
#define ELLIPSOID_CONE_BOTTOM_REGULAR // when there's a bottom cone
@@ -291,6 +292,7 @@ uniform float u_stepSize;
#define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
#define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
#define CYLINDER_WEDGE_FLAT
+ #define CYLINDER_WEDGE_EMPTY
#define CYLINDER_WEDGE_ANGLE_FLIPPED //
#define CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY
#define CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY
@@ -586,6 +588,25 @@ vec2 intersectZPlane(Ray ray)
}
#endif
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_EMPTY)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_EMPTY))
+vec4 intersectHalfPlane(Ray ray, float angle) {
+ vec2 o = ray.pos.xy;
+ vec2 d = ray.dir.xy;
+ vec2 planeDirection = vec2(cos(angle), sin(angle));
+ vec2 planeNormal = vec2(planeDirection.y, -planeDirection.x);
+
+ float a = dot(o, planeNormal);
+ float b = dot(d, planeNormal);
+ float t = -a / b;
+
+ vec2 p = o + t * d;
+ bool outside = dot(p, planeDirection) < 0.0;
+ if (outside) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
+
+ return vec4(-INF_HIT, t, t, +INF_HIT);
+}
+#endif
+
#if (defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_WEDGE_FLIPPED) || defined(ELLIPSOID_WEDGE_FLAT))) || (defined(SHAPE_CYLINDER) && (defined(CYLINDER_WEDGE_FLIPPED) || defined(CYLINDER_WEDGE_FLAT)))
vec2 intersectHalfSpace(Ray ray, float angle)
{
@@ -1054,6 +1075,10 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
#elif defined(ELLIPSOID_WEDGE_FLAT)
vec2 wedgeIntersect = intersectHalfSpace(ray, west);
setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX, wedgeIntersect);
+ #elif defined(ELLIPSOID_WEDGE_EMPTY)
+ vec4 wedgeIntersect = intersectHalfPlane(ray, west);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 1, wedgeIntersect.zw);
#endif
#endif
}
@@ -1078,7 +1103,7 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
#if defined(ELLIPSOID_WEDGE)
#if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
// Comparing against u_ellipsoidMinAngleUv has precision problems. u_ellipsoidEmptyMidpointAngleUv is more conservative.
- longitude += float(longitude < u_ellipsoidEmptyMidpointAngleUv);
+ longitude += float(longitude < u_ellipsoidEmptyMidpointLongitudeUv);
#endif
// When the min or max angle is near the -pi/+pi discontinuity there may be flickering as both sides of the voxel data are read.
@@ -1131,17 +1156,19 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
void intersectCylinderShape(Ray ray, inout Intersections ix)
{
#if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER) || defined(CYLINDER_HEIGHT_NON_DEFAULT)
- Ray outerRay = Ray(ray.pos * u_cylinderScaleUvToBounds + u_cylinderTranslateUvToBounds, ray.dir * u_cylinderScaleUvToBounds);
+ ray.pos = ray.pos * u_cylinderScaleUvToBounds + u_cylinderTranslateUvToBounds;
+ ray.dir *= u_cylinderScaleUvToBounds;
#else
// Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
// Direction is scaled as well to be in sync with position.
- Ray outerRay = Ray(ray.pos * 2.0 - 1.0, ray.dir * 2.0);
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir *= 2.0;
#endif
#if defined(CYLINDER_HEIGHT_ZERO)
- vec2 outerIntersect = intersectUnitCircle(outerRay);
+ vec2 outerIntersect = intersectUnitCircle(ray);
#else
- vec2 outerIntersect = intersectUnitCylinder(outerRay);
+ vec2 outerIntersect = intersectUnitCylinder(ray);
#endif
setIntersectionPair(ix, CYLINDER_OUTER_INDEX, outerIntersect);
@@ -1167,27 +1194,31 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
// Note: If initializeIntersections() changes its sorting function
// from bubble sort to something else, this code may need to change.
- vec2 innerIntersect = intersectInfiniteUnitCylinder(outerRay);
+ vec2 innerIntersect = intersectInfiniteUnitCylinder(ray);
setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
#elif defined(CYLINDER_INNER)
- Ray innerRay = Ray(outerRay.pos * u_cylinderInverseInnerRadiusUv, outerRay.dir * u_cylinderInverseInnerRadiusUv);
+ Ray innerRay = Ray(ray.pos * u_cylinderInverseInnerRadiusUv, ray.dir * u_cylinderInverseInnerRadiusUv);
vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
#endif
#if defined(CYLINDER_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectRegularWedge(outerRay, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
+ vec2 wedgeIntersect = intersectRegularWedge(ray, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
#elif defined(CYLINDER_WEDGE_FLIPPED)
- vec4 wedgeIntersect = intersectFlippedWedge(outerRay, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
+ vec4 wedgeIntersect = intersectFlippedWedge(ray, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, wedgeIntersect.xy);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, wedgeIntersect.zw);
#elif defined(CYLINDER_WEDGE_FLAT)
vec2 wedgeIntersect = intersectHalfSpace(ray, u_cylinderAngleMinMax.x);
setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
+ #elif defined(CYLINDER_WEDGE_EMPTY)
+ vec4 wedgeIntersect = intersectHalfPlane(ray, u_cylinderAngleMinMax.x);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, wedgeIntersect.zw);
#endif
}
#endif
From 7b9cfffe621ab3427d519adf1e5fff9141938d90 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 21 Apr 2022 13:40:44 -0400
Subject: [PATCH 040/679] shape space clipping
---
Source/Scene/VoxelBoxShape.js | 200 +++--
Source/Scene/VoxelCylinderShape.js | 445 ++++++----
Source/Scene/VoxelEllipsoidShape.js | 794 ++++++++++++------
Source/Scene/VoxelPrimitive.js | 214 ++---
Source/Scene/VoxelShape.js | 9 +
Source/Shaders/VoxelFS.glsl | 586 +++++++------
.../VoxelInspector/VoxelInspectorViewModel.js | 3 +-
7 files changed, 1310 insertions(+), 941 deletions(-)
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index 301524456b1..782caeb50c7 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -75,11 +75,11 @@ function VoxelBoxShape() {
* @readonly
*/
this.shaderUniforms = {
- boxScaleUvToBounds: new Cartesian3(),
- boxOffsetUvToBounds: new Cartesian3(),
- boxTransformUvToBounds: new Matrix4(),
- boxScaleUvToBoundsUv: new Cartesian3(),
- boxOffsetUvToBoundsUv: new Cartesian3(),
+ boxTransformUvToRenderBounds: new Matrix4(),
+ boxScaleUvToRenderBounds: new Cartesian3(),
+ boxOffsetUvToRenderBounds: new Cartesian3(),
+ boxScaleUvToShapeBoundsUv: new Cartesian3(),
+ boxOffsetUvToShapeBoundsUv: new Cartesian3(),
};
/**
@@ -87,11 +87,18 @@ function VoxelBoxShape() {
* @readonly
*/
this.shaderDefines = {
- BOX_INTERSECTION_COUNT: undefined,
BOX_INTERSECTION_INDEX: undefined,
- BOX_IS_BOUNDED: undefined,
- BOX_IS_RECTANGLE: undefined,
+ BOX_HAS_RENDER_BOUND: undefined,
+ BOX_HAS_SHAPE_BOUND: undefined,
+ BOX_IS_2D: undefined,
};
+
+ /**
+ * The maximum number of intersections against the shape for any ray direction.
+ * @type {Number}
+ * @readonly
+ */
+ this.shaderMaximumIntersectionsLength = undefined;
}
const scratchTranslation = new Cartesian3();
@@ -100,6 +107,10 @@ const scratchRotation = new Matrix3();
const scratchTransformLocalToBounds = new Matrix4();
const scratchBoundsTranslation = new Cartesian3();
const scratchBoundsScale = new Cartesian3();
+const scratchClipMinBounds = new Cartesian3();
+const scratchClipMaxBounds = new Cartesian3();
+const scratchRenderMinBounds = new Cartesian3();
+const scratchRenderMaxBounds = new Cartesian3();
const transformUvToLocal = Matrix4.fromRotationTranslation(
Matrix3.fromUniformScale(2.0, new Matrix3()),
@@ -129,13 +140,23 @@ const transformXYZToXZY = Matrix4.fromRotation(
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
+ * @param {Cartesian3} clipMinBounds The minimum clip bounds.
+ * @param {Cartesian3} clipMaxBounds The maximum clip bounds.
* @returns {Boolean} Whether the shape is visible.
*/
-VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
+VoxelBoxShape.prototype.update = function (
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("modelMatrix", modelMatrix);
Check.typeOf.object("minBounds", minBounds);
Check.typeOf.object("maxBounds", maxBounds);
+ Check.typeOf.object("clipMinBounds", clipMinBounds);
+ Check.typeOf.object("clipMaxBounds", clipMaxBounds);
//>>includeEnd('debug');
const defaultMinBounds = VoxelBoxShape.DefaultMinBounds;
@@ -155,19 +176,55 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
this._maxBounds
);
+ clipMinBounds = Cartesian3.clamp(
+ clipMinBounds,
+ defaultMinBounds,
+ defaultMaxBounds,
+ scratchClipMinBounds
+ );
+
+ clipMaxBounds = Cartesian3.clamp(
+ clipMaxBounds,
+ defaultMinBounds,
+ defaultMaxBounds,
+ scratchClipMaxBounds
+ );
+
+ const renderMinBounds = Cartesian3.clamp(
+ minBounds,
+ clipMinBounds,
+ clipMaxBounds,
+ scratchRenderMinBounds
+ );
+
+ const renderMaxBounds = Cartesian3.clamp(
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds,
+ scratchRenderMaxBounds
+ );
+
const scale = Matrix4.getScale(modelMatrix, scratchScale);
// Box is not visible if:
// - any of the min bounds exceed the max bounds
// - two or more of the min bounds equal the max bounds (line / point)
+ // - same as above, but for clip bounds
// - scale is 0 for any component (too annoying to reconstruct rotation matrix)
if (
- minBounds.x > maxBounds.x ||
- minBounds.y > maxBounds.y ||
- minBounds.z > maxBounds.z ||
- (minBounds.x === maxBounds.x) +
- (minBounds.y === maxBounds.y) +
- (minBounds.z === maxBounds.z) >=
+ renderMinBounds.x > renderMaxBounds.x ||
+ renderMinBounds.y > renderMaxBounds.y ||
+ renderMinBounds.z > renderMaxBounds.z ||
+ (renderMinBounds.x === renderMaxBounds.x) +
+ (renderMinBounds.y === renderMaxBounds.y) +
+ (renderMinBounds.z === renderMaxBounds.z) >=
+ 2 ||
+ clipMinBounds.x > clipMaxBounds.x ||
+ clipMinBounds.y > clipMaxBounds.y ||
+ clipMinBounds.z > clipMaxBounds.z ||
+ (clipMinBounds.x === clipMaxBounds.x) +
+ (clipMinBounds.y === clipMaxBounds.y) +
+ (clipMinBounds.z === clipMaxBounds.z) >=
2 ||
scale.x === 0.0 ||
scale.y === 0.0 ||
@@ -179,8 +236,8 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
this.orientedBoundingBox = getBoxChunkObb(
- this._minBounds,
- this._maxBounds,
+ renderMinBounds,
+ renderMaxBounds,
this.shapeTransform,
this.orientedBoundingBox
);
@@ -208,60 +265,69 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
}
}
- // Never changes
- shaderDefines["BOX_INTERSECTION_COUNT"] = 1;
- shaderDefines["BOX_INTERSECTION_INDEX"] = 0;
+ const hasRenderBound =
+ renderMinBounds.x !== defaultMinBounds.x ||
+ renderMaxBounds.x !== defaultMaxBounds.x ||
+ renderMinBounds.y !== defaultMinBounds.y ||
+ renderMaxBounds.y !== defaultMaxBounds.y ||
+ renderMinBounds.z !== defaultMinBounds.z ||
+ renderMaxBounds.z !== defaultMaxBounds.z;
- if (
+ const hasShapeBound =
minBounds.x !== defaultMinBounds.x ||
- minBounds.y !== defaultMinBounds.y ||
- minBounds.z !== defaultMinBounds.z ||
maxBounds.x !== defaultMaxBounds.x ||
+ minBounds.y !== defaultMinBounds.y ||
maxBounds.y !== defaultMaxBounds.y ||
- maxBounds.z !== defaultMaxBounds.z
- ) {
- shaderDefines["BOX_IS_BOUNDED"] = true;
+ minBounds.z !== defaultMinBounds.z ||
+ maxBounds.z !== defaultMaxBounds.z;
+
+ let intersectionCount = 0;
+
+ shaderDefines["BOX_INTERSECTION_INDEX"] = intersectionCount;
+ intersectionCount += 1;
+
+ if (hasRenderBound) {
+ shaderDefines["BOX_HAS_RENDER_BOUND"] = true;
+
+ const min = renderMinBounds;
+ const max = renderMaxBounds;
// inverse(scale)
// inverse(maxBoundsUv - minBoundsUv)
// inverse((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
// inverse(0.5 * (maxBounds - minBounds))
// 2.0 / (maxBounds - minBounds) // with divide by zero protection
- const boundsScaleLocalToBounds = Cartesian3.fromElements(
- 2.0 / (minBounds.x === maxBounds.x ? 1.0 : maxBounds.x - minBounds.x),
- 2.0 / (minBounds.y === maxBounds.y ? 1.0 : maxBounds.y - minBounds.y),
- 2.0 / (minBounds.z === maxBounds.z ? 1.0 : maxBounds.z - minBounds.z),
+ const scaleLocalToBounds = Cartesian3.fromElements(
+ 2.0 / (min.x === max.x ? 1.0 : max.x - min.x),
+ 2.0 / (min.y === max.y ? 1.0 : max.y - min.y),
+ 2.0 / (min.z === max.z ? 1.0 : max.z - min.z),
scratchBoundsScale
);
// -inverse(scale) * translation // affine inverse
// -inverse(scale) * 0.5 * (minBounds + maxBounds)
- const boundsTranslateLocalToBounds = Cartesian3.fromElements(
- -boundsScaleLocalToBounds.x * 0.5 * (minBounds.x + maxBounds.x),
- -boundsScaleLocalToBounds.y * 0.5 * (minBounds.y + maxBounds.y),
- -boundsScaleLocalToBounds.z * 0.5 * (minBounds.z + maxBounds.z),
+ const translateLocalToBounds = Cartesian3.fromElements(
+ -scaleLocalToBounds.x * 0.5 * (min.x + max.x),
+ -scaleLocalToBounds.y * 0.5 * (min.y + max.y),
+ -scaleLocalToBounds.z * 0.5 * (min.z + max.z),
scratchBoundsTranslation
);
let transformLocalToBounds = Matrix4.fromRotationTranslation(
- Matrix3.fromScale(boundsScaleLocalToBounds),
- boundsTranslateLocalToBounds,
+ Matrix3.fromScale(scaleLocalToBounds),
+ translateLocalToBounds,
scratchTransformLocalToBounds
);
- if (
- minBounds.x === maxBounds.x ||
- minBounds.y === maxBounds.y ||
- minBounds.z === maxBounds.z
- ) {
- shaderDefines["BOX_IS_RECTANGLE"] = true;
+ if (min.x === max.x || min.y === max.y || min.z === max.z) {
+ shaderDefines["BOX_IS_2D"] = true;
let transformAxisConversion;
- if (minBounds.x === maxBounds.x) {
+ if (min.x === max.x) {
transformAxisConversion = transformXYZToZYX;
- } else if (minBounds.y === maxBounds.y) {
+ } else if (min.y === max.y) {
transformAxisConversion = transformXYZToXZY;
- } else if (minBounds.z === maxBounds.z) {
+ } else if (min.z === max.z) {
transformAxisConversion = Matrix4.IDENTITY;
}
transformLocalToBounds = Matrix4.multiply(
@@ -271,19 +337,26 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
);
}
- shaderUniforms.boxTransformUvToBounds = Matrix4.multiplyTransformation(
+ shaderUniforms.boxTransformUvToRenderBounds = Matrix4.multiplyTransformation(
transformLocalToBounds,
transformUvToLocal,
- shaderUniforms.boxTransformUvToBounds
+ shaderUniforms.boxTransformUvToRenderBounds
);
- shaderUniforms.boxScaleUvToBounds = Matrix4.getScale(
- shaderUniforms.boxTransformUvToBounds,
- shaderUniforms.boxScaleUvToBounds
+ shaderUniforms.boxScaleUvToRenderBounds = Matrix4.getScale(
+ shaderUniforms.boxTransformUvToRenderBounds,
+ shaderUniforms.boxScaleUvToRenderBounds
);
- shaderUniforms.boxOffsetUvToBounds = Matrix4.getTranslation(
- shaderUniforms.boxTransformUvToBounds,
- shaderUniforms.boxOffsetUvToBounds
+ shaderUniforms.boxOffsetUvToRenderBounds = Matrix4.getTranslation(
+ shaderUniforms.boxTransformUvToRenderBounds,
+ shaderUniforms.boxOffsetUvToRenderBounds
);
+ }
+
+ if (hasShapeBound) {
+ shaderDefines["BOX_HAS_SHAPE_BOUND"] = true;
+
+ const min = minBounds;
+ const max = maxBounds;
// Go from UV space to bounded UV space:
// delerp(posUv, minBoundsUv, maxBoundsUv)
@@ -295,18 +368,23 @@ VoxelBoxShape.prototype.update = function (modelMatrix, minBounds, maxBounds) {
// offset = -minBoundsUv / ((maxBounds * 0.5 + 0.5) - (minBounds * 0.5 + 0.5))
// offset = -2.0 * (minBounds * 0.5 + 0.5) / (maxBounds - minBounds)
// offset = -scale * (minBounds * 0.5 + 0.5)
- shaderUniforms.boxScaleUvToBoundsUv = Cartesian3.clone(
- boundsScaleLocalToBounds,
- shaderUniforms.boxScaleUvToBoundsUv
+ shaderUniforms.boxScaleUvToShapeBoundsUv = Cartesian3.fromElements(
+ 2.0 / (min.x === max.x ? 1.0 : max.x - min.x),
+ 2.0 / (min.y === max.y ? 1.0 : max.y - min.y),
+ 2.0 / (min.z === max.z ? 1.0 : max.z - min.z),
+ shaderUniforms.boxScaleUvToShapeBoundsUv
);
- shaderUniforms.boxOffsetUvToBoundsUv = Cartesian3.fromElements(
- -boundsScaleLocalToBounds.x * (minBounds.x * 0.5 + 0.5),
- -boundsScaleLocalToBounds.y * (minBounds.y * 0.5 + 0.5),
- -boundsScaleLocalToBounds.z * (minBounds.z * 0.5 + 0.5),
- shaderUniforms.boxOffsetUvToBoundsUv
+
+ shaderUniforms.boxOffsetUvToShapeBoundsUv = Cartesian3.fromElements(
+ -shaderUniforms.boxScaleUvToShapeBoundsUv.x * (min.x * 0.5 + 0.5),
+ -shaderUniforms.boxScaleUvToShapeBoundsUv.y * (min.y * 0.5 + 0.5),
+ -shaderUniforms.boxScaleUvToShapeBoundsUv.z * (min.z * 0.5 + 0.5),
+ shaderUniforms.boxOffsetUvToShapeBoundsUv
);
}
+ this.shaderMaximumIntersectionsLength = intersectionCount;
+
return true;
};
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index df561591a43..6cf44efe5ba 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -94,16 +94,15 @@ function VoxelCylinderShape() {
* @readonly
*/
this.shaderUniforms = {
- cylinderScaleUvToBounds: new Cartesian3(),
- cylinderTranslateUvToBounds: new Cartesian3(),
- cylinderInverseInnerRadiusUv: 0.0,
- cylinderAngleMinMax: new Cartesian2(),
- cylinderRadiusUvScaleAndOffset: new Cartesian2(),
- cylinderHeightUvScaleAndOffset: new Cartesian2(),
- cylinderAngleUvScaleAndOffset: new Cartesian2(),
- cylinderMinAngleUv: 0.0,
- cylinderMaxAngleUv: 0.0,
- cylinderEmptyMidpointAngleUv: 0.0,
+ cylinderUvToRenderBoundsScale: new Cartesian3(),
+ cylinderUvToRenderBoundsTranslate: new Cartesian3(),
+ cylinderUvToRenderRadiusMin: 0.0,
+ cylinderRenderAngleMinMax: new Cartesian2(),
+ cylinderUvToShapeUvRadius: new Cartesian2(),
+ cylinderUvToShapeUvHeight: new Cartesian2(),
+ cylinderUvToShapeUvAngle: new Cartesian2(),
+ cylinderShapeUvAngleMinMax: new Cartesian2(),
+ cylinderShapeUvAngleEmptyMid: 0.0,
};
/**
@@ -111,23 +110,38 @@ function VoxelCylinderShape() {
* @readonly
*/
this.shaderDefines = {
- CYLINDER_INTERSECTION_COUNT: undefined,
- CYLINDER_OUTER_INDEX: undefined,
- CYLINDER_OUTER_NON_DEFAULT: undefined,
- CYLINDER_INNER: undefined,
- CYLINDER_INNER_OUTER_EQUAL: undefined,
- CYLINDER_INNER_INDEX: undefined,
- CYLINDER_HEIGHT_NON_DEFAULT: undefined,
- CYLINDER_HEIGHT_ZERO: undefined,
- CYLINDER_WEDGE_ANGLE_FLIPPED: undefined,
- CYLINDER_WEDGE_INDEX: undefined,
- CYLINDER_WEDGE_REGULAR: undefined,
- CYLINDER_WEDGE_FLIPPED: undefined,
- CYLINDER_WEDGE_FLAT: undefined,
- CYLINDER_WEDGE_EMPTY: undefined,
- CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY: undefined,
- CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_HEIGHT: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF: undefined,
+ CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO: undefined,
+
+ CYLINDER_HAS_SHAPE_BOUNDS_RADIUS: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_ANGLE: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY: undefined,
+ CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED: undefined,
+
+ CYLINDER_INTERSECTION_INDEX_RADIUS_MAX: undefined,
+ CYLINDER_INTERSECTION_INDEX_RADIUS_MIN: undefined,
+ CYLINDER_INTERSECTION_INDEX_ANGLE: undefined,
};
+
+ /**
+ * The maximum number of intersections against the shape for any ray direction.
+ * @type {Number}
+ * @readonly
+ */
+ this.shaderMaximumIntersectionsLength = 0; // not known until update
}
const scratchScale = new Cartesian3();
@@ -147,16 +161,22 @@ const transformUvToLocal = Matrix4.fromRotationTranslation(
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
+ * @param {Cartesian3} clipMinBounds The minimum clip bounds.
+ * @param {Cartesian3} clipMaxBounds The maximum clip bounds.
*/
VoxelCylinderShape.prototype.update = function (
modelMatrix,
minBounds,
- maxBounds
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("modelMatrix", modelMatrix);
Check.typeOf.object("minBounds", minBounds);
Check.typeOf.object("maxBounds", maxBounds);
+ Check.typeOf.object("clipMinBounds", clipMinBounds);
+ Check.typeOf.object("clipMaxBounds", clipMaxBounds);
//>>includeEnd('debug');
const scale = Matrix4.getScale(modelMatrix, scratchScale);
@@ -169,37 +189,65 @@ VoxelCylinderShape.prototype.update = function (
const defaultAngleWidth = defaultMaxAngle - defaultMinAngle;
const defaultHalfAngleWidth = 0.5 * defaultAngleWidth;
+ const zeroScaleEpsilon = CesiumMath.EPSILON10;
+ const angleDiscontinuityEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
+ const angleEpsilon = CesiumMath.EPSILON10;
+
// Clamp the radii to the valid range
- const minRadius = CesiumMath.clamp(
+ const shapeMinRadius = CesiumMath.clamp(
minBounds.x,
defaultMinRadius,
defaultMaxRadius
);
- const maxRadius = CesiumMath.clamp(
+ const shapeMaxRadius = CesiumMath.clamp(
maxBounds.x,
defaultMinRadius,
defaultMaxRadius
);
+ const clipMinRadius = CesiumMath.clamp(
+ clipMinBounds.x,
+ defaultMinRadius,
+ defaultMaxRadius
+ );
+ const clipMaxRadius = CesiumMath.clamp(
+ clipMaxBounds.x,
+ defaultMinRadius,
+ defaultMaxRadius
+ );
+ const renderMinRadius = Math.max(shapeMinRadius, clipMinRadius);
+ const renderMaxRadius = Math.min(shapeMaxRadius, clipMaxRadius);
// Clamp the heights to the valid range
- const minHeight = CesiumMath.clamp(
+ const shapeMinHeight = CesiumMath.clamp(
minBounds.y,
defaultMinHeight,
defaultMaxHeight
);
- const maxHeight = CesiumMath.clamp(
+ const shapeMaxHeight = CesiumMath.clamp(
maxBounds.y,
defaultMinHeight,
defaultMaxHeight
);
+ const clipMinHeight = CesiumMath.clamp(
+ clipMinBounds.y,
+ defaultMinHeight,
+ defaultMaxHeight
+ );
+ const clipMaxHeight = CesiumMath.clamp(
+ clipMaxBounds.y,
+ defaultMinHeight,
+ defaultMaxHeight
+ );
+ const renderMinHeight = Math.max(shapeMinHeight, clipMinHeight);
+ const renderMaxHeight = Math.min(shapeMaxHeight, clipMaxHeight);
// Clamp the angles to the valid range
- const minAngle = CesiumMath.negativePiToPi(minBounds.z);
- const maxAngle = CesiumMath.negativePiToPi(maxBounds.z);
-
- const zeroScaleEpsilon = CesiumMath.EPSILON10;
- const angleDiscontinuityEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
- const wedgeEpsilon = CesiumMath.EPSILON10;
+ const shapeMinAngle = CesiumMath.negativePiToPi(minBounds.z);
+ const shapeMaxAngle = CesiumMath.negativePiToPi(maxBounds.z);
+ const clipMinAngle = CesiumMath.negativePiToPi(clipMinBounds.z);
+ const clipMaxAngle = CesiumMath.negativePiToPi(clipMaxBounds.z);
+ const renderMinAngle = Math.max(shapeMinAngle, clipMinAngle);
+ const renderMaxAngle = Math.min(shapeMaxAngle, clipMaxAngle);
// Exit early if the shape is not visible.
// Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
@@ -210,9 +258,9 @@ VoxelCylinderShape.prototype.update = function (
// - scale is 0 for any component (too annoying to reconstruct rotation matrix)
if (
- maxRadius === 0.0 ||
- minRadius > maxRadius ||
- minHeight > maxHeight ||
+ renderMaxRadius === 0.0 ||
+ renderMinRadius > renderMaxRadius ||
+ renderMinHeight > renderMaxHeight ||
CesiumMath.equalsEpsilon(scale.x, 0.0, undefined, zeroScaleEpsilon) ||
CesiumMath.equalsEpsilon(scale.y, 0.0, undefined, zeroScaleEpsilon) ||
CesiumMath.equalsEpsilon(scale.z, 0.0, undefined, zeroScaleEpsilon)
@@ -220,22 +268,22 @@ VoxelCylinderShape.prototype.update = function (
return false;
}
- this._minimumRadius = minRadius; // [0,1]
- this._maximumRadius = maxRadius; // [0,1]
- this._minimumHeight = minHeight; // [-1,+1]
- this._maximumHeight = maxHeight; // [-1,+1]
- this._minimumAngle = minAngle; // [-halfPi,+halfPi]
- this._maximumAngle = maxAngle; // [-halfPi,+halfPi]
+ this._minimumRadius = shapeMinRadius; // [0,1]
+ this._maximumRadius = shapeMaxRadius; // [0,1]
+ this._minimumHeight = shapeMinHeight; // [-1,+1]
+ this._maximumHeight = shapeMaxHeight; // [-1,+1]
+ this._minimumAngle = shapeMinAngle; // [-halfPi,+halfPi]
+ this._maximumAngle = shapeMaxAngle; // [-halfPi,+halfPi]
this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
this.orientedBoundingBox = getCylinderChunkObb(
- minRadius,
- maxRadius,
- minHeight,
- maxHeight,
- minAngle,
- maxAngle,
+ renderMinRadius,
+ renderMaxRadius,
+ renderMinHeight,
+ renderMaxHeight,
+ renderMinAngle,
+ renderMaxAngle,
this.shapeTransform,
this.orientedBoundingBox
);
@@ -251,39 +299,68 @@ VoxelCylinderShape.prototype.update = function (
this.boundingSphere
);
- const isDefaultOuterCylinder = maxRadius === defaultMaxRadius;
- const hasInnerCylinder = minRadius > defaultMinRadius;
- const isDefaultHeight =
- minHeight === defaultMinHeight && maxHeight === defaultMaxHeight;
-
- const isAngleFlipped = maxAngle < minAngle;
- const angleWidth = maxAngle - minAngle + isAngleFlipped * defaultAngleWidth;
- const hasWedgeRegular =
- angleWidth > defaultHalfAngleWidth + wedgeEpsilon &&
- angleWidth < defaultAngleWidth - wedgeEpsilon;
- const hasWedgeFlipped =
- angleWidth > wedgeEpsilon &&
- angleWidth < defaultHalfAngleWidth - wedgeEpsilon;
- const hasWedgeFlat =
- angleWidth >= defaultHalfAngleWidth - wedgeEpsilon &&
- angleWidth <= defaultHalfAngleWidth + wedgeEpsilon;
- const hasWedgeEmpty = angleWidth <= wedgeEpsilon;
- const hasWedge =
- hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat || hasWedgeEmpty;
-
- const isMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
- minAngle,
+ const isDefaultMaxRadiusShape = shapeMaxRadius === defaultMaxRadius;
+ const isDefaultMinRadiusShape = shapeMinRadius === defaultMinRadius;
+ const isDefaultRadiusShape =
+ isDefaultMinRadiusShape && isDefaultMaxRadiusShape;
+ const isDefaultMaxRadiusRender = renderMaxRadius === defaultMaxRadius;
+ const isDefaultMinRadiusRender = renderMinRadius === defaultMinRadius;
+ const isDefaultHeightShape =
+ shapeMinHeight === defaultMinHeight && shapeMaxHeight === defaultMaxHeight;
+ const isDefaultHeightRender =
+ renderMinHeight === defaultMinHeight &&
+ renderMaxHeight === defaultMaxHeight;
+
+ const isAngleReversedShape = shapeMaxAngle < shapeMinAngle;
+ const shapeAngleWidth =
+ shapeMaxAngle - shapeMinAngle + isAngleReversedShape * defaultAngleWidth;
+ const hasAngleRegularShape =
+ shapeAngleWidth > defaultHalfAngleWidth + angleEpsilon &&
+ shapeAngleWidth < defaultAngleWidth - angleEpsilon;
+ const hasAngleFlippedShape =
+ shapeAngleWidth > angleEpsilon &&
+ shapeAngleWidth < defaultHalfAngleWidth - angleEpsilon;
+ const hasAngleFlatShape =
+ shapeAngleWidth >= defaultHalfAngleWidth - angleEpsilon &&
+ shapeAngleWidth <= defaultHalfAngleWidth + angleEpsilon;
+ const hasAngleEmptyShape = shapeAngleWidth <= angleEpsilon;
+ const hasAngleShape =
+ hasAngleRegularShape ||
+ hasAngleFlippedShape ||
+ hasAngleFlatShape ||
+ hasAngleEmptyShape;
+ const hasAngleMinDiscontinuityShape = CesiumMath.equalsEpsilon(
+ shapeMinAngle,
defaultMinAngle,
undefined,
angleDiscontinuityEpsilon
);
- const isMaxAngleDiscontinuity = CesiumMath.equalsEpsilon(
- maxAngle,
+ const hasAngleMaxDiscontinuityShape = CesiumMath.equalsEpsilon(
+ shapeMaxAngle,
defaultMaxAngle,
undefined,
angleDiscontinuityEpsilon
);
+ const isAngleReversedRender = renderMaxAngle < renderMinAngle;
+ const renderAngleWidth =
+ renderMaxAngle - renderMinAngle + isAngleReversedRender * defaultAngleWidth;
+ const hasAngleRegularRender =
+ renderAngleWidth > defaultHalfAngleWidth + angleEpsilon &&
+ renderAngleWidth < defaultAngleWidth - angleEpsilon;
+ const hasAngleFlippedRender =
+ renderAngleWidth > angleEpsilon &&
+ renderAngleWidth < defaultHalfAngleWidth - angleEpsilon;
+ const hasAngleFlatRender =
+ renderAngleWidth >= defaultHalfAngleWidth - angleEpsilon &&
+ renderAngleWidth <= defaultHalfAngleWidth + angleEpsilon;
+ const hasAngleEmptyRender = renderAngleWidth <= angleEpsilon;
+ const hasAngleRender =
+ hasAngleRegularRender ||
+ hasAngleFlippedRender ||
+ hasAngleFlatRender ||
+ hasAngleEmptyRender;
+
const shaderUniforms = this.shaderUniforms;
const shaderDefines = this.shaderDefines;
@@ -297,63 +374,37 @@ VoxelCylinderShape.prototype.update = function (
// Keep track of how many intersections there are going to be.
let intersectionCount = 0;
- // Intersects an outer cylinder.
- shaderDefines["CYLINDER_OUTER_INDEX"] = intersectionCount;
+ shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MAX"] = intersectionCount;
intersectionCount += 1;
- if (!isDefaultOuterCylinder || hasInnerCylinder || !isDefaultHeight) {
- shaderDefines["CYLINDER_OUTER_NON_DEFAULT"] = true;
- const boundsScaleLocalToBounds = Cartesian3.fromElements(
- 1.0 / maxRadius,
- 1.0 / maxRadius,
- 1.0 / (maxHeight === minHeight ? 1.0 : 0.5 * (maxHeight - minHeight)),
- scratchBoundsScale
- );
-
- // -inverse(scale) * translation // affine inverse
- // -inverse(scale) * 0.5 * (minHeight + maxHeight)
- const boundsTranslateLocalToBounds = Cartesian3.fromElements(
- 0.0,
- 0.0,
- -boundsScaleLocalToBounds.z * 0.5 * (minHeight + maxHeight),
- scratchBoundsTranslation
- );
+ if (!isDefaultMinRadiusRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN"] = true;
+ shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MIN"] = intersectionCount;
+ intersectionCount += 1;
- const transformLocalToBounds = Matrix4.fromRotationTranslation(
- Matrix3.fromScale(boundsScaleLocalToBounds),
- boundsTranslateLocalToBounds,
- scratchTransformLocalToBounds
- );
- const transformUvToBounds = Matrix4.multiplyTransformation(
- transformLocalToBounds,
- transformUvToLocal,
- scratchTransformUvToBounds
- );
- shaderUniforms.cylinderScaleUvToBounds = Matrix4.getScale(
- transformUvToBounds,
- shaderUniforms.cylinderScaleUvToBounds
- );
- shaderUniforms.cylinderTranslateUvToBounds = Matrix4.getTranslation(
- transformUvToBounds,
- shaderUniforms.cylinderTranslateUvToBounds
- );
+ shaderUniforms.cylinderInverseMinRadiusUv =
+ renderMaxRadius / renderMinRadius;
}
-
- // Intersects an inner cylinder for the min height.
- if (hasInnerCylinder) {
- shaderDefines["CYLINDER_INNER"] = true;
-
- if (minRadius === maxRadius) {
- shaderDefines["CYLINDER_INNER_OUTER_EQUAL"] = true;
- } else {
- shaderDefines["CYLINDER_INNER_INDEX"] = intersectionCount;
- }
-
- intersectionCount += 1;
+ if (!isDefaultMaxRadiusRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX"] = true;
}
-
- if (!isDefaultOuterCylinder || hasInnerCylinder || !isDefaultHeight) {
- shaderUniforms.cylinderInverseInnerRadiusUv = maxRadius / minRadius;
+ if (renderMinRadius === renderMaxRadius) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT"] = true;
+ }
+ if (!isDefaultHeightRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_HEIGHT"] = true;
+ }
+ if (renderMinHeight === renderMaxHeight) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT"] = true;
+ }
+ if (shapeMinHeight === shapeMaxHeight) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT"] = true;
+ }
+ if (shapeMinRadius === shapeMaxRadius) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT"] = true;
+ }
+ if (!isDefaultRadiusShape) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_RADIUS"] = true;
// delerp(radius, minRadius, maxRadius)
// (radius - minRadius) / (maxRadius - minRadius)
@@ -361,21 +412,17 @@ VoxelCylinderShape.prototype.update = function (
// scale = 1.0 / (maxRadius - minRadius)
// offset = -minRadius / (maxRadius - minRadius)
// offset = minRadius / (minRadius - maxRadius)
- const scale = 1.0 / (maxRadius - minRadius);
- const offset = minRadius / (minRadius - maxRadius);
- shaderUniforms.cylinderRadiusUvScaleAndOffset = Cartesian2.fromElements(
+ const scale = 1.0 / (shapeMaxRadius - shapeMinRadius);
+ const offset = shapeMinRadius / (shapeMinRadius - shapeMaxRadius);
+ shaderUniforms.cylinderUvToShapeUvRadius = Cartesian2.fromElements(
scale,
offset,
- shaderUniforms.cylinderRadiusUvScaleAndOffset
+ shaderUniforms.cylinderUvToShapeUvRadius
);
}
- if (!isDefaultHeight) {
- shaderDefines["CYLINDER_HEIGHT_NON_DEFAULT"] = true;
-
- if (minHeight === maxHeight) {
- shaderDefines["CYLINDER_HEIGHT_ZERO"] = true;
- }
+ if (!isDefaultHeightShape) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT"] = true;
// delerp(heightUv, minHeightUv, maxHeightUv)
// (heightUv - minHeightUv) / (maxHeightUv - minHeightUv)
@@ -388,58 +435,102 @@ VoxelCylinderShape.prototype.update = function (
// offset = -2.0 * (minHeight * 0.5 + 0.5) / (maxHeight - minHeight)
// offset = -(minHeight + 1.0) / (maxHeight - minHeight)
// offset = (minHeight + 1.0) / (minHeight - maxHeight)
- const scale = 2.0 / (maxHeight - minHeight);
- const offset = (minHeight + 1.0) / (minHeight - maxHeight);
- shaderUniforms.cylinderHeightUvScaleAndOffset = Cartesian2.fromElements(
+ const scale = 2.0 / (shapeMaxHeight - shapeMinHeight);
+ const offset = (shapeMinHeight + 1.0) / (shapeMinHeight - shapeMaxHeight);
+ shaderUniforms.cylinderUvToShapeUvHeight = Cartesian2.fromElements(
scale,
offset,
- shaderUniforms.cylinderHeightUvScaleAndOffset
+ shaderUniforms.cylinderUvToShapeUvHeight
);
}
- if (isAngleFlipped) {
- shaderDefines["CYLINDER_WEDGE_ANGLE_FLIPPED"] = true;
+ if (!isDefaultMaxRadiusRender || !isDefaultHeightRender) {
+ const heightScale = 0.5 * (renderMaxHeight - renderMinHeight);
+ const scaleLocalToBounds = Cartesian3.fromElements(
+ 1.0 / renderMaxRadius,
+ 1.0 / renderMaxRadius,
+ 1.0 / (heightScale === 0.0 ? 1.0 : heightScale),
+ scratchBoundsScale
+ );
+ // -inverse(scale) * translation // affine inverse
+ // -inverse(scale) * 0.5 * (minHeight + maxHeight)
+ const translateLocalToBounds = Cartesian3.fromElements(
+ 0.0,
+ 0.0,
+ -scaleLocalToBounds.z * 0.5 * (renderMinHeight + renderMaxHeight),
+ scratchBoundsTranslation
+ );
+ const transformLocalToBounds = Matrix4.fromRotationTranslation(
+ Matrix3.fromScale(scaleLocalToBounds),
+ translateLocalToBounds,
+ scratchTransformLocalToBounds
+ );
+ const transformUvToBounds = Matrix4.multiplyTransformation(
+ transformLocalToBounds,
+ transformUvToLocal,
+ scratchTransformUvToBounds
+ );
+ shaderUniforms.cylinderUvToRenderBoundsScale = Matrix4.getScale(
+ transformUvToBounds,
+ shaderUniforms.cylinderUvToRenderBoundsScale
+ );
+ shaderUniforms.cylinderUvToRenderBoundsTranslate = Matrix4.getTranslation(
+ transformUvToBounds,
+ shaderUniforms.cylinderUvToRenderBoundsTranslate
+ );
}
- // Intersects a wedge for the min and max longitude.
- if (hasWedge) {
- shaderDefines["CYLINDER_WEDGE"] = true;
- shaderDefines["CYLINDER_WEDGE_INDEX"] = intersectionCount;
+ if (isAngleReversedShape) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED"] = true;
+ }
+
+ if (hasAngleRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE"] = true;
+ shaderDefines["CYLINDER_INTERSECTION_INDEX_ANGLE"] = intersectionCount;
- if (hasWedgeRegular) {
- shaderDefines["CYLINDER_WEDGE_REGULAR"] = true;
+ if (hasAngleRegularRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF"] = true;
intersectionCount += 1;
- } else if (hasWedgeFlipped) {
- shaderDefines["CYLINDER_WEDGE_FLIPPED"] = true;
+ } else if (hasAngleFlippedRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF"] = true;
intersectionCount += 2;
- } else if (hasWedgeFlat) {
- shaderDefines["CYLINDER_WEDGE_FLAT"] = true;
+ } else if (hasAngleFlatRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF"] = true;
intersectionCount += 1;
- } else if (hasWedgeEmpty) {
- shaderDefines["CYLINDER_WEDGE_EMPTY"] = true;
+ } else if (hasAngleEmptyRender) {
+ shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO"] = true;
intersectionCount += 2;
}
- if (isMinAngleDiscontinuity) {
- shaderDefines["CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY"] = true;
- }
- if (isMaxAngleDiscontinuity) {
- shaderDefines["CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY"] = true;
- }
-
- shaderUniforms.cylinderAngleMinMax = Cartesian2.fromElements(
- minAngle,
- maxAngle,
+ shaderUniforms.cylinderRenderAngleMinMax = Cartesian2.fromElements(
+ renderMinAngle,
+ renderMaxAngle,
shaderUniforms.cylinderAngleMinMax
);
+ }
- const minAngleUv = (minAngle - defaultMinAngle) / defaultAngleWidth;
- const maxAngleUv = (maxAngle - defaultMinAngle) / defaultAngleWidth;
- const emptyAngleWidthUv = 1.0 - angleWidth / defaultAngleWidth;
+ if (hasAngleShape) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE"] = true;
+ if (hasAngleEmptyShape) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO"] = true;
+ }
+ if (hasAngleMinDiscontinuityShape) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY"] = true;
+ }
+ if (hasAngleMaxDiscontinuityShape) {
+ shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY"] = true;
+ }
- shaderUniforms.cylinderMinAngleUv = minAngleUv;
- shaderUniforms.cylinderMaxAngleUv = maxAngleUv;
- shaderUniforms.cylinderEmptyMidpointAngleUv =
+ const minAngleUv = (shapeMinAngle - defaultMinAngle) / defaultAngleWidth;
+ const maxAngleUv = (shapeMaxAngle - defaultMinAngle) / defaultAngleWidth;
+ const emptyAngleWidthUv = 1.0 - shapeAngleWidth / defaultAngleWidth;
+
+ shaderUniforms.cylinderShapeUvAngleMinMax = Cartesian2.fromElements(
+ minAngleUv,
+ maxAngleUv,
+ shaderUniforms.cylinderShapeUvAngleMinMax
+ );
+ shaderUniforms.cylinderShapeUvAngleEmptyMid =
(maxAngleUv + 0.5 * emptyAngleWidthUv) % 1.0;
// delerp(angleUv, minAngleUv, maxAngleUv)
@@ -451,16 +542,16 @@ VoxelCylinderShape.prototype.update = function (
// offset = -minAngleUv / (maxAngleUv - minAngleUv)
// offset = -((minAngle - pi) / (2.0 * pi)) / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
// offset = -(minAngle - pi) / (maxAngle - minAngle)
- const scale = defaultAngleWidth / angleWidth;
- const offset = -(minAngle - defaultMinAngle) / angleWidth;
- shaderUniforms.cylinderAngleUvScaleAndOffset = Cartesian2.fromElements(
+ const scale = defaultAngleWidth / shapeAngleWidth;
+ const offset = -(shapeMinAngle - defaultMinAngle) / shapeAngleWidth;
+ shaderUniforms.cylinderUvToShapeUvAngle = Cartesian2.fromElements(
scale,
offset,
- shaderUniforms.cylinderAngleUvScaleAndOffset
+ shaderUniforms.cylinderUvToShapeUvAngle
);
}
- shaderDefines["CYLINDER_INTERSECTION_COUNT"] = intersectionCount;
+ this.shaderMaximumIntersectionsLength = intersectionCount;
return true;
};
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 03d1a847c98..51708ae4b9f 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -1,7 +1,6 @@
import BoundingSphere from "../Core/BoundingSphere.js";
import Cartesian2 from "../Core/Cartesian2.js";
import Cartesian3 from "../Core/Cartesian3.js";
-import Cartesian4 from "../Core/Cartesian4.js";
import Check from "../Core/Check.js";
import Ellipsoid from "../Core/Ellipsoid.js";
import CesiumMath from "../Core/Math.js";
@@ -94,22 +93,16 @@ function VoxelEllipsoidShape() {
* @readonly
*/
this.shaderUniforms = {
- ellipsoidRectangle: new Cartesian4(),
ellipsoidRadiiUv: new Cartesian3(),
ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
- // Wedge uniforms
- ellipsoidMinLongitudeUv: 0.0,
- ellipsoidMaxLongitudeUv: 0.0,
- ellipsoidEmptyMidpointLongitudeUv: 0.0,
- ellipsoidLongitudeUvScaleAndOffset: new Cartesian2(),
- // Cone uniforms
- ellipsoidLatitudeUvScaleAndOffset: new Cartesian2(),
- ellipsoidMinLatitudeCosSqrHalfAngle: 0.0,
- ellipsoidMaxLatitudeCosSqrHalfAngle: 0.0,
- // Inner ellipsoid uniforms
+ ellipsoidRenderLongitudeMinMax: new Cartesian2(),
+ ellipsoidShapeUvLongitudeMinMaxMid: new Cartesian3(),
+ ellipsoidUvToShapeUvLongitude: new Cartesian2(),
+ ellipsoidUvToShapeUvLatitude: new Cartesian2(),
+ ellipsoidRenderLatitudeCosSqrHalfMinMax: new Cartesian2(),
ellipsoidInverseHeightDifferenceUv: 0.0,
- ellipsoidInverseInnerScaleUv: 0.0,
ellipseInnerRadiiUv: new Cartesian2(),
+ ellipsoidInverseInnerScaleUv: 0.0,
};
/**
@@ -117,39 +110,51 @@ function VoxelEllipsoidShape() {
* @readonly
*/
this.shaderDefines = {
- ELLIPSOID_INTERSECTION_COUNT: undefined,
- ELLIPSOID_WEDGE: undefined,
- ELLIPSOID_WEDGE_INDEX: undefined,
- ELLIPSOID_WEDGE_REGULAR: undefined,
- ELLIPSOID_WEDGE_FLIPPED: undefined,
- ELLIPSOID_WEDGE_FLAT: undefined,
- ELLIPSOID_WEDGE_EMPTY: undefined,
- ELLIPSOID_WEDGE_ANGLE_FLIPPED: undefined,
- ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY: undefined,
- ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY: undefined,
- ELLIPSOID_CONE_BOTTOM: undefined,
- ELLIPSOID_CONE_BOTTOM_REGULAR: undefined,
- ELLIPSOID_CONE_BOTTOM_FLIPPED: undefined,
- ELLIPSOID_CONE_BOTTOM_FLAT: undefined,
- ELLIPSOID_CONE_BOTTOM_INDEX: undefined,
- ELLIPSOID_CONE_TOP: undefined,
- ELLIPSOID_CONE_TOP_REGULAR: undefined,
- ELLIPSOID_CONE_TOP_FLIPPED: undefined,
- ELLIPSOID_CONE_TOP_FLAT: undefined,
- ELLIPSOID_CONE_TOP_INDEX: undefined,
- ELLIPSOID_OUTER: undefined,
- ELLIPSOID_OUTER_INDEX: undefined,
- ELLIPSOID_INNER: undefined,
- ELLIPSOID_INNER_OUTER_EQUAL: undefined,
- ELLIPSOID_INNER_INDEX: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY: undefined,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE: undefined,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO: undefined,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO: undefined,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE: undefined,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO: undefined,
+ ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN: undefined,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN: undefined,
+ ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO: undefined,
ELLIPSOID_IS_SPHERE: undefined,
+ ELLIPSOID_INTERSECTION_INDEX_LONGITUDE: undefined,
+ ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX: undefined,
+ ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN: undefined,
+ ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX: undefined,
+ ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN: undefined,
};
+
+ /**
+ * The maximum number of intersections against the shape for any ray direction.
+ * @type {Number}
+ * @readonly
+ */
+ this.shaderMaximumIntersectionsLength = 0; // not known until update
}
const scratchScale = new Cartesian3();
const scratchRotationScale = new Matrix3();
-const scratchOuter = new Cartesian3();
-const scratchInner = new Cartesian3();
+const scratchOuterExtentShape = new Cartesian3();
+const scratchInnerExtentShape = new Cartesian3();
+const scratchOuterExtentRender = new Cartesian3();
+const scratchInnerExtentRender = new Cartesian3();
/**
* Update the shape's state.
@@ -157,92 +162,208 @@ const scratchInner = new Cartesian3();
* @param {Matrix4} modelMatrix The model matrix.
* @param {Cartesian3} minBounds The minimum bounds.
* @param {Cartesian3} maxBounds The maximum bounds.
+ * @param {Cartesian3} clipMinBounds The minimum clip bounds.
+ * @param {Cartesian3} clipMaxBounds The maximum clip bounds.
* @returns {Boolean} Whether the shape is visible.
*/
VoxelEllipsoidShape.prototype.update = function (
modelMatrix,
minBounds,
- maxBounds
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("modelMatrix", modelMatrix);
Check.typeOf.object("minBounds", minBounds);
Check.typeOf.object("maxBounds", maxBounds);
+ Check.typeOf.object("clipMinBounds", clipMinBounds);
+ Check.typeOf.object("clipMaxBounds", clipMaxBounds);
//>>includeEnd('debug');
- const defaultMinLongitude = VoxelEllipsoidShape.DefaultMinBounds.x;
- const defaultMaxLongitude = VoxelEllipsoidShape.DefaultMaxBounds.x;
- const defaultLongitudeLength = defaultMaxLongitude - defaultMinLongitude;
- const defaultLongitudeHalfLength = 0.5 * defaultLongitudeLength;
- const defaultMinLatitude = VoxelEllipsoidShape.DefaultMinBounds.y;
- const defaultMaxLatitude = VoxelEllipsoidShape.DefaultMaxBounds.y;
- const defaultLatitudeLength = defaultMaxLatitude - defaultMinLatitude;
+ const zeroScaleEpsilon = CesiumMath.EPSILON10;
+ const longitudeDiscontinuityEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
+ const longitudeEpsilon = CesiumMath.EPSILON10;
+ const latitudeEpsilon = CesiumMath.EPSILON10;
+ const flatLatitudeEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
+
+ const longitudeMinDefault = VoxelEllipsoidShape.DefaultMinBounds.x;
+ const longitudeMaxDefault = VoxelEllipsoidShape.DefaultMaxBounds.x;
+ const longitudeRangeDefault = longitudeMaxDefault - longitudeMinDefault;
+ const longitudeHalfRangeDefault = 0.5 * longitudeRangeDefault;
+ const latitudeMinDefault = VoxelEllipsoidShape.DefaultMinBounds.y;
+ const latitudeMaxDefault = VoxelEllipsoidShape.DefaultMaxBounds.y;
+ const latitudeRangeDefault = latitudeMaxDefault - latitudeMinDefault;
// Clamp the longitude / latitude to the valid range
- const west = CesiumMath.clamp(
+ const longitudeMinShape = CesiumMath.clamp(
minBounds.x,
- defaultMinLongitude,
- defaultMaxLongitude
+ longitudeMinDefault,
+ longitudeMaxDefault
);
- const east = CesiumMath.clamp(
+ const longitudeMaxShape = CesiumMath.clamp(
maxBounds.x,
- defaultMinLongitude,
- defaultMaxLongitude
+ longitudeMinDefault,
+ longitudeMaxDefault
+ );
+ const longitudeMinClip = CesiumMath.clamp(
+ clipMinBounds.x,
+ longitudeMinDefault,
+ longitudeMaxDefault
+ );
+ const longitudeMaxClip = CesiumMath.clamp(
+ clipMaxBounds.x,
+ longitudeMinDefault,
+ longitudeMaxDefault
+ );
+ const longitudeMinRender = CesiumMath.clamp(
+ longitudeMinClip,
+ longitudeMinShape,
+ longitudeMaxShape
+ );
+ const longitudeMaxRender = CesiumMath.clamp(
+ longitudeMaxShape,
+ longitudeMinClip,
+ longitudeMaxClip
);
- const south = CesiumMath.clamp(
+
+ const latitudeMinShape = CesiumMath.clamp(
minBounds.y,
- defaultMinLatitude,
- defaultMaxLatitude
+ latitudeMinDefault,
+ latitudeMaxDefault
);
- const north = CesiumMath.clamp(
+ const latitudeMaxShape = CesiumMath.clamp(
maxBounds.y,
- defaultMinLatitude,
- defaultMaxLatitude
+ latitudeMinDefault,
+ latitudeMaxDefault
+ );
+ const latitudeMinClip = CesiumMath.clamp(
+ clipMinBounds.y,
+ latitudeMinDefault,
+ latitudeMaxDefault
+ );
+ const latitudeMaxClip = CesiumMath.clamp(
+ clipMaxBounds.y,
+ latitudeMinDefault,
+ latitudeMaxDefault
+ );
+ const latitudeMinRender = CesiumMath.clamp(
+ latitudeMinClip,
+ latitudeMinShape,
+ latitudeMaxShape
+ );
+ const latitudeMaxRender = CesiumMath.clamp(
+ latitudeMaxShape,
+ latitudeMinClip,
+ latitudeMaxClip
);
// Don't let the height go below the center of the ellipsoid.
const radii = Matrix4.getScale(modelMatrix, scratchScale);
+ const isSphere = radii.x === radii.y && radii.y === radii.z;
const minRadius = Cartesian3.minimumComponent(radii);
- const minHeight = Math.max(minBounds.z, -minRadius);
- const maxHeight = Math.max(maxBounds.z, -minRadius);
+ const minHeightShape = Math.max(minBounds.z, -minRadius);
+ const maxHeightShape = Math.max(maxBounds.z, -minRadius);
+ const minHeightClip = Math.max(clipMinBounds.z, -minRadius);
+ const maxHeightClip = Math.max(clipMaxBounds.z, -minRadius);
+ const minHeightRender = CesiumMath.clamp(
+ minHeightShape,
+ minHeightClip,
+ maxHeightClip
+ );
+ const maxHeightRender = CesiumMath.clamp(
+ maxHeightShape,
+ minHeightClip,
+ maxHeightClip
+ );
// Compute the closest and farthest a point can be from the center of the ellipsoid.
- const innerExtent = Cartesian3.add(
+ const innerExtentShape = Cartesian3.add(
radii,
- Cartesian3.fromElements(minHeight, minHeight, minHeight, scratchInner),
- scratchInner
+ Cartesian3.fromElements(
+ minHeightShape,
+ minHeightShape,
+ minHeightShape,
+ scratchInnerExtentShape
+ ),
+ scratchInnerExtentShape
);
- const outerExtent = Cartesian3.add(
+ const outerExtentShape = Cartesian3.add(
radii,
- Cartesian3.fromElements(maxHeight, maxHeight, maxHeight, scratchOuter),
- scratchOuter
+ Cartesian3.fromElements(
+ maxHeightShape,
+ maxHeightShape,
+ maxHeightShape,
+ scratchOuterExtentShape
+ ),
+ scratchOuterExtentShape
);
- const maxExtent = Cartesian3.maximumComponent(outerExtent);
+ const maxExtentShape = Cartesian3.maximumComponent(outerExtentShape);
- const zeroScaleEpsilon = CesiumMath.EPSILON10;
- const angleDiscontinuityEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
- const wedgeEpsilon = CesiumMath.EPSILON10;
- const coneEpsilon = CesiumMath.EPSILON10;
- const flatConeEpsilon = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
+ const innerExtentRender = Cartesian3.add(
+ radii,
+ Cartesian3.fromElements(
+ minHeightRender,
+ minHeightRender,
+ minHeightRender,
+ scratchInnerExtentRender
+ ),
+ scratchInnerExtentRender
+ );
+ const outerExtentRender = Cartesian3.add(
+ radii,
+ Cartesian3.fromElements(
+ maxHeightRender,
+ maxHeightRender,
+ maxHeightRender,
+ scratchOuterExtentRender
+ ),
+ scratchOuterExtentRender
+ );
+ const maxExtentRender = Cartesian3.maximumComponent(outerExtentRender);
// Exit early if the shape is not visible.
- // Note that west may be greater than east when crossing the 180th meridian.
+ // Note that minLongitude may be greater than maxLongitude when crossing the 180th meridian.
if (
- south > north ||
- minHeight > maxHeight ||
- CesiumMath.equalsEpsilon(outerExtent.x, 0.0, undefined, zeroScaleEpsilon) ||
- CesiumMath.equalsEpsilon(outerExtent.y, 0.0, undefined, zeroScaleEpsilon) ||
- CesiumMath.equalsEpsilon(outerExtent.z, 0.0, undefined, zeroScaleEpsilon)
+ latitudeMinRender > latitudeMaxRender ||
+ latitudeMinRender === latitudeMaxDefault ||
+ latitudeMaxRender === latitudeMinDefault ||
+ minHeightRender > maxHeightRender ||
+ maxHeightShape < minHeightClip ||
+ minHeightShape > maxHeightClip ||
+ CesiumMath.equalsEpsilon(
+ outerExtentRender.x,
+ 0.0,
+ undefined,
+ zeroScaleEpsilon
+ ) ||
+ CesiumMath.equalsEpsilon(
+ outerExtentRender.y,
+ 0.0,
+ undefined,
+ zeroScaleEpsilon
+ ) ||
+ CesiumMath.equalsEpsilon(
+ outerExtentRender.z,
+ 0.0,
+ undefined,
+ zeroScaleEpsilon
+ )
) {
return false;
}
- this._rectangle = Rectangle.fromRadians(west, south, east, north);
+ this._rectangle = Rectangle.fromRadians(
+ longitudeMinShape,
+ latitudeMinShape,
+ longitudeMaxShape,
+ latitudeMaxShape
+ );
this._translation = Matrix4.getTranslation(modelMatrix, this._translation);
this._rotation = Matrix4.getRotation(modelMatrix, this._rotation);
this._ellipsoid = Ellipsoid.fromCartesian3(radii, this._ellipsoid);
- this._minimumHeight = minHeight;
- this._maximumHeight = maxHeight;
+ this._minimumHeight = minHeightShape;
+ this._maximumHeight = maxHeightShape;
this.orientedBoundingBox = getEllipsoidChunkObb(
this._rectangle,
@@ -255,7 +376,7 @@ VoxelEllipsoidShape.prototype.update = function (
);
this.shapeTransform = Matrix4.fromRotationTranslation(
- Matrix3.setScale(this._rotation, outerExtent, scratchRotationScale),
+ Matrix3.setScale(this._rotation, outerExtentRender, scratchRotationScale),
this._translation,
this.shapeTransform
);
@@ -281,18 +402,10 @@ VoxelEllipsoidShape.prototype.update = function (
}
}
- shaderUniforms.ellipsoidRectangle = Cartesian4.fromElements(
- west,
- south,
- east,
- north,
- shaderUniforms.ellipsoidRectangle
- );
-
// The ellipsoid radii scaled to [0,1]. The max ellipsoid radius will be 1.0 and others will be less.
shaderUniforms.ellipsoidRadiiUv = Cartesian3.divideByScalar(
- outerExtent,
- maxExtent,
+ outerExtentShape,
+ maxExtentShape,
shaderUniforms.ellipsoidRadiiUv
);
@@ -307,125 +420,221 @@ VoxelEllipsoidShape.prototype.update = function (
shaderUniforms.ellipsoidInverseRadiiSquaredUv
);
- const rectangleHeight = Rectangle.computeHeight(this._rectangle);
- const hasInnerEllipsoid = !Cartesian3.equals(innerExtent, Cartesian3.ZERO);
-
- const isAngleFlipped = east < west;
- const angleWidth = east - west + isAngleFlipped * defaultLongitudeLength;
- const hasWedgeRegular =
- angleWidth > defaultLongitudeHalfLength + wedgeEpsilon &&
- angleWidth < defaultLongitudeLength - wedgeEpsilon;
- const hasWedgeFlipped =
- angleWidth > wedgeEpsilon &&
- angleWidth < defaultLongitudeHalfLength - wedgeEpsilon;
- const hasWedgeFlat =
- angleWidth >= defaultLongitudeHalfLength - wedgeEpsilon &&
- angleWidth <= defaultLongitudeHalfLength + wedgeEpsilon;
- const hasWedgeEmpty = angleWidth <= wedgeEpsilon;
- const hasWedge =
- hasWedgeRegular || hasWedgeFlipped || hasWedgeFlat || hasWedgeEmpty;
-
- const hasTopConeRegular =
- north > +flatConeEpsilon && north < defaultMaxLatitude - coneEpsilon;
- const hasTopConeFlipped = north < -flatConeEpsilon;
- const hasTopConeFlat = north >= -flatConeEpsilon && north <= +flatConeEpsilon;
- const hasTopCone = hasTopConeRegular || hasTopConeFlipped || hasTopConeFlat;
-
- const hasBottomConeRegular =
- south > defaultMinLatitude + coneEpsilon && south < -flatConeEpsilon;
- const hasBottomConeFlipped = south > +flatConeEpsilon;
- const hasBottomConeFlat =
- south >= -flatConeEpsilon && south <= +flatConeEpsilon;
- const hasBottomCone =
- hasBottomConeRegular || hasBottomConeFlipped || hasBottomConeFlat;
-
- const isSphere = radii.x === radii.y && radii.y === radii.z;
+ // Longitude
+ const isLongitudeMinMaxReversedRender =
+ longitudeMaxRender < longitudeMinRender;
+ const longitudeRangeRender =
+ longitudeMaxRender -
+ longitudeMinRender +
+ isLongitudeMinMaxReversedRender * longitudeRangeDefault;
+ const hasLongitudeRangeEqualZeroRender =
+ longitudeRangeRender <= longitudeEpsilon;
+ const hasLongitudeRangeUnderHalfRender =
+ longitudeRangeRender > longitudeHalfRangeDefault + longitudeEpsilon &&
+ longitudeRangeRender < longitudeRangeDefault - longitudeEpsilon;
+ const hasLongitudeRangeEqualHalfRender =
+ longitudeRangeRender >= longitudeHalfRangeDefault - longitudeEpsilon &&
+ longitudeRangeRender <= longitudeHalfRangeDefault + longitudeEpsilon;
+ const hasLongitudeRangeOverHalfRender =
+ longitudeRangeRender > longitudeEpsilon &&
+ longitudeRangeRender < longitudeHalfRangeDefault - longitudeEpsilon;
+ const hasLongitudeRender =
+ hasLongitudeRangeEqualZeroRender ||
+ hasLongitudeRangeUnderHalfRender ||
+ hasLongitudeRangeEqualHalfRender ||
+ hasLongitudeRangeOverHalfRender;
+
+ const isLongitudeMinMaxReversedShape =
+ longitudeMaxRender < longitudeMinRender;
+ const longitudeRangeShape =
+ longitudeMaxShape -
+ longitudeMinShape +
+ isLongitudeMinMaxReversedShape * longitudeRangeDefault;
+ const hasLongitudeRangeEqualZeroShape =
+ longitudeRangeShape <= longitudeEpsilon;
+ const hasLongitudeRangeUnderHalfShape =
+ longitudeRangeShape > longitudeHalfRangeDefault + longitudeEpsilon &&
+ longitudeRangeShape < longitudeRangeDefault - longitudeEpsilon;
+ const hasLongitudeRangeEqualHalfShape =
+ longitudeRangeShape >= longitudeHalfRangeDefault - longitudeEpsilon &&
+ longitudeRangeShape <= longitudeHalfRangeDefault + longitudeEpsilon;
+ const hasLongitudeRangeOverHalfShape =
+ longitudeRangeShape > longitudeEpsilon &&
+ longitudeRangeShape < longitudeHalfRangeDefault - longitudeEpsilon;
+ const hasLongitudeShape =
+ hasLongitudeRangeEqualZeroShape ||
+ hasLongitudeRangeUnderHalfShape ||
+ hasLongitudeRangeEqualHalfShape ||
+ hasLongitudeRangeOverHalfShape;
+
+ // Latitude
+ const latitudeRangeRender = latitudeMaxRender - latitudeMinRender;
+ const hasLatitudeMaxUnderHalfRender =
+ latitudeMaxRender < -flatLatitudeEpsilon;
+ const hasLatitudeMaxEqualHalfRender =
+ latitudeMaxRender >= -flatLatitudeEpsilon &&
+ latitudeMaxRender <= +flatLatitudeEpsilon;
+ const hasLatitudeMaxOverHalfRender =
+ latitudeMaxRender > +flatLatitudeEpsilon &&
+ latitudeMaxRender < latitudeMaxDefault - latitudeEpsilon;
+ const hasLatitudeMaxRender =
+ hasLatitudeMaxUnderHalfRender ||
+ hasLatitudeMaxEqualHalfRender ||
+ hasLatitudeMaxOverHalfRender;
+ const hasLatitudeMinUnderHalfRender =
+ latitudeMinRender > latitudeMinDefault + latitudeEpsilon &&
+ latitudeMinRender < -flatLatitudeEpsilon;
+ const hasLatitudeMinEqualHalfRender =
+ latitudeMinRender >= -flatLatitudeEpsilon &&
+ latitudeMinRender <= +flatLatitudeEpsilon;
+ const hasLatitudeMinOverHalfRender = latitudeMinRender > +flatLatitudeEpsilon;
+ const hasLatitudeMinRender =
+ hasLatitudeMinUnderHalfRender ||
+ hasLatitudeMinEqualHalfRender ||
+ hasLatitudeMinOverHalfRender;
+ const hasLatitudeRender = hasLatitudeMaxRender || hasLatitudeMinRender;
+
+ const latitudeRangeShape = latitudeMaxShape - latitudeMinShape;
+ const hasLatitudeMaxUnderHalfShape = latitudeMaxShape < -flatLatitudeEpsilon;
+ const hasLatitudeMaxEqualHalfShape =
+ latitudeMaxShape >= -flatLatitudeEpsilon &&
+ latitudeMaxShape <= +flatLatitudeEpsilon;
+ const hasLatitudeMaxOverHalfShape =
+ latitudeMaxShape > +flatLatitudeEpsilon &&
+ latitudeMaxShape < latitudeMaxDefault - latitudeEpsilon;
+ const hasLatitudeMaxShape =
+ hasLatitudeMaxUnderHalfShape ||
+ hasLatitudeMaxEqualHalfShape ||
+ hasLatitudeMaxOverHalfShape;
+ const hasLatitudeMinUnderHalfShape =
+ latitudeMinShape > latitudeMinDefault + latitudeEpsilon &&
+ latitudeMinShape < -flatLatitudeEpsilon;
+ const hasLatitudeMinEqualHalfShape =
+ latitudeMinShape >= -flatLatitudeEpsilon &&
+ latitudeMinShape <= +flatLatitudeEpsilon;
+ const hasLatitudeMinOverHalfShape = latitudeMinShape > +flatLatitudeEpsilon;
+ const hasLatitudeMinShape =
+ hasLatitudeMinUnderHalfShape ||
+ hasLatitudeMinEqualHalfShape ||
+ hasLatitudeMinOverHalfShape;
+ const hasLatitudeShape = hasLatitudeMaxShape || hasLatitudeMinShape;
+
+ // Height
+ const hasHeightMinRender = !Cartesian3.equals(
+ innerExtentRender,
+ Cartesian3.ZERO
+ );
+ const hasHeightMaxRender = !Cartesian3.equals(
+ outerExtentRender,
+ Cartesian3.ZERO
+ );
+ const hasHeightRender = hasHeightMinRender || hasHeightMaxRender;
+ const heightRangeRender = maxHeightRender - minHeightRender;
+ const hasHeightMinShape = !Cartesian3.equals(
+ innerExtentShape,
+ Cartesian3.ZERO
+ );
+ const hasHeightMaxShape = !Cartesian3.equals(
+ outerExtentShape,
+ Cartesian3.ZERO
+ );
+ const hasHeightShape = hasHeightMinShape || hasHeightMaxShape;
// Keep track of how many intersections there are going to be.
let intersectionCount = 0;
// Intersects an outer ellipsoid for the max height.
- shaderDefines["ELLIPSOID_OUTER"] = true;
- shaderDefines["ELLIPSOID_OUTER_INDEX"] = intersectionCount;
+ shaderDefines["ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX"] = intersectionCount;
intersectionCount += 1;
- // Intersects an inner ellipsoid for the min height.
- if (hasInnerEllipsoid) {
- shaderDefines["ELLIPSOID_INNER"] = true;
- shaderDefines["ELLIPSOID_INNER_INDEX"] = intersectionCount;
- if (minHeight === maxHeight) {
- shaderDefines["ELLIPSOID_INNER_OUTER_EQUAL"] = true;
+ if (hasHeightRender) {
+ if (heightRangeRender === 0.0) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO"
+ ] = true;
}
- intersectionCount += 1;
-
- // The percent of space that is between the inner and outer ellipsoid.
- const thickness = (maxHeight - minHeight) / maxExtent;
- shaderUniforms.ellipsoidInverseHeightDifferenceUv = 1.0 / thickness;
-
- // The percent of space that is taken up by the inner ellipsoid.
- const innerScale = 1.0 - thickness;
- shaderUniforms.ellipsoidInverseInnerScaleUv = 1.0 / innerScale;
+ if (hasHeightMinRender) {
+ shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN"] = true;
+ shaderDefines[
+ "ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN"
+ ] = intersectionCount;
+ intersectionCount += 1;
- // The inner ellipsoid radii scaled to [0,innerScale]. The max inner ellipsoid radius will equal innerScale and others will be less.
- shaderUniforms.ellipseInnerRadiiUv = Cartesian2.fromElements(
- shaderUniforms.ellipsoidRadiiUv.x * innerScale,
- shaderUniforms.ellipsoidRadiiUv.z * innerScale,
- shaderUniforms.ellipseInnerRadiiUv
- );
+ // The inverse of the percent of space that is taken up by the inner ellipsoid.
+ // 1.0 / (1.0 - thickness) // thickness = percent of space that is between the min and max height.
+ // 1.0 / (1.0 - (maxHeightRender - minHeightRender) / maxExtentRender)
+ // maxExtentRender / (maxExtentRender - (maxHeightRender - minHeightRender))
+ shaderUniforms.ellipsoidInverseInnerScaleUv =
+ maxExtentRender / (maxExtentRender - heightRangeRender);
+ }
}
- if (isSphere) {
- shaderDefines["ELLIPSOID_IS_SPHERE"] = true;
+ if (hasHeightShape) {
+ if (hasHeightMinShape) {
+ shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN"] = true;
+
+ // The percent of space that is between the inner and outer ellipsoid.
+ const thickness = (maxHeightShape - minHeightShape) / maxExtentShape;
+ shaderUniforms.ellipsoidInverseHeightDifferenceUv = 1.0 / thickness;
+ shaderUniforms.ellipseInnerRadiiUv = Cartesian2.fromElements(
+ shaderUniforms.ellipsoidRadiiUv.x * (1.0 - thickness),
+ shaderUniforms.ellipsoidRadiiUv.z * (1.0 - thickness),
+ shaderUniforms.ellipseInnerRadiiUv
+ );
+ }
+ if (minHeightShape === maxHeightShape) {
+ shaderDefines[
+ "ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO"
+ ] = true;
+ }
}
// Intersects a wedge for the min and max longitude.
- if (hasWedge) {
- shaderDefines["ELLIPSOID_WEDGE"] = true;
- shaderDefines["ELLIPSOID_WEDGE_INDEX"] = intersectionCount;
-
- if (hasWedgeRegular) {
- shaderDefines["ELLIPSOID_WEDGE_REGULAR"] = true;
+ if (hasLongitudeRender) {
+ shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE"] = true;
+ shaderDefines["ELLIPSOID_INTERSECTION_INDEX_LONGITUDE"] = intersectionCount;
+
+ if (hasLongitudeRangeUnderHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF"
+ ] = true;
intersectionCount += 1;
- } else if (hasWedgeFlipped) {
- shaderDefines["ELLIPSOID_WEDGE_FLIPPED"] = true;
+ } else if (hasLongitudeRangeOverHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF"
+ ] = true;
intersectionCount += 2;
- } else if (hasWedgeFlat) {
- shaderDefines["ELLIPSOID_WEDGE_FLAT"] = true;
+ } else if (hasLongitudeRangeEqualHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF"
+ ] = true;
intersectionCount += 1;
- } else if (hasWedgeEmpty) {
- shaderDefines["ELLIPSOID_WEDGE_EMPTY"] = true;
+ } else if (hasLongitudeRangeEqualZeroRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO"
+ ] = true;
intersectionCount += 2;
}
- const isMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
- west,
- defaultMinLongitude,
- undefined,
- angleDiscontinuityEpsilon
- );
- const isMaxAngleDiscontinuity = CesiumMath.equalsEpsilon(
- east,
- defaultMaxLongitude,
- undefined,
- angleDiscontinuityEpsilon
+ shaderUniforms.ellipsoidRenderLongitudeMinMax = Cartesian2.fromElements(
+ longitudeMinRender,
+ longitudeMaxRender,
+ shaderUniforms.ellipsoidRenderLongitudeMinMax
);
+ }
- if (isMinAngleDiscontinuity) {
- shaderDefines["ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY"] = true;
- }
- if (isMaxAngleDiscontinuity) {
- shaderDefines["ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY"] = true;
- }
+ if (hasLongitudeShape) {
+ shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE"] = true;
- const westUv = (west - defaultMinLongitude) / defaultLongitudeLength;
- const eastUv = (east - defaultMinLongitude) / defaultLongitudeLength;
- const emptyAngleWidthUv = 1.0 - angleWidth / defaultLongitudeLength;
+ const isLongitudeMinMaxReversedShape =
+ longitudeMaxShape < longitudeMinShape;
- shaderUniforms.ellipsoidMinLongitudeUv = westUv;
- shaderUniforms.ellipsoidMaxLongitudeUv = eastUv;
- shaderUniforms.ellipsoidEmptyMidpointLongitudeUv =
- (eastUv + 0.5 * emptyAngleWidthUv) % 1.0;
+ if (isLongitudeMinMaxReversedShape) {
+ shaderDefines[
+ "ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED"
+ ] = true;
+ }
// delerp(longitudeUv, minLongitudeUv, maxLongitudeUv)
// (longitudeUv - minLongitudeUv) / (maxLongitudeUv - minLongitudeUv)
@@ -436,20 +645,141 @@ VoxelEllipsoidShape.prototype.update = function (
// offset = -minLongitudeUv / (maxLongitudeUv - minLongitudeUv)
// offset = -((minLongitude - pi) / (2.0 * pi)) / (((maxLongitude - pi) / (2.0 * pi)) - ((minLongitude - pi) / (2.0 * pi)))
// offset = -(minLongitude - pi) / (maxLongitude - minLongitude)
- const scale = defaultLongitudeLength / angleWidth;
- const offset = -(west - defaultMinLongitude) / angleWidth;
- shaderUniforms.ellipsoidLongitudeUvScaleAndOffset = Cartesian2.fromElements(
+ const scale = longitudeRangeDefault / longitudeRangeShape;
+ const offset =
+ -(longitudeMinShape - longitudeMinDefault) / longitudeRangeShape;
+ shaderUniforms.ellipsoidUvToShapeUvLongitude = Cartesian2.fromElements(
scale,
offset,
- shaderUniforms.ellipsoidLongitudeUvScaleAndOffset
+ shaderUniforms.ellipsoidUvToShapeUvLongitude
+ );
+ }
+
+ if (hasLongitudeShape || hasLongitudeRender) {
+ const isMinLongitudeDiscontinuityRender = CesiumMath.equalsEpsilon(
+ longitudeMinRender,
+ longitudeMinDefault,
+ undefined,
+ longitudeDiscontinuityEpsilon
+ );
+ const isMaxLongitudeDiscontinuityRender = CesiumMath.equalsEpsilon(
+ longitudeMaxRender,
+ longitudeMaxDefault,
+ undefined,
+ longitudeDiscontinuityEpsilon
+ );
+
+ if (isMinLongitudeDiscontinuityRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY"
+ ] = true;
+ }
+ if (isMaxLongitudeDiscontinuityRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY"
+ ] = true;
+ }
+ const longitudeMinShapeUv =
+ (longitudeMinShape - longitudeMinDefault) / longitudeRangeDefault;
+ const longitudeMaxShapeUv =
+ (longitudeMaxShape - longitudeMinDefault) / longitudeRangeDefault;
+
+ const longitudeMaxRenderUv =
+ (longitudeMaxRender - longitudeMinDefault) / longitudeRangeDefault;
+ const longitudeRangeEmptyRender =
+ 1.0 - longitudeRangeRender / longitudeRangeDefault;
+ const emptyMidLongitudeRenderUv =
+ (longitudeMaxRenderUv + 0.5 * longitudeRangeEmptyRender) % 1.0;
+
+ shaderUniforms.ellipsoidShapeUvLongitudeMinMaxMid = Cartesian3.fromElements(
+ longitudeMinShapeUv,
+ longitudeMaxShapeUv,
+ emptyMidLongitudeRenderUv,
+ shaderUniforms.ellipsoidShapeUvLongitudeMinMaxMid
);
}
- if (isAngleFlipped) {
- shaderDefines["ELLIPSOID_WEDGE_ANGLE_FLIPPED"] = true;
+ if (hasLatitudeRender) {
+ shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE"] = true;
+
+ // Intersects a cone for min latitude
+ if (hasLatitudeMinRender) {
+ shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN"] = true;
+ shaderDefines[
+ "ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN"
+ ] = intersectionCount;
+
+ if (hasLatitudeMinUnderHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF"
+ ] = true;
+ intersectionCount += 1;
+ } else if (hasLatitudeMinEqualHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF"
+ ] = true;
+ intersectionCount += 1;
+ } else if (hasLatitudeMinOverHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF"
+ ] = true;
+ intersectionCount += 2;
+ }
+ }
+
+ // Intersects a cone for max latitude
+ if (hasLatitudeMaxRender) {
+ shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX"] = true;
+ shaderDefines[
+ "ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX"
+ ] = intersectionCount;
+
+ if (hasLatitudeMaxUnderHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF"
+ ] = true;
+ intersectionCount += 2;
+ } else if (hasLatitudeMaxEqualHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF"
+ ] = true;
+ intersectionCount += 1;
+ } else if (hasLatitudeMaxOverHalfRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF"
+ ] = true;
+ intersectionCount += 1;
+ }
+ }
+
+ if (latitudeMinRender === latitudeMaxRender) {
+ shaderDefines[
+ "ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO"
+ ] = true;
+ }
+
+ const minCosHalfAngleSqr = Math.pow(
+ Math.cos(CesiumMath.PI_OVER_TWO - Math.abs(latitudeMinRender)),
+ 2.0
+ );
+ const maxCosHalfAngleSqr = Math.pow(
+ Math.cos(CesiumMath.PI_OVER_TWO - Math.abs(latitudeMaxRender)),
+ 2.0
+ );
+ shaderUniforms.ellipsoidRenderLatitudeCosSqrHalfMinMax = Cartesian2.fromElements(
+ minCosHalfAngleSqr,
+ maxCosHalfAngleSqr,
+ shaderUniforms.ellipsoidRenderLatitudeCosSqrHalfMinMax
+ );
}
- if (hasBottomCone || hasTopCone) {
+ if (hasLatitudeShape) {
+ if (latitudeMinShape === latitudeMaxShape) {
+ shaderDefines[
+ "ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO"
+ ] = true;
+ }
+
// delerp(latitudeUv, minLatitudeUv, maxLatitudeUv)
// (latitudeUv - minLatitudeUv) / (maxLatitudeUv - minLatitudeUv)
// latitudeUv / (maxLatitudeUv - minLatitudeUv) - minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
@@ -457,65 +787,23 @@ VoxelEllipsoidShape.prototype.update = function (
// scale = 1.0 / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
// scale = 2.0 * pi / (maxLatitude - minLatitude)
// offset = -minLatitudeUv / (maxLatitudeUv - minLatitudeUv)
- // offset = -((minLatitude - pi) / (2.0 * pi)) / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
- // offset = -(minLatitude - pi) / (maxLatitude - minLatitude)
- const scale = defaultLatitudeLength / rectangleHeight;
- const offset = -(south - defaultMinLatitude) / rectangleHeight;
+ // offset = -((minLatitude - -pi) / (2.0 * pi)) / (((maxLatitude - pi) / (2.0 * pi)) - ((minLatitude - pi) / (2.0 * pi)))
+ // offset = -(minLatitude - -pi) / (maxLatitude - minLatitude)
+ // offset = (-pi - minLatitude) / (maxLatitude - minLatitude)
+ const scale = latitudeRangeDefault / latitudeRangeShape;
+ const offset = (latitudeMinDefault - latitudeMinShape) / latitudeRangeShape;
shaderUniforms.ellipsoidLatitudeUvScaleAndOffset = Cartesian2.fromElements(
scale,
offset,
shaderUniforms.ellipsoidLatitudeUvScaleAndOffset
);
-
- const minAngle = hasBottomConeRegular ? -south : south;
- const maxAngle = hasTopConeFlipped ? -north : north;
- const minCosHalfAngle = Math.cos(
- CesiumMath.PI_OVER_TWO - Math.abs(minAngle)
- );
- const maxCosHalfAngle = Math.cos(
- CesiumMath.PI_OVER_TWO - Math.abs(maxAngle)
- );
- shaderUniforms.ellipsoidMinLatitudeCosSqrHalfAngle =
- minCosHalfAngle * minCosHalfAngle;
- shaderUniforms.ellipsoidMaxLatitudeCosSqrHalfAngle =
- maxCosHalfAngle * maxCosHalfAngle;
- }
-
- // Intersects a cone for min latitude
- if (hasBottomCone) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM"] = true;
- shaderDefines["ELLIPSOID_CONE_BOTTOM_INDEX"] = intersectionCount;
-
- if (hasBottomConeRegular) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_REGULAR"] = true;
- intersectionCount += 1;
- } else if (hasBottomConeFlipped) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_FLIPPED"] = true;
- intersectionCount += 2;
- } else if (hasBottomConeFlat) {
- shaderDefines["ELLIPSOID_CONE_BOTTOM_FLAT"] = true;
- intersectionCount += 1;
- }
}
- // Intersects a cone for max latitude
- if (hasTopCone) {
- shaderDefines["ELLIPSOID_CONE_TOP"] = true;
- shaderDefines["ELLIPSOID_CONE_TOP_INDEX"] = intersectionCount;
-
- if (hasTopConeRegular) {
- shaderDefines["ELLIPSOID_CONE_TOP_REGULAR"] = true;
- intersectionCount += 1;
- } else if (hasTopConeFlipped) {
- shaderDefines["ELLIPSOID_CONE_TOP_FLIPPED"] = true;
- intersectionCount += 2;
- } else if (hasTopConeFlat) {
- shaderDefines["ELLIPSOID_CONE_TOP_FLAT"] = true;
- intersectionCount += 1;
- }
+ if (isSphere) {
+ shaderDefines["ELLIPSOID_IS_SPHERE"] = true;
}
- shaderDefines["ELLIPSOID_INTERSECTION_COUNT"] = intersectionCount;
+ this.shaderMaximumIntersectionsLength = intersectionCount;
return true;
};
@@ -549,19 +837,19 @@ VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForTile = function (
//>>includeEnd('debug');
const sizeAtLevel = 1.0 / Math.pow(2.0, tileLevel);
- const westLerp = tileX * sizeAtLevel;
- const eastLerp = (tileX + 1) * sizeAtLevel;
- const southLerp = tileY * sizeAtLevel;
- const northLerp = (tileY + 1) * sizeAtLevel;
+ const minLongitudeLerp = tileX * sizeAtLevel;
+ const maxLongitudeLerp = (tileX + 1) * sizeAtLevel;
+ const minLatitudeLerp = tileY * sizeAtLevel;
+ const maxLatitudeLerp = (tileY + 1) * sizeAtLevel;
const minHeightLerp = tileZ * sizeAtLevel;
const maxHeightLerp = (tileZ + 1) * sizeAtLevel;
const rectangle = Rectangle.subsection(
this._rectangle,
- westLerp,
- southLerp,
- eastLerp,
- northLerp,
+ minLongitudeLerp,
+ minLatitudeLerp,
+ maxLongitudeLerp,
+ maxLatitudeLerp,
scratchRectangle
);
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index b63aa42b04e..5b7a32b81ae 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -420,8 +420,6 @@ function VoxelPrimitive(options) {
cameraPositionUv: new Cartesian3(),
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
stepSize: 1.0,
- minClippingBounds: new Cartesian3(),
- maxClippingBounds: new Cartesian3(),
pickColor: new Color(),
};
@@ -1135,6 +1133,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
const maxBounds = defaultValue(provider.maxBounds, defaultMaxBounds);
this._minBounds = Cartesian3.clone(minBounds, this._minBounds);
this._maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
+ this._minBoundsOld = Cartesian3.clone(this._minBounds, this._minBoundsOld);
+ this._maxBoundsOld = Cartesian3.clone(this._maxBounds, this._maxBoundsOld);
this._minClippingBounds = Cartesian3.clone(
defaultMinBounds,
this._minClippingBounds
@@ -1143,6 +1143,14 @@ VoxelPrimitive.prototype.update = function (frameState) {
defaultMaxBounds,
this._maxClippingBounds
);
+ this._minClippingBoundsOld = Cartesian3.clone(
+ this._minClippingBounds,
+ this._minClippingBoundsOld
+ );
+ this._maxClippingBoundsOld = Cartesian3.clone(
+ this._maxClippingBounds,
+ this._maxClippingBoundsOld
+ );
// Create the shape object
const ShapeConstructor = VoxelShapeType.getShapeConstructor(shapeType);
@@ -1220,14 +1228,31 @@ VoxelPrimitive.prototype.update = function (frameState) {
);
const shape = this._shape;
- const shapeType = provider.shape;
const minBounds = this._minBounds;
const maxBounds = this._maxBounds;
const minBoundsOld = this._minBoundsOld;
const maxBoundsOld = this._maxBoundsOld;
const minBoundsDirty = !Cartesian3.equals(minBounds, minBoundsOld);
const maxBoundsDirty = !Cartesian3.equals(maxBounds, maxBoundsOld);
- const shapeDirty = compoundTransformDirty || minBoundsDirty || maxBoundsDirty;
+ const clipMinBounds = this._minClippingBounds;
+ const clipMaxBounds = this._maxClippingBounds;
+ const clipMinBoundsOld = this._minClippingBoundsOld;
+ const clipMaxBoundsOld = this._maxClippingBoundsOld;
+ const clipMinBoundsDirty = !Cartesian3.equals(
+ clipMinBounds,
+ clipMinBoundsOld
+ );
+ const clipMaxBoundsDirty = !Cartesian3.equals(
+ clipMaxBounds,
+ clipMaxBoundsOld
+ );
+
+ const shapeDirty =
+ compoundTransformDirty ||
+ minBoundsDirty ||
+ maxBoundsDirty ||
+ clipMinBoundsDirty ||
+ clipMaxBoundsDirty;
if (shapeDirty) {
if (compoundTransformDirty) {
@@ -1242,13 +1267,31 @@ VoxelPrimitive.prototype.update = function (frameState) {
if (maxBoundsDirty) {
this._maxBoundsOld = Cartesian3.clone(maxBounds, this._maxBoundsOld);
}
+ if (clipMinBoundsDirty) {
+ this._minClippingBoundsOld = Cartesian3.clone(
+ clipMinBounds,
+ this._minClippingBoundsOld
+ );
+ }
+ if (clipMaxBoundsDirty) {
+ this._maxClippingBoundsOld = Cartesian3.clone(
+ clipMaxBounds,
+ this._maxClippingBoundsOld
+ );
+ }
}
// Update the shape on the first frame or if it's dirty.
// If the shape is visible it will need to do some extra work.
if (
(!this._ready || shapeDirty) &&
- (this._shapeVisible = shape.update(compoundTransform, minBounds, maxBounds))
+ (this._shapeVisible = shape.update(
+ compoundTransform,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+ ))
) {
// Rebuild the shader if any of the shape defines changed.
const shapeDefines = shape.shaderDefines;
@@ -1472,125 +1515,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
if (hasLoadedData && !this._disableRender) {
- // Process clipping bounds.
- const minClip = this._minClippingBounds;
- const maxClip = this._maxClippingBounds;
-
- const defaultMinBounds = VoxelShapeType.getMinBounds(shapeType);
- const defaultMaxBounds = VoxelShapeType.getMaxBounds(shapeType);
-
- let isDefaultClippingBoundsMin =
- minClip.x === defaultMinBounds.x &&
- minClip.y === defaultMinBounds.y &&
- minClip.z === defaultMinBounds.z;
-
- let isDefaultClippingBoundsMax =
- maxClip.x === defaultMaxBounds.x &&
- maxClip.y === defaultMaxBounds.y &&
- maxClip.z === defaultMaxBounds.z;
-
- // Clamp the min bounds to the valid range.
- if (!isDefaultClippingBoundsMin) {
- minClip.x = CesiumMath.clamp(
- minClip.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
- );
- minClip.y = CesiumMath.clamp(
- minClip.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
- );
- if (shapeType !== VoxelShapeType.ELLIPSOID) {
- minClip.z = CesiumMath.clamp(
- minClip.z,
- defaultMinBounds.z,
- defaultMaxBounds.z
- );
- }
- isDefaultClippingBoundsMin =
- minClip.x === defaultMinBounds.x &&
- minClip.y === defaultMinBounds.y &&
- minClip.z === defaultMinBounds.z;
- }
-
- // Clamp the max bounds to the valid range.
- if (!isDefaultClippingBoundsMax) {
- maxClip.x = CesiumMath.clamp(
- maxClip.x,
- defaultMinBounds.x,
- defaultMaxBounds.x
- );
- maxClip.y = CesiumMath.clamp(
- maxClip.y,
- defaultMinBounds.y,
- defaultMaxBounds.y
- );
- if (shapeType !== VoxelShapeType.ELLIPSOID) {
- maxClip.z = CesiumMath.clamp(
- maxClip.z,
- defaultMinBounds.z,
- defaultMaxBounds.z
- );
- }
- isDefaultClippingBoundsMax =
- maxClip.x === defaultMaxBounds.x &&
- maxClip.y === defaultMaxBounds.y &&
- maxClip.z === defaultMaxBounds.z;
- }
-
- const minClipOld = this._minClippingBoundsOld;
- const maxClipOld = this._maxClippingBoundsOld;
- const minClipDirty = !Cartesian3.equals(minClip, minClipOld);
- const maxClipDirty = !Cartesian3.equals(maxClip, maxClipOld);
- const clippingBoundsDirty = minClipDirty || maxClipDirty;
-
- if (clippingBoundsDirty) {
- if (minClipDirty) {
- if (
- (minClip.x === defaultMinBounds.x) !==
- (minClipOld.x === defaultMinBounds.x) ||
- (minClip.y === defaultMinBounds.y) !==
- (minClipOld.y === defaultMinBounds.y) ||
- (minClip.z === defaultMinBounds.z) !==
- (minClipOld.z === defaultMinBounds.z)
- ) {
- this._shaderDirty = true;
- }
- this._minClippingBoundsOld = Cartesian3.clone(
- minClip,
- this._minClippingBoundsOld
- );
- }
- if (maxClipDirty) {
- if (
- (maxClip.x === defaultMaxBounds.x) !==
- (maxClipOld.x === defaultMaxBounds.x) ||
- (maxClip.y === defaultMaxBounds.y) !==
- (maxClipOld.y === defaultMaxBounds.y) ||
- (maxClip.z === defaultMaxBounds.z) !==
- (maxClipOld.z === defaultMaxBounds.z)
- ) {
- this._shaderDirty = true;
- }
- this._maxClippingBoundsOld = Cartesian3.clone(
- maxClip,
- this._maxClippingBoundsOld
- );
- }
- if (!isDefaultClippingBoundsMin || !isDefaultClippingBoundsMax) {
- // Set clipping uniforms
- uniforms.minClippingBounds = Cartesian3.clone(
- minClip,
- uniforms.minClippingBounds
- );
- uniforms.maxClippingBounds = Cartesian3.clone(
- maxClip,
- uniforms.maxClippingBounds
- );
- }
- }
-
// Check if log depth changed
if (this._useLogDepth !== frameState.useLogDepth) {
this._useLogDepth = frameState.useLogDepth;
@@ -1834,11 +1758,6 @@ function buildDrawCommands(that, context) {
attributeLength,
ShaderDestination.FRAGMENT
);
- shaderBuilder.addDefine(
- `SHAPE_${shapeType}`,
- undefined,
- ShaderDestination.FRAGMENT
- );
shaderBuilder.addDefine(
"MEGATEXTURE_2D",
@@ -1892,6 +1811,22 @@ function buildDrawCommands(that, context) {
);
}
+ // Count how many intersections the shader will do.
+ let intersectionCount = shape.shaderMaximumIntersectionsLength;
+ if (depthTest) {
+ shaderBuilder.addDefine(
+ "DEPTH_INTERSECTION_INDEX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount += 1;
+ }
+ shaderBuilder.addDefine(
+ "INTERSECTION_COUNT",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+
const sampleCount = keyframeCount > 1 ? 2 : 1;
shaderBuilder.addDefine(
"SAMPLE_COUNT",
@@ -1900,6 +1835,12 @@ function buildDrawCommands(that, context) {
);
// Shape specific defines
+ shaderBuilder.addDefine(
+ `SHAPE_${shapeType}`,
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+
for (const key in shapeDefines) {
if (shapeDefines.hasOwnProperty(key)) {
let value = shapeDefines[key];
@@ -1912,21 +1853,6 @@ function buildDrawCommands(that, context) {
}
}
- // const useClippingBounds =
- // minClippingBounds.x !== defaultMinBounds.x ||
- // minClippingBounds.y !== defaultMinBounds.y ||
- // minClippingBounds.z !== defaultMinBounds.z ||
- // maxClippingBounds.x !== defaultMaxBounds.x ||
- // maxClippingBounds.y !== defaultMaxBounds.y ||
- // maxClippingBounds.z !== defaultMaxBounds.z;
- // if (useClippingBounds) {
- // shaderBuilder.addDefine(
- // "CLIPPING_BOUNDS",
- // undefined,
- // ShaderDestination.FRAGMENT
- // );
- // }
-
// Fragment shader uniforms
// The reason this uniform is added by shader builder is because some of the
diff --git a/Source/Scene/VoxelShape.js b/Source/Scene/VoxelShape.js
index 4291a2563d3..f8449276f11 100644
--- a/Source/Scene/VoxelShape.js
+++ b/Source/Scene/VoxelShape.js
@@ -82,6 +82,15 @@ Object.defineProperties(VoxelShape.prototype, {
shaderDefines: {
get: DeveloperError.throwInstantiationError,
},
+
+ /**
+ * The maximum number of intersections against the shape for any ray direction.
+ * @type {Number}
+ * @readonly
+ */
+ shaderMaximumIntersectionsLength: {
+ get: DeveloperError.throwInstantiationError,
+ },
});
/**
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 60243c57f9b..4a9d86a5061 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -12,13 +12,13 @@ Below is an example of how this code might look. Properties like "temperature" a
#define MEGATEXTURE_2D
#define MEGATEXTURE_3D
#define DEPTH_TEST
+#define DEPTH_INTERSECTION_INDEX ###
+#define INTERSECTION_COUNT ###
#define JITTER
#define NEAREST_SAMPLING
#define DESPECKLE
#define STATISTICS
#define PADDING
-#define BOUNDS
-#define CLIPPING_BOUNDS
#define PICKING
// Uniforms
@@ -184,60 +184,63 @@ uniform float u_stepSize;
uniform vec4 u_pickColor;
#endif
-#if defined(CLIPPING_BOUNDS)
- uniform vec3 u_minClippingBounds;
- uniform vec3 u_maxClippingBounds;
-#endif
-
#if defined(SHAPE_BOX)
/* Box defines:
- #define BOX_INTERSECTION_COUNT ### // always 1
#define BOX_INTERSECTION_INDEX ### // always 0
- #define BOX_IS_BOUNDED
- #define BOX_IS_RECTANGLE
+ #define BOX_HAS_SHAPE_BOUND
+ #define BOX_HAS_RENDER_BOUND
+ #define BOX_IS_2D
*/
// Box uniforms:
- #if defined(BOX_IS_BOUNDED)
- uniform vec3 u_boxScaleUvToBoundsUv;
- uniform vec3 u_boxOffsetUvToBoundsUv;
- #if defined(BOX_IS_RECTANGLE)
+ #if defined(BOX_HAS_SHAPE_BOUND)
+ uniform vec3 u_boxScaleUvToShapeBoundsUv;
+ uniform vec3 u_boxOffsetUvToShapeBoundsUv;
+ #endif
+ #if defined(BOX_HAS_RENDER_BOUND)
+ #if defined(BOX_IS_2D)
// This matrix bakes in an axis conversion so that the math works for XY plane.
- uniform mat4 u_boxTransformUvToBounds;
+ uniform mat4 u_boxTransformUvToRenderBounds;
#else
// Similar to u_boxTransformUvToBounds but fewer instructions needed.
- uniform vec3 u_boxScaleUvToBounds;
- uniform vec3 u_boxOffsetUvToBounds;
+ uniform vec3 u_boxScaleUvToRenderBounds;
+ uniform vec3 u_boxOffsetUvToRenderBounds;
#endif
#endif
#endif
#if defined(SHAPE_ELLIPSOID)
/* Ellipsoid defines:
- #define ELLIPSOID_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
- #define ELLIPSOID_WEDGE
- #define ELLIPSOID_WEDGE_INDEX ###
- #define ELLIPSOID_WEDGE_REGULAR ### // when there's a wedge
- #define ELLIPSOID_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
- #define ELLIPSOID_WEDGE_FLAT ###
- #define ELLIPSOID_WEDGE_EMPTY
- #define ELLIPSOID_WEDGE_ANGLE_FLIPPED //
- #define ELLIPSOID_CONE_BOTTOM
- #define ELLIPSOID_CONE_BOTTOM_REGULAR // when there's a bottom cone
- #define ELLIPSOID_CONE_BOTTOM_FLIPPED // when cone shape has two intersection intervals
- #define ELLIPSOID_CONE_BOTTOM_FLAT
- #define ELLIPSOID_CONE_BOTTOM_INDEX ###
- #define ELLIPSOID_CONE_TOP
- #define ELLIPSOID_CONE_TOP_REGULAR ### // when there's a top cone
- #define ELLIPSOID_CONE_TOP_FLIPPED ### // when cone shape has two intersection intervals
- #define ELLIPSOID_CONE_TOP_FLAT
- #define ELLIPSOID_CONE_TOP_INDEX ###
- #define ELLIPSOID_OUTER ### // outer ellipsoid - always defined
- #define ELLIPSOID_OUTER_INDEX ###
- #define ELLIPSOID_INNER ### // when there's an inner ellipsoid
- #define ELLIPSOID_INNER_INDEX ###
- #define ELLIPSOID_INNER_OUTER_EQUAL
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO
#define ELLIPSOID_IS_SPHERE
+ #define ELLIPSOID_INTERSECTION_INDEX_LONGITUDE
+ #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX
+ #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN
+ #define ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX
+ #define ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN
*/
// Ellipsoid uniforms
@@ -245,102 +248,85 @@ uniform float u_stepSize;
#if !defined(ELLIPSOID_IS_SPHERE)
uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
#endif
- #if defined(ELLIPSOID_WEDGE) || defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
- uniform vec4 u_ellipsoidRectangle; // west [-pi,+pi], south [-halfPi,+halfPi], east [-pi,+pi], north [-halfPi,+halfPi].
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE)
+ uniform vec2 u_ellipsoidRenderLongitudeMinMax;
#endif
- #if defined(ELLIPSOID_WEDGE)
- #if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED) || defined(ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY) || defined(ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
- uniform float u_ellipsoidEmptyMidpointLongitudeUv;
- #endif
- #if defined(ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
- uniform float u_ellipsoidMinLongitudeUv;
- #endif
- #if defined(ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
- uniform float u_ellipsoidMaxLongitudeUv;
- #endif
- uniform vec2 u_ellipsoidLongitudeUvScaleAndOffset;
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY) || defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
+ uniform vec3 u_ellipsoidShapeUvLongitudeMinMaxMid;
#endif
-
- #if defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP)
- uniform vec2 u_ellipsoidLatitudeUvScaleAndOffset;
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
+ uniform vec2 u_ellipsoidUvToShapeUvLongitude; // x = scale, y = offset
#endif
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
- uniform float u_ellipsoidMinLatitudeCosSqrHalfAngle;
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
+ uniform vec2 u_ellipsoidUvToShapeUvLatitude; // x = scale, y = offset
#endif
- #if defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
- uniform float u_ellipsoidMaxLatitudeCosSqrHalfAngle;
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF)
+ uniform vec2 u_ellipsoidRenderLatitudeCosSqrHalfMinMax;
#endif
- #if defined(ELLIPSOID_INNER) && !defined(ELLIPSOID_INNER_OUTER_EQUAL)
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN) && !defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
uniform float u_ellipsoidInverseHeightDifferenceUv;
- uniform float u_ellipsoidInverseInnerScaleUv;
uniform vec2 u_ellipseInnerRadiiUv; // [0,1]
#endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN)
+ uniform float u_ellipsoidInverseInnerScaleUv;
+ #endif
#endif
#if defined(SHAPE_CYLINDER)
/* Cylinder defines:
- #define CYLINDER_INTERSECTION_COUNT ### // the total number of enter and exit points for all the constituent intersections
- #define CYLINDER_OUTER_INDEX ### // outer cylinder
- #define CYLINDER_OUTER_NON_DEFAULT ### //
- #define CYLINDER_INNER
- #define CYLINDER_INNER_OUTER_EQUAL ### // when inner and outer cylinder have the same radius
- #define CYLINDER_INNER_INDEX ### // when there's an inner cylinder
- #define CYLINDER_HEIGHT_NON_DEFAULT ### //
- #define CYLINDER_HEIGHT_ZERO // when the height is 0
- #define CYLINDER_WEDGE //
- #define CYLINDER_WEDGE_INDEX //
- #define CYLINDER_WEDGE_REGULAR ### // when there's a wedge
- #define CYLINDER_WEDGE_FLIPPED ### // when the wedge has two intersection intervals
- #define CYLINDER_WEDGE_FLAT
- #define CYLINDER_WEDGE_EMPTY
- #define CYLINDER_WEDGE_ANGLE_FLIPPED //
- #define CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY
- #define CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY
+ #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN
+ #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX
+ #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT
+ #define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT
+ #define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO
+
+ #define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS
+ #define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT
+ #define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT
+ #define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED
+
+ #define CYLINDER_INTERSECTION_INDEX_RADIUS_MAX
+ #define CYLINDER_INTERSECTION_INDEX_RADIUS_MIN
+ #define CYLINDER_INTERSECTION_INDEX_ANGLE
+
*/
// Cylinder uniforms
- #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER) || defined(CYLINDER_HEIGHT_NON_DEFAULT)
- uniform vec3 u_cylinderScaleUvToBounds;
- uniform vec3 u_cylinderTranslateUvToBounds;
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT)
+ uniform vec3 u_cylinderUvToRenderBoundsScale;
+ uniform vec3 u_cylinderUvToRenderBoundsTranslate;
#endif
- #if defined(CYLINDER_INNER) && !defined(CYLINDER_INNER_OUTER_EQUAL)
- uniform float u_cylinderInverseInnerRadiusUv;
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN) && !defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
+ uniform float u_cylinderUvToRenderRadiusMin;
#endif
- #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
- uniform vec2 u_cylinderRadiusUvScaleAndOffset;
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE)
+ uniform vec2 u_cylinderRenderAngleMinMax;
#endif
- #if defined(CYLINDER_HEIGHT_NON_DEFAULT)
- uniform vec2 u_cylinderHeightUvScaleAndOffset;
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
+ uniform vec2 u_cylinderUvToShapeUvRadius; // x = scale, y = offset
#endif
- #if defined(CYLINDER_WEDGE)
- uniform vec2 u_cylinderAngleMinMax;
- uniform vec2 u_cylinderAngleUvScaleAndOffset;
- #if defined(CYLINDER_WEDGE_ANGLE_FLIPPED) || defined(CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY) || defined(CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
- uniform float u_cylinderEmptyMidpointAngleUv;
- #endif
- #if defined(CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
- uniform float u_cylinderMinAngleUv;
- #endif
- #if defined(CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
- uniform float u_cylinderMaxAngleUv;
- #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
+ uniform vec2 u_cylinderUvToShapeUvHeight; // x = scale, y = offset
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
+ uniform vec2 u_cylinderUvToShapeUvAngle; // x = scale, y = offset
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
+ uniform vec2 u_cylinderShapeUvAngleMinMax;
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
+ uniform float u_cylinderShapeUvAngleEmptyMid;
#endif
-#endif
-
-// Keep track of how many intersections there are going to be
-#if defined(SHAPE_BOX)
- #define SHAPE_INTERSECTION_COUNT BOX_INTERSECTION_COUNT
-#elif defined(SHAPE_ELLIPSOID)
- #define SHAPE_INTERSECTION_COUNT ELLIPSOID_INTERSECTION_COUNT
-#elif defined(SHAPE_CYLINDER)
- #define SHAPE_INTERSECTION_COUNT CYLINDER_INTERSECTION_COUNT
-#endif
-
-#if defined(DEPTH_TEST)
- #define DEPTH_INTERSECTION_INDEX SHAPE_INTERSECTION_COUNT
- #define SCENE_INTERSECTION_COUNT (SHAPE_INTERSECTION_COUNT + 1)
-#else
- #define SCENE_INTERSECTION_COUNT SHAPE_INTERSECTION_COUNT
#endif
// --------------------------------------------------------
@@ -406,7 +392,7 @@ vec2 index1DTo2DTexcoord(int index, ivec2 dimensions, vec2 uvScale)
struct Intersections {
// Don't access these member variables directly - call the functions instead.
- #if (SCENE_INTERSECTION_COUNT > 1)
+ #if (INTERSECTION_COUNT > 1)
// Store an array of intersections. Each intersection is composed of:
// x for the T value
// y for the shape type - which encodes positive vs negative and entering vs exiting
@@ -415,7 +401,7 @@ struct Intersections {
// y = 1: positive shape exit
// y = 2: negative shape entry
// y = 3: negative shape exit
- vec2 intersections[SCENE_INTERSECTION_COUNT * 2];
+ vec2 intersections[INTERSECTION_COUNT * 2];
// Maintain state for future nextIntersection calls
int index;
@@ -428,7 +414,7 @@ struct Intersections {
};
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#if (SCENE_INTERSECTION_COUNT > 1)
+#if (INTERSECTION_COUNT > 1)
#define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)].x
#else
#define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)]
@@ -438,25 +424,25 @@ struct Intersections {
#define getIntersectionPair(/*inout Intersections*/ ix, /*int*/ index) vec2(getIntersection((ix), (index) * 2 + 0), getIntersection((ix), (index) * 2 + 1))
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#if (SCENE_INTERSECTION_COUNT > 1)
+#if (INTERSECTION_COUNT > 1)
#define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = vec2((t), float(!positive) * 2.0 + float(!enter))
#else
#define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = (t)
#endif
// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#if (SCENE_INTERSECTION_COUNT > 1)
+#if (INTERSECTION_COUNT > 1)
#define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = vec2((entryExit).x, float((index) > 0) * 2.0 + 0.0); (ix).intersections[(index) * 2 + 1] = vec2((entryExit).y, float((index) > 0) * 2.0 + 1.0)
#else
#define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = (entryExit).x; (ix).intersections[(index) * 2 + 1] = (entryExit).y
#endif
-#if (SCENE_INTERSECTION_COUNT > 1)
+#if (INTERSECTION_COUNT > 1)
vec2 nextIntersection(inout Intersections ix) {
vec2 entryExitT = vec2(NO_HIT);
- const int passCount = SCENE_INTERSECTION_COUNT * 2;
- for (int i = 0; i < passCount; i++) {
+ const int passCount = INTERSECTION_COUNT * 2;
+ for (int i = 0; i < passCount; ++i) {
// The loop should be: for (i = ix.index; i < passCount; ++i) {...} but WebGL1 cannot
// loop with non-constant condition, so it has to continue instead.
if (i < ix.index) {
@@ -486,7 +472,7 @@ vec2 nextIntersection(inout Intersections ix) {
// entry and exit have been found, so the loop can stop
if (exitPositive) {
// After exiting positive shape there is nothing left to intersect, so jump to the end index.
- ix.index = SCENE_INTERSECTION_COUNT * 2;
+ ix.index = passCount;
} else {
// There could be more intersections against the positive shape in the future.
ix.index = i + 1;
@@ -499,12 +485,12 @@ vec2 nextIntersection(inout Intersections ix) {
}
#endif
-#if (SCENE_INTERSECTION_COUNT > 1)
+#if (INTERSECTION_COUNT > 1)
void initializeIntersections(inout Intersections ix) {
// Sort the intersections from min T to max T with bubble sort.
// Note: If this sorting function changes, some of the intersection test may
// need to be updated. Search for "bubble sort" to find those areas.
- const int sortPasses = SCENE_INTERSECTION_COUNT * 2 - 1;
+ const int sortPasses = INTERSECTION_COUNT * 2 - 1;
for (int n = sortPasses; n > 0; --n) {
for (int i = 0; i < sortPasses; ++i) {
// The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
@@ -575,7 +561,7 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_FLAT) || defined(ELLIPSOID_CONE_TOP_FLAT))
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF))
vec2 intersectZPlane(Ray ray)
{
float o = ray.pos.z;
@@ -588,7 +574,7 @@ vec2 intersectZPlane(Ray ray)
}
#endif
-#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_EMPTY)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_EMPTY))
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO))
vec4 intersectHalfPlane(Ray ray, float angle) {
vec2 o = ray.pos.xy;
vec2 d = ray.dir.xy;
@@ -607,7 +593,7 @@ vec4 intersectHalfPlane(Ray ray, float angle) {
}
#endif
-#if (defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_WEDGE_FLIPPED) || defined(ELLIPSOID_WEDGE_FLAT))) || (defined(SHAPE_CYLINDER) && (defined(CYLINDER_WEDGE_FLIPPED) || defined(CYLINDER_WEDGE_FLAT)))
+#if (defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF))) || (defined(SHAPE_CYLINDER) && (defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF) || defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF)))
vec2 intersectHalfSpace(Ray ray, float angle)
{
vec2 o = ray.pos.xy;
@@ -624,7 +610,7 @@ vec2 intersectHalfSpace(Ray ray, float angle)
}
#endif
-#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_REGULAR)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_REGULAR))
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF))
vec2 intersectRegularWedge(Ray ray, float minAngle, float maxAngle)
{
vec2 o = ray.pos.xy;
@@ -659,7 +645,7 @@ vec2 intersectRegularWedge(Ray ray, float minAngle, float maxAngle)
}
#endif
-#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_WEDGE_FLIPPED)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_WEDGE_FLIPPED))
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF))
vec4 intersectFlippedWedge(Ray ray, float minAngle, float maxAngle)
{
vec2 planeIntersectMin = intersectHalfSpace(ray, minAngle);
@@ -717,7 +703,7 @@ vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_REGULAR) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+#if defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE)
vec2 intersectDoubleEndedCone(Ray ray, float cosSqrHalfAngle)
{
vec3 o = ray.pos;
@@ -740,7 +726,7 @@ vec2 intersectDoubleEndedCone(Ray ray, float cosSqrHalfAngle)
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_FLIPPED) || defined(ELLIPSOID_CONE_TOP_FLIPPED))
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF))
vec4 intersectFlippedCone(Ray ray, float cosSqrHalfAngle) {
vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
@@ -764,7 +750,7 @@ vec4 intersectFlippedCone(Ray ray, float cosSqrHalfAngle) {
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_TOP_REGULAR))
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF))
vec2 intersectRegularCone(Ray ray, float cosSqrHalfAngle) {
vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
@@ -843,7 +829,7 @@ vec2 intersectUnitCircle(Ray ray) {
}
#endif
-#if defined(SHAPE_CYLINDER) && defined(CYLINDER_INNER)
+#if defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN)
vec2 intersectInfiniteUnitCylinder(Ray ray)
{
vec3 o = ray.pos;
@@ -883,7 +869,7 @@ float ellipseDistanceIterative (vec2 pos, vec2 radii) {
vec2 v = radii * t;
const int iterations = 3;
- for (int i = 0; i < iterations; i++) {
+ for (int i = 0; i < iterations; ++i) {
vec2 e = a * pow(t, vec2(3.0));
vec2 q = normalize(p - e) * length(v - e);
t = normalize((q + e) * invRadii);
@@ -945,19 +931,21 @@ float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
#endif
#if defined(SHAPE_BOX)
-void intersectBoxShape(Ray ray, out Intersections ix)
+void intersectShape(Ray ray, inout Intersections ix)
{
- #if defined(BOX_IS_RECTANGLE)
- // Transform the ray into unit square space on Z plane
- // This matrix bakes in an axis conversion so that the math works for XY plane.
- ray.pos = vec3(u_boxTransformUvToBounds * vec4(ray.pos, 1.0));
- ray.dir = vec3(u_boxTransformUvToBounds * vec4(ray.dir, 0.0));
- vec2 entryExit = intersectUnitSquare(ray);
- #elif defined(BOX_IS_BOUNDED)
- // Transform the ray into unit cube space
- ray.pos = ray.pos * u_boxScaleUvToBounds + u_boxOffsetUvToBounds;
- ray.dir *= u_boxScaleUvToBounds;
- vec2 entryExit = intersectUnitCube(ray);
+ #if defined(BOX_HAS_RENDER_BOUND)
+ #if defined(BOX_IS_2D)
+ // Transform the ray into unit square space on Z plane
+ // This matrix bakes in an axis conversion so that the math works for XY plane.
+ ray.pos = vec3(u_boxTransformUvToRenderBounds * vec4(ray.pos, 1.0));
+ ray.dir = vec3(u_boxTransformUvToRenderBounds * vec4(ray.dir, 0.0));
+ vec2 entryExit = intersectUnitSquare(ray);
+ #else
+ // Transform the ray into unit cube space
+ ray.pos = ray.pos * u_boxScaleUvToRenderBounds + u_boxOffsetUvToRenderBounds;
+ ray.dir *= u_boxScaleUvToRenderBounds;
+ vec2 entryExit = intersectUnitCube(ray);
+ #endif
#else
// Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
// Direction is scaled as well to be in sync with position.
@@ -971,9 +959,9 @@ void intersectBoxShape(Ray ray, out Intersections ix)
#endif
#if defined(SHAPE_BOX)
-vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
- #if defined(BOX_IS_BOUNDED)
- return positionUv * u_boxScaleUvToBoundsUv + u_boxOffsetUvToBoundsUv;
+vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
+ #if defined(BOX_HAS_SHAPE_BOUND)
+ return positionUv * u_boxScaleUvToShapeBoundsUv + u_boxOffsetUvToShapeBoundsUv;
#else
return positionUv;
#endif
@@ -981,7 +969,7 @@ vec3 transformFromUvToBoxSpace(in vec3 positionUv) {
#endif
#if defined(SHAPE_ELLIPSOID)
-void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
+void intersectShape(in Ray ray, inout Intersections ix) {
// Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
// Direction is scaled as well to be in sync with position.
ray.pos = ray.pos * 2.0 - 1.0;
@@ -989,7 +977,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
// Outer ellipsoid
vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- setIntersectionPair(ix, ELLIPSOID_OUTER_INDEX, outerIntersect);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX, outerIntersect);
// Exit early if the outer ellipsoid was missed.
if (outerIntersect.x == NO_HIT) {
@@ -997,7 +985,7 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
}
// Inner ellipsoid
- #if defined(ELLIPSOID_INNER_OUTER_EQUAL)
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
// When the ellipsoid is perfectly thin it's necessary to sandwich the
// inner ellipsoid intersection inside the outer ellipsoid intersection.
@@ -1018,74 +1006,66 @@ void intersectEllipsoidShape(in Ray ray, inout Intersections ix) {
setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #elif defined(ELLIPSOID_INNER)
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN)
Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- setIntersectionPair(ix, ELLIPSOID_INNER_INDEX, innerIntersect);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN, innerIntersect);
#endif
// Flip the ray because the intersection function expects a cone growing towards +Z.
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR) || defined(ELLIPSOID_CONE_BOTTOM_FLAT) || defined(ELLIPSOID_CONE_TOP_FLIPPED)
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF)
Ray flippedRay = ray;
flippedRay.dir.z *= -1.0;
flippedRay.pos.z *= -1.0;
#endif
// Bottom cone
- #if defined(ELLIPSOID_CONE_BOTTOM)
- #if defined(ELLIPSOID_CONE_BOTTOM_REGULAR)
- vec2 bottomConeIntersection = intersectRegularCone(flippedRay, u_ellipsoidMinLatitudeCosSqrHalfAngle);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX, bottomConeIntersection);
- #elif defined(ELLIPSOID_CONE_BOTTOM_FLIPPED)
- vec4 bottomConeIntersection = intersectFlippedCone(ray, u_ellipsoidMinLatitudeCosSqrHalfAngle);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 0, bottomConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX + 1, bottomConeIntersection.zw);
- #elif defined(ELLIPSOID_CONE_BOTTOM_FLAT)
- vec2 bottomConeIntersection = intersectZPlane(flippedRay);
- setIntersectionPair(ix, ELLIPSOID_CONE_BOTTOM_INDEX, bottomConeIntersection);
- #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF)
+ vec2 bottomConeIntersection = intersectRegularCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF)
+ vec2 bottomConeIntersection = intersectZPlane(flippedRay);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF)
+ vec4 bottomConeIntersection = intersectFlippedCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 0, bottomConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 1, bottomConeIntersection.zw);
#endif
// Top cone
- #if defined(ELLIPSOID_CONE_TOP)
- #if defined(ELLIPSOID_CONE_TOP_REGULAR)
- vec2 topConeIntersection = intersectRegularCone(ray, u_ellipsoidMaxLatitudeCosSqrHalfAngle);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX, topConeIntersection);
- #elif defined(ELLIPSOID_CONE_TOP_FLIPPED)
- vec4 topConeIntersection = intersectFlippedCone(flippedRay, u_ellipsoidMaxLatitudeCosSqrHalfAngle);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 0, topConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX + 1, topConeIntersection.zw);
- #elif defined(ELLIPSOID_CONE_TOP_FLAT)
- vec2 topConeIntersection = intersectZPlane(ray);
- setIntersectionPair(ix, ELLIPSOID_CONE_TOP_INDEX, topConeIntersection);
- #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF)
+ vec4 topConeIntersection = intersectFlippedCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 0, topConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 1, topConeIntersection.zw);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF)
+ vec2 topConeIntersection = intersectZPlane(ray);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF)
+ vec2 topConeIntersection = intersectRegularCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection);
#endif
// Wedge
- #if defined(ELLIPSOID_WEDGE)
- float west = u_ellipsoidRectangle.x; // [-pi,+pi]
- float east = u_ellipsoidRectangle.z; // [-pi,+pi]
- #if defined(ELLIPSOID_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectRegularWedge(ray, west, east);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX, wedgeIntersect);
- #elif defined(ELLIPSOID_WEDGE_FLIPPED)
- vec4 wedgeIntersect = intersectFlippedWedge(ray, west, east);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 1, wedgeIntersect.zw);
- #elif defined(ELLIPSOID_WEDGE_FLAT)
- vec2 wedgeIntersect = intersectHalfSpace(ray, west);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX, wedgeIntersect);
- #elif defined(ELLIPSOID_WEDGE_EMPTY)
- vec4 wedgeIntersect = intersectHalfPlane(ray, west);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, ELLIPSOID_WEDGE_INDEX + 1, wedgeIntersect.zw);
- #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)
+ vec4 wedgeIntersect = intersectHalfPlane(ray, u_ellipsoidRenderLongitudeMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 1, wedgeIntersect.zw);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF)
+ vec2 wedgeIntersect = intersectRegularWedge(ray, u_ellipsoidRenderLongitudeMinMax.x, u_ellipsoidRenderLongitudeMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE, wedgeIntersect);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF)
+ vec2 wedgeIntersect = intersectHalfSpace(ray, u_ellipsoidRenderLongitudeMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE, wedgeIntersect);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF)
+ vec4 wedgeIntersect = intersectFlippedWedge(ray, u_ellipsoidRenderLongitudeMinMax.x, u_ellipsoidRenderLongitudeMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 1, wedgeIntersect.zw);
#endif
}
#endif
#if defined(SHAPE_ELLIPSOID)
-vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
+vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
// Compute position and normal.
// Convert positionUv [0,1] to local space [-1,+1] to "normalized" cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height).
// A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
@@ -1099,44 +1079,55 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
#endif
// Compute longitude
- float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
- #if defined(ELLIPSOID_WEDGE)
- #if defined(ELLIPSOID_WEDGE_ANGLE_FLIPPED)
- // Comparing against u_ellipsoidMinAngleUv has precision problems. u_ellipsoidEmptyMidpointAngleUv is more conservative.
- longitude += float(longitude < u_ellipsoidEmptyMidpointLongitudeUv);
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)
+ float longitude = 1.0;
+ #else
+ float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
+
+ // Correct the angle when max < min
+ // Technically this should compare against min longitude - but it has precision problems so compare against the middle of empty space.
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
+ longitude += float(longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z);
#endif
- // When the min or max angle is near the -pi/+pi discontinuity there may be flickering as both sides of the voxel data are read.
- #if defined(ELLIPSOID_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
- longitude = longitude > u_ellipsoidEmptyMidpointLongitudeUv ? u_ellipsoidMinLongitudeUv : longitude;
- #elif defined(ELLIPSOID_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
- longitude = longitude < u_ellipsoidEmptyMidpointLongitudeUv ? u_ellipsoidMaxLongitudeUv : longitude;
+ // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY)
+ longitude = longitude > u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.x : longitude;
+ #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY)
+ longitude = longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.y : longitude;
#endif
- longitude = longitude * u_ellipsoidLongitudeUvScaleAndOffset.x + u_ellipsoidLongitudeUvScaleAndOffset.y;
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
+ longitude = longitude * u_ellipsoidUvToShapeUvLongitude.x + u_ellipsoidUvToShapeUvLongitude.y;
+ #endif
#endif
// Compute latitude
- float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi;
- #if (defined(ELLIPSOID_CONE_BOTTOM) || defined(ELLIPSOID_CONE_TOP))
- latitude = latitude * u_ellipsoidLatitudeUvScaleAndOffset.x + u_ellipsoidLatitudeUvScaleAndOffset.y;
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO)
+ float latitude = 1.0;
+ #else
+ float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi;
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
+ latitude = latitude * u_ellipsoidUvToShapeUvLatitude.x + u_ellipsoidUvToShapeUvLatitude.y;
+ #endif
#endif
// Compute height
- #if defined(ELLIPSOID_INNER_OUTER_EQUAL)
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
// TODO: This breaks down when minBounds == maxBounds. To fix it, this
// function would have to know if ray is intersecting the front or back of the shape
// and set the shape space position to 1 (front) or 0 (back) accordingly.
float height = 1.0;
#else
#if defined(ELLIPSOID_IS_SPHERE)
- #if defined(ELLIPSOID_INNER)
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN)
float height = (length(posEllipsoid) - u_ellipseInnerRadiiUv.x) * u_ellipsoidInverseHeightDifferenceUv;
#else
float height = length(posEllipsoid);
#endif
#else
- #if defined(ELLIPSOID_INNER)
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN)
// Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84).
// This is an optimization so that math can be done with ellipses instead of ellipsoids.
vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
@@ -1153,11 +1144,11 @@ vec3 transformFromUvToEllipsoidSpace(in vec3 positionUv) {
#endif
#if defined(SHAPE_CYLINDER)
-void intersectCylinderShape(Ray ray, inout Intersections ix)
+void intersectShape(Ray ray, inout Intersections ix)
{
- #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER) || defined(CYLINDER_HEIGHT_NON_DEFAULT)
- ray.pos = ray.pos * u_cylinderScaleUvToBounds + u_cylinderTranslateUvToBounds;
- ray.dir *= u_cylinderScaleUvToBounds;
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT)
+ ray.pos = ray.pos * u_cylinderUvToRenderBoundsScale + u_cylinderUvToRenderBoundsTranslate;
+ ray.dir *= u_cylinderUvToRenderBoundsScale;
#else
// Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
// Direction is scaled as well to be in sync with position.
@@ -1165,19 +1156,19 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
ray.dir *= 2.0;
#endif
- #if defined(CYLINDER_HEIGHT_ZERO)
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT)
vec2 outerIntersect = intersectUnitCircle(ray);
#else
vec2 outerIntersect = intersectUnitCylinder(ray);
#endif
- setIntersectionPair(ix, CYLINDER_OUTER_INDEX, outerIntersect);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MAX, outerIntersect);
if (outerIntersect.x == NO_HIT) {
return;
}
- #if defined(CYLINDER_INNER_OUTER_EQUAL)
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
// When the cylinder is perfectly thin it's necessary to sandwich the
// inner cylinder intersection inside the outer cylinder intersection.
@@ -1199,107 +1190,93 @@ void intersectCylinderShape(Ray ray, inout Intersections ix)
setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #elif defined(CYLINDER_INNER)
- Ray innerRay = Ray(ray.pos * u_cylinderInverseInnerRadiusUv, ray.dir * u_cylinderInverseInnerRadiusUv);
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN)
+ Ray innerRay = Ray(ray.pos * u_cylinderUvToRenderRadiusMin, ray.dir * u_cylinderUvToRenderRadiusMin);
vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
- setIntersectionPair(ix, CYLINDER_INNER_INDEX, innerIntersect);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MIN, innerIntersect);
#endif
- #if defined(CYLINDER_WEDGE_REGULAR)
- vec2 wedgeIntersect = intersectRegularWedge(ray, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
- #elif defined(CYLINDER_WEDGE_FLIPPED)
- vec4 wedgeIntersect = intersectFlippedWedge(ray, u_cylinderAngleMinMax.x, u_cylinderAngleMinMax.y);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, wedgeIntersect.zw);
- #elif defined(CYLINDER_WEDGE_FLAT)
- vec2 wedgeIntersect = intersectHalfSpace(ray, u_cylinderAngleMinMax.x);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX, wedgeIntersect);
- #elif defined(CYLINDER_WEDGE_EMPTY)
- vec4 wedgeIntersect = intersectHalfPlane(ray, u_cylinderAngleMinMax.x);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, CYLINDER_WEDGE_INDEX + 1, wedgeIntersect.zw);
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF)
+ vec2 wedgeIntersect = intersectRegularWedge(ray, u_cylinderRenderAngleMinMax.x, u_cylinderRenderAngleMinMax.y);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE, wedgeIntersect);
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF)
+ vec4 wedgeIntersect = intersectFlippedWedge(ray, u_cylinderRenderAngleMinMax.x, u_cylinderRenderAngleMinMax.y);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 1, wedgeIntersect.zw);
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF)
+ vec2 wedgeIntersect = intersectHalfSpace(ray, u_cylinderRenderAngleMinMax.x);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE, wedgeIntersect);
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO)
+ vec4 wedgeIntersect = intersectHalfPlane(ray, u_cylinderRenderAngleMinMax.x);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 1, wedgeIntersect.zw);
#endif
}
#endif
#if defined(SHAPE_CYLINDER)
-vec3 transformFromUvToCylinderSpace(in vec3 positionUv) {
+vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
vec3 positionLocal = positionUv * 2.0 - 1.0; // [-1,+1]
// Compute radius
- #if defined(CYLINDER_INNER_OUTER_EQUAL)
- // TODO: This breaks down when minBounds == maxBounds. To fix it, this
- // function would have to know if ray is intersecting the front or back of the shape
- // and set the shape space position to 1 (front) or 0 (back) accordingly.
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
float radius = 1.0;
#else
float radius = length(positionLocal.xy); // [0,1]
- #if defined(CYLINDER_OUTER_NON_DEFAULT) || defined(CYLINDER_INNER)
- radius = radius * u_cylinderRadiusUvScaleAndOffset.x + u_cylinderRadiusUvScaleAndOffset.y;
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
+ radius = radius * u_cylinderUvToShapeUvRadius.x + u_cylinderUvToShapeUvRadius.y; // x = scale, y = offset
#endif
#endif
// Compute height
- float height = positionUv.z; // [0,1]
- #if defined(CYLINDER_HEIGHT_NON_DEFAULT)
- height = height * u_cylinderHeightUvScaleAndOffset.x + u_cylinderHeightUvScaleAndOffset.y;
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT)
+ float height = 1.0;
+ #else
+ float height = positionUv.z; // [0,1]
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
+ height = height * u_cylinderUvToShapeUvHeight.x + u_cylinderUvToShapeUvHeight.y; // x = scale, y = offset
+ #endif
#endif
// Compute angle
- float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
- #if defined(CYLINDER_WEDGE)
- #if defined(CYLINDER_WEDGE_ANGLE_FLIPPED)
- // Comparing against u_cylinderMinAngleUv has precision problems. u_cylinderEmptyMidpointAngleUv is more conservative.
- angle += float(angle < u_cylinderEmptyMidpointAngleUv);
- #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO) || defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO)
+ float angle = 1.0;
+ #else
+ float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
+ // Comparing against u_cylinderShapeUvAngleMinMax has precision problems. u_cylinderShapeUvAngleEmptyMid is more conservative.
+ angle += float(angle < u_cylinderShapeUvAngleEmptyMid);
+ #endif
- // When the min or max angle is near the -pi/+pi discontinuity there may be flickering as both sides of the voxel data are read.
- #if defined(CYLINDER_WEDGE_MIN_ANGLE_ON_DISCONTINUITY)
- angle = angle > u_cylinderEmptyMidpointAngleUv ? u_cylinderMinAngleUv : angle;
- #elif defined(CYLINDER_WEDGE_MAX_ANGLE_ON_DISCONTINUITY)
- angle = angle < u_cylinderEmptyMidpointAngleUv ? u_cylinderMaxAngleUv : angle;
- #endif
+ // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY)
+ angle = angle > u_cylinderShapeUvAngleEmptyMid ? u_cylinderShapeUvAngleMinMax.x : angle;
+ #elif defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
+ angle = angle < u_cylinderShapeUvAngleEmptyMid ? u_cylinderShapeUvAngleMinMax.y : angle;
+ #endif
- angle = angle * u_cylinderAngleUvScaleAndOffset.x + u_cylinderAngleUvScaleAndOffset.y;
+ angle = angle * u_cylinderUvToShapeUvAngle.x + u_cylinderUvToShapeUvAngle.y; // x = scale, y = offset
+ #endif
#endif
return vec3(radius, height, angle);
}
#endif
-vec3 transformFromUvToShapeSpace(in vec3 positionUv) {
- #if defined(SHAPE_BOX)
- return transformFromUvToBoxSpace(positionUv);
- #elif defined(SHAPE_ELLIPSOID)
- return transformFromUvToEllipsoidSpace(positionUv);
- #elif defined(SHAPE_CYLINDER)
- return transformFromUvToCylinderSpace(positionUv);
- #endif
-}
-
-void intersectShape(Ray ray, out Intersections ix) {
- #if defined(SHAPE_BOX)
- intersectBoxShape(ray, ix);
- #elif defined(SHAPE_ELLIPSOID)
- intersectEllipsoidShape(ray, ix);
- #elif defined(SHAPE_CYLINDER)
- intersectCylinderShape(ray, ix);
- #endif
-}
-
#if defined(DEPTH_TEST)
-float intersectDepth(vec2 screenCoord, Ray ray) {
+void intersectDepth(vec2 screenCoord, Ray ray, inout Intersections ix) {
float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenCoord));
if (logDepthOrDepth != 0.0) {
// Calculate how far the ray must travel before it hits the depth buffer.
vec4 eyeCoordinateDepth = czm_screenToEyeCoordinates(screenCoord, logDepthOrDepth);
eyeCoordinateDepth /= eyeCoordinateDepth.w;
vec3 depthPositionUv = vec3(u_transformPositionViewToUv * eyeCoordinateDepth);
- return dot(depthPositionUv - ray.pos, ray.dir);
+ float t = dot(depthPositionUv - ray.pos, ray.dir);
+ setIntersectionPair(ix, DEPTH_INTERSECTION_INDEX, vec2(t, +INF_HIT));
} else {
- // There's no depth at this position so set it to some really far value.
- return +INF_HIT;
+ // There's no depth at this location.
+ setIntersectionPair(ix, DEPTH_INTERSECTION_INDEX, vec2(NO_HIT));
}
}
#endif
@@ -1312,20 +1289,20 @@ vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Int
// Exit early if the positive shape was completely missed or behind the ray.
vec2 entryExitT = getIntersectionPair(ix, 0);
- if (entryExitT.x < 0.0 && entryExitT.y < 0.0) {
+ if (entryExitT.x == NO_HIT) {
+ // Positive shape was completely missed - so exit early.
return vec2(NO_HIT);
}
- // Add depth to the list of intersections
+ // Depth
#if defined(DEPTH_TEST)
- float depthT = intersectDepth(screenCoord, ray);
- setIntersectionPair(ix, DEPTH_INTERSECTION_INDEX, vec2(depthT, +INF_HIT));
+ intersectDepth(screenCoord, ray, ix);
#endif
// Find the first intersection that's in front of the ray
- #if (SCENE_INTERSECTION_COUNT > 1)
+ #if (INTERSECTION_COUNT > 1)
initializeIntersections(ix);
- for (int i = 0; i < SCENE_INTERSECTION_COUNT; i++) {
+ for (int i = 0; i < INTERSECTION_COUNT; ++i) {
entryExitT = nextIntersection(ix);
if (entryExitT.y > 0.0) {
// Set start to 0.0 when ray is inside the shape.
@@ -1358,7 +1335,7 @@ Properties getPropertiesFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxel
// Final location in the megatexture
vec3 uv = tileUvOffset + voxelUvOffset;
- for (int i = 0; i < PROPERTY_COUNT; i++) {
+ for (int i = 0; i < PROPERTY_COUNT; ++i) {
vec4 sample = texture3D(u_megatextureTextures[i], uv);
samples[i] = decodeTextureSample(sample);
}
@@ -1474,7 +1451,7 @@ Properties getPropertiesFromMegatextureAtLocalPosition(vec3 positionUvLocal, ive
#else
// When more than one sample is taken the accumulator needs to start at 0
Properties properties = clearProperties();
- for (int i = 0; i < SAMPLE_COUNT; i++) {
+ for (int i = 0; i < SAMPLE_COUNT; ++i) {
vec3 actualUv = computeAncestorUv(positionUvLocal, sampleDatas[i].levelsAbove, octreeCoords);
Properties tempProperties = getPropertiesFromMegatextureAtTileUv(actualUvLocal, sampleDatas[i].megatextureIndex);
properties = sumProperties(properties, tempProperties)
@@ -1550,7 +1527,7 @@ void traverseOctreeDownwards(in vec3 positionUv, inout ivec4 octreeCoords, inout
vec3 start = vec3(octreeCoords.xyz) * sizeAtLevel;
vec3 end = start + vec3(sizeAtLevel);
- for (int i = 0; i < OCTREE_MAX_LEVELS; i++) {
+ for (int i = 0; i < OCTREE_MAX_LEVELS; ++i) {
// Find out which octree child contains the position
// 0 if before center, 1 if after
vec3 center = 0.5 * (start + end);
@@ -1580,7 +1557,7 @@ void traverseOctree(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3
parentOctreeIndex = 0;
// TODO: is it possible for this to be out of bounds, and does it matter?
- positionUvShapeSpace = transformFromUvToShapeSpace(positionUv);
+ positionUvShapeSpace = convertUvToShapeUvSpace(positionUv);
positionUvLocal = positionUvShapeSpace;
OctreeNodeData rootData = getOctreeRootData();
@@ -1599,7 +1576,7 @@ void traverseOctree(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3
void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, inout float levelStepMult, inout ivec4 octreeCoords, inout int parentOctreeIndex, inout SampleData sampleDatas[SAMPLE_COUNT]) {
float dimAtLevel = pow(2.0, float(octreeCoords.w));
- positionUvShapeSpace = transformFromUvToShapeSpace(positionUv);
+ positionUvShapeSpace = convertUvToShapeUvSpace(positionUv);
positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
// Note: This code assumes the position is always inside the root tile.
@@ -1608,7 +1585,7 @@ void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpac
if (!insideTile)
{
// Go up tree
- for (int i = 0; i < OCTREE_MAX_LEVELS; i++)
+ for (int i = 0; i < OCTREE_MAX_LEVELS; ++i)
{
octreeCoords.xyz /= ivec3(2);
octreeCoords.w -= 1;
@@ -1641,14 +1618,13 @@ void main()
vec3 viewPosUv = u_cameraPositionUv;
Intersections ix;
- vec2 entryExitT = intersectScene(screenCoord, viewPosUv, viewDirUv, ix);
+ vec2 entryExitT = intersectScene(screenCoord, viewPosUv, viewDirUv, ix);
// Exit early if the scene was completely missed.
if (entryExitT.x == NO_HIT) {
discard;
}
-
float currT = entryExitT.x;
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
@@ -1684,7 +1660,7 @@ void main()
setStatistics(fragmentInput.metadata.statistics);
#endif
- for (int stepCount = 0; stepCount < STEP_COUNT_MAX; stepCount++) {
+ for (int stepCount = 0; stepCount < STEP_COUNT_MAX; ++stepCount) {
// Read properties from the megatexture based on the traversal state
Properties properties = getPropertiesFromMegatextureAtLocalPosition(positionUvLocal, octreeCoords, sampleDatas);
@@ -1748,7 +1724,7 @@ void main()
// Check if there's more intersections.
if (currT > endT) {
- #if (SCENE_INTERSECTION_COUNT == 1)
+ #if (INTERSECTION_COUNT == 1)
break;
#else
vec2 entryExitT = nextIntersection(ix);
diff --git a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
index 8414b50a1d7..648fa10564a 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
@@ -678,7 +678,8 @@ function VoxelInspectorViewModel(scene) {
);
},
getPrimitiveFunction: function () {
- that.clippingBoxMinY = that._voxelPrimitive.minClippingBounds.y;
+ that.clippingEllipsoidMinLatitude =
+ that._voxelPrimitive.minClippingBounds.y;
},
});
addProperty({
From cbe900490c9bced74dfb74b78c7c6d31789fca97 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 21 Apr 2022 17:14:50 -0400
Subject: [PATCH 041/679] default voxel provider and latitude fix
---
Source/Scene/VoxelEllipsoidShape.js | 6 ++-
Source/Scene/VoxelPrimitive.js | 62 +++++++++++++++++------------
Source/Shaders/VoxelFS.glsl | 6 ++-
3 files changed, 44 insertions(+), 30 deletions(-)
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 51708ae4b9f..728a846a45c 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -774,6 +774,8 @@ VoxelEllipsoidShape.prototype.update = function (
}
if (hasLatitudeShape) {
+ shaderDefines["ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE"] = true;
+
if (latitudeMinShape === latitudeMaxShape) {
shaderDefines[
"ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO"
@@ -792,10 +794,10 @@ VoxelEllipsoidShape.prototype.update = function (
// offset = (-pi - minLatitude) / (maxLatitude - minLatitude)
const scale = latitudeRangeDefault / latitudeRangeShape;
const offset = (latitudeMinDefault - latitudeMinShape) / latitudeRangeShape;
- shaderUniforms.ellipsoidLatitudeUvScaleAndOffset = Cartesian2.fromElements(
+ shaderUniforms.ellipsoidUvToShapeUvLatitude = Cartesian2.fromElements(
scale,
offset,
- shaderUniforms.ellipsoidLatitudeUvScaleAndOffset
+ shaderUniforms.ellipsoidUvToShapeUvLatitude
);
}
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 5b7a32b81ae..98290b8ecb7 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -15,42 +15,30 @@ import JulianDate from "../Core/JulianDate.js";
import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import PrimitiveType from "../Core/PrimitiveType.js";
-import RenderState from "../Renderer/RenderState.js";
-import ShaderDestination from "../Renderer/ShaderDestination.js";
import BlendingState from "./BlendingState.js";
import CullFace from "./CullFace.js";
-import CustomShader from "./ModelExperimental/CustomShader.js";
import Material from "./Material.js";
+import MetadataComponentType from "./MetadataComponentType.js";
+import MetadataType from "./MetadataType.js";
import PolylineCollection from "./PolylineCollection.js";
import VoxelShapeType from "./VoxelShapeType.js";
import VoxelTraversal from "./VoxelTraversal.js";
+import CustomShader from "./ModelExperimental/CustomShader.js";
import DrawCommand from "../Renderer/DrawCommand.js";
import Pass from "../Renderer/Pass.js";
+import RenderState from "../Renderer/RenderState.js";
import ShaderBuilder from "../Renderer/ShaderBuilder.js";
+import ShaderDestination from "../Renderer/ShaderDestination.js";
import VoxelFS from "../Shaders/VoxelFS.js";
import VoxelVS from "../Shaders/VoxelVS.js";
-import MetadataType from "./MetadataType.js";
/**
* A primitive that renders voxel data from a {@link VoxelProvider}.
*
- * TODO: make sure the following terms/definitions are consistent across all files
- * world space: Cartesian WGS84
- * local space: Cartesian [-0.5, 0.5] aligned with shape.
- * For box, the origin is the center of the box, and the six sides sit on the planes x = -0.5, x = 0.5 etc.
- * For cylinder, the origin is the center of the cylinder with the cylinder enclosed by the [-0.5, 0.5] box on xy-plane. Positive x-axis points to theta = 0. The top and bottom caps sit at planes z = -0.5, z = 0.5. Positive y points to theta = pi/2
- * For ellipsoid, the origin is the center of the ellipsoid. The maximum height of the ellipsoid touches -0.5, 0.5 in xyz directions.
- * intersection space: local space times 2 to be [-1, 1]. Used for ray intersection calculation
- * UV space: local space plus 0.5 to be [0, 1].
- * shape space: In the coordinate system of the shape [0, 1]
- * For box, this is the same as UV space
- * For cylinder, the coordinate system is (radius, theta, z). theta = 0 is aligned with x axis
- * For ellipsoid, the coordinate system is (longitude, latitude, height). where 0 is the minimum value in each dimension, and 1 is the max.
- *
* @alias VoxelPrimitive
* @constructor
*
- * @param {Object} options Object with the following properties:
- * @param {VoxelProvider} options.provider The voxel provider that supplies the primitive with tile data.
+ * @param {Object} [options] Object with the following properties:
+ * @param {VoxelProvider} [options.provider] The voxel provider that supplies the primitive with tile data.
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The model matrix used to transform the primitive.
* @param {CustomShader} [options.customShader] The custom shader used to style the primitive.
* @param {Clock} [options.clock] The clock used to control time dynamic behavior.
@@ -65,10 +53,6 @@ import MetadataType from "./MetadataType.js";
function VoxelPrimitive(options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- //>>includeStart('debug', pragmas.debug);
- Check.defined("options.provider", options.provider);
- //>>includeEnd('debug');
-
/**
* @type {Boolean}
* @private
@@ -85,7 +69,10 @@ function VoxelPrimitive(options) {
* @type {VoxelProvider}
* @private
*/
- this._provider = options.provider;
+ this._provider = defaultValue(
+ options.provider,
+ VoxelPrimitive.DefaultProvider
+ );
/**
* This member is not created until the provider and shape are ready.
@@ -1972,7 +1959,7 @@ function buildDrawCommands(that, context) {
}
shaderBuilder.addStructField(voxelStructId, "vec3", "positionEC");
shaderBuilder.addStructField(voxelStructId, "vec3", "positionUv");
- shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvShapeSpace");
+ shaderBuilder.addStructField(voxelStructId, "vec3", "positionShapeUv");
shaderBuilder.addStructField(voxelStructId, "vec3", "positionUvLocal");
shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirUv");
shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirWorld");
@@ -2167,7 +2154,8 @@ function buildDrawCommands(that, context) {
enabled: false,
},
depthMask: false,
- blending: BlendingState.ALPHA_BLEND,
+ // internally the shader does premultiplied alpha, so it makes sense to blend that way too
+ blending: BlendingState.PRE_MULTIPLIED_ALPHA_BLEND,
});
// Create the draw commands
@@ -2523,4 +2511,26 @@ VoxelPrimitive.DefaultCustomShader = new CustomShader({
}`,
});
+function DefaultVoxelProvider() {
+ this.ready = true;
+ this.readyPromise = Promise.resolve(this);
+ this.shape = VoxelShapeType.BOX;
+ this.dimensions = new Cartesian3(1, 1, 1);
+ this.names = ["data"];
+ this.types = [MetadataType.SCALAR];
+ this.componentTypes = [MetadataComponentType.FLOAT32];
+ this.maximumTileCount = 1;
+}
+
+DefaultVoxelProvider.prototype.requestData = function (options) {
+ const tileLevel = defined(options) ? defaultValue(options.tileLevel, 0) : 0;
+ if (tileLevel >= 1) {
+ return undefined;
+ }
+
+ return Promise.resolve([new Float32Array(1)]);
+};
+
+VoxelPrimitive.DefaultProvider = new DefaultVoxelProvider();
+
export default VoxelPrimitive;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 4a9d86a5061..802b629d6bb 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -59,7 +59,7 @@ struct Voxel {
VoxelProperty_direction direction;
vec3 positionEC;
vec3 positionUv;
- vec3 positionUvShapeSpace;
+ vec3 positionShapeUv;
vec3 positionUvLocal;
vec3 viewDirUv;
vec3 viewDirWorld;
@@ -1629,6 +1629,8 @@ void main()
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
+ // gl_FragColor = vec4(convertUvToShapeUvSpace(positionUv).yyy, 1.0); return;
+
vec4 colorAccum = vec4(0.0);
#if defined(DESPECKLE)
@@ -1667,7 +1669,7 @@ void main()
// Prepare the custom shader inputs
copyPropertiesToMetadata(properties, fragmentInput.metadata);
fragmentInput.voxel.positionUv = positionUv;
- fragmentInput.voxel.positionUvShapeSpace = positionUvShapeSpace;
+ fragmentInput.voxel.positionShapeUv = positionUvShapeSpace;
fragmentInput.voxel.positionUvLocal = positionUvLocal;
fragmentInput.voxel.viewDirUv = viewDirUv;
fragmentInput.voxel.viewDirWorld = viewDirWorld;
From 4608f9b673f4c6dbf570b1899037eabbf16fde90 Mon Sep 17 00:00:00 2001
From: IanLilleyT
Date: Thu, 21 Apr 2022 17:15:39 -0400
Subject: [PATCH 042/679] sandcastle tweaks
---
Apps/Sandcastle/gallery/Voxels.html | 71 ++++++++++++++++-------------
1 file changed, 40 insertions(+), 31 deletions(-)
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index b8b1321c0cb..048c6a58984 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -84,8 +84,12 @@
break;
}
case Cesium.VoxelShapeType.CYLINDER: {
- this.minBounds = new Cesium.Cartesian3(0.0, -1.0, +0.2);
- this.maxBounds = new Cesium.Cartesian3(1.0, +1.0, -0.2);
+ // this.minBounds = new Cesium.Cartesian3(
+ // 0.0,
+ // -1.0,
+ // -Cesium.Math.PI
+ // );
+ // this.maxBounds = new Cesium.Cartesian3(1.0, +1.0, -Cesium.Math.PI + 0.5);
break;
}
}
@@ -154,6 +158,10 @@
dataColor[index * channelCount + 2] = color.blue;
dataColor[index * channelCount + 3] = 0.75;
// }
+ // dataColor[index * channelCount + 0] = lerperX;
+ // dataColor[index * channelCount + 1] = lerperY;
+ // dataColor[index * channelCount + 2] = lerperZ;
+ // dataColor[index * channelCount + 3] = 1.0; //0.75;
}
}
}
@@ -356,12 +364,12 @@
provider: provider,
customShader: customShader,
modelMatrix: Cesium.Matrix4.fromScale(
- Cesium.Ellipsoid.WGS84.radii,
- // Cesium.Cartesian3.fromElements(
- // Cesium.Ellipsoid.WGS84.radii.x,
- // Cesium.Ellipsoid.WGS84.radii.y,
- // Cesium.Ellipsoid.WGS84.radii.z * 2.0
- // ),
+ // Cesium.Ellipsoid.WGS84.radii,
+ Cesium.Cartesian3.fromElements(
+ Cesium.Ellipsoid.WGS84.radii.x,
+ Cesium.Ellipsoid.WGS84.radii.x,
+ Cesium.Ellipsoid.WGS84.radii.x
+ ),
new Cesium.Matrix4()
),
})
@@ -398,6 +406,29 @@
});
Sandcastle.addToolbarMenu([
+ {
+ text: "Ellipsoid - Procedural Tile",
+ onselect: function () {
+ const provider = new ProceduralSingleTileVoxelProvider(
+ Cesium.VoxelShapeType.ELLIPSOID
+ );
+ const primitive = createPrimitive(provider, customShaderColor);
+ primitive.readyPromise.then(function () {
+ viewer.camera.flyToBoundingSphere(primitive.boundingSphere, {
+ duration: 0.0,
+ });
+ });
+ },
+ },
+ {
+ text: "Cylinder - Procedural Tile",
+ onselect: function () {
+ const provider = new ProceduralSingleTileVoxelProvider(
+ Cesium.VoxelShapeType.CYLINDER
+ );
+ const primitive = createPrimitive(provider, customShaderColor);
+ },
+ },
{
text: "Box - Procedural Tile",
onselect: function () {
@@ -422,6 +453,7 @@
),
duration: 0.0,
});
+ primitive.maxClippingBounds.x = 0.99999999;
});
},
},
@@ -454,20 +486,6 @@
const primitive = createPrimitive(provider, customShaderAlpha);
},
},
- {
- text: "Ellipsoid - Procedural Tile",
- onselect: function () {
- const provider = new ProceduralSingleTileVoxelProvider(
- Cesium.VoxelShapeType.ELLIPSOID
- );
- const primitive = createPrimitive(provider, customShaderColor);
- primitive.readyPromise.then(function () {
- viewer.camera.flyToBoundingSphere(primitive.boundingSphere, {
- duration: 0.0,
- });
- });
- },
- },
{
text: "Ellipsoid - Procedural Tileset",
onselect: function () {
@@ -497,15 +515,6 @@
const primitive = createPrimitive(provider, customShaderAlpha);
},
},
- {
- text: "Cylinder - Procedural Tile",
- onselect: function () {
- const provider = new ProceduralSingleTileVoxelProvider(
- Cesium.VoxelShapeType.CYLINDER
- );
- const primitive = createPrimitive(provider, customShaderColor);
- },
- },
{
text: "Cylinder - Procedural Tileset",
onselect: function () {
From 485f079dae1778e3b1c00f81e7034ae8d1cc4794 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Thu, 28 Apr 2022 15:05:01 -0700
Subject: [PATCH 043/679] handling custom shader uniforms
---
Source/Scene/Cesium3DTilesVoxelProvider.js | 47 +++++++++++++------
Source/Scene/VoxelPrimitive.js | 46 +++++++++++++-----
.../Widgets/VoxelInspector/VoxelInspector.js | 35 +++++++-------
.../VoxelInspector/VoxelInspectorViewModel.js | 34 +++++++-------
4 files changed, 104 insertions(+), 58 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
index 4611faf0bd0..8d441a1e72c 100644
--- a/Source/Scene/Cesium3DTilesVoxelProvider.js
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -281,8 +281,8 @@ function Cesium3DTilesVoxelProvider(options) {
const names = new Array(attributesLength);
const types = new Array(attributesLength);
const componentTypes = new Array(attributesLength);
- const minimumValues = new Array(attributesLength);
- const maximumValues = new Array(attributesLength);
+ let minimumValues = new Array(attributesLength);
+ let maximumValues = new Array(attributesLength);
const schema = metadata.schema;
const statistics = metadata.statistics;
@@ -298,31 +298,48 @@ function Cesium3DTilesVoxelProvider(options) {
for (let i = 0; i < propertyNamesLength; i++) {
const propertyName = propertyNames[i];
const property = properties[propertyName];
- const propertyStatistics = classStatistics.properties[propertyName];
- const propertyMin = Array.isArray(propertyStatistics.min)
- ? propertyStatistics.min
- : [propertyStatistics.min];
- const propertyMax = Array.isArray(propertyStatistics.max)
- ? propertyStatistics.max
- : [propertyStatistics.max];
+
const metadataType = property.type;
const metadataComponentType = property.componentType;
const metadataComponentCount = MetadataType.getComponentCount(
metadataType
);
+ if (defined(classStatistics)) {
+ const propertyStatistics = classStatistics.properties[propertyName];
+ const propertyMin = Array.isArray(propertyStatistics.min)
+ ? propertyStatistics.min
+ : [propertyStatistics.min];
+ const propertyMax = Array.isArray(propertyStatistics.max)
+ ? propertyStatistics.max
+ : [propertyStatistics.max];
+
+ minimumValues[i] = new Array(metadataComponentCount);
+ maximumValues[i] = new Array(metadataComponentCount);
+
+ for (let j = 0; j < metadataComponentCount; j++) {
+ minimumValues[i][j] = propertyMin[j];
+ maximumValues[i][j] = propertyMax[j];
+ }
+ }
+
names[i] = propertyName;
types[i] = metadataType;
componentTypes[i] = metadataComponentType;
- minimumValues[i] = new Array(metadataComponentCount);
- maximumValues[i] = new Array(metadataComponentCount);
+ }
+ }
- for (let j = 0; j < metadataComponentCount; j++) {
- minimumValues[i][j] = propertyMin[j];
- maximumValues[i][j] = propertyMax[j];
- }
+ let hasMinimumMaximumValues = false;
+ for (let i = 0; i < attributesLength; i++) {
+ if (defined(minimumValues[i]) && defined(maximumValues[i])) {
+ hasMinimumMaximumValues = true;
+ break;
}
}
+ if (!hasMinimumMaximumValues) {
+ minimumValues = undefined;
+ maximumValues = undefined;
+ }
that.shape = VoxelShapeType.fromPrimitiveType(primitiveType);
that.minBounds = Cartesian3.clone(voxel.minBounds);
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 98290b8ecb7..cc0f50b4dfe 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -5,6 +5,7 @@ import CesiumMath from "../Core/Math.js";
import Check from "../Core/Check.js";
import clone from "../Core/clone.js";
import Color from "../Core/Color.js";
+import combine from "../Core/combine.js";
import defaultValue from "../Core/defaultValue.js";
import defer from "../Core/defer.js";
import defined from "../Core/defined.js";
@@ -398,8 +399,6 @@ function VoxelPrimitive(options) {
dimensions: new Cartesian3(),
paddingBefore: new Cartesian3(),
paddingAfter: new Cartesian3(),
- minimumValues: [],
- maximumValues: [],
transformPositionViewToUv: new Matrix4(),
transformPositionUvToView: new Matrix4(),
transformDirectionViewToLocal: new Matrix3(),
@@ -439,7 +438,7 @@ function VoxelPrimitive(options) {
const provider = this._provider;
const primitive = this;
provider.readyPromise.catch(function (error) {
- primitive._readyPromise.reject(`provider failed with error:\n${error}`);
+ primitive._readyPromise.reject(error);
});
}
@@ -1011,6 +1010,20 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
set: function (customShader) {
if (this._customShader !== customShader) {
+ // Delete old custom shader entries from the uniform map
+ const uniformMap = this._uniformMap;
+ const oldCustomShader = this._customShader;
+ const oldCustomShaderUniformMap = oldCustomShader.uniformMap;
+ for (const uniformName in oldCustomShaderUniformMap) {
+ if (oldCustomShaderUniformMap.hasOwnProperty(uniformName)) {
+ // If the custom shader was set but the voxel shader was never
+ // built, the custom shader uniforms wouldn't have been added to
+ // the uniform map. But it doesn't matter because the delete
+ // operator ignores if the key doesn't exist.
+ delete uniformMap[uniformName];
+ }
+ }
+
if (!defined(customShader)) {
this._customShader = VoxelPrimitive.DefaultCustomShader;
} else {
@@ -1071,12 +1084,16 @@ VoxelPrimitive.prototype.update = function (frameState) {
const context = frameState.context;
const provider = this._provider;
const uniforms = this._uniforms;
+ const customShader = this._customShader;
// Update the provider, if applicable.
if (defined(provider.update)) {
provider.update(frameState);
}
+ // Update the custom shader in case it has texture uniforms.
+ customShader.update(frameState);
+
// Exit early if it's not ready yet.
if (!this._ready && !provider.ready) {
return;
@@ -1186,13 +1203,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
this._paddingAfter,
uniforms.paddingAfter
);
-
- const minimumValues = provider.minimumValues;
- const maximumValues = provider.maximumValues;
- if (defined(minimumValues) && defined(maximumValues)) {
- uniforms.minimumValues = minimumValues.slice();
- uniforms.maximumValues = maximumValues.slice();
- }
}
// Check if the shape is dirty before updating it. This needs to happen every
@@ -1721,6 +1731,7 @@ function buildDrawCommands(that, context) {
const customShader = that._customShader;
const attributeLength = types.length;
const hasStatistics = defined(minimumValues) && defined(maximumValues);
+ let uniformMap = that._uniformMap;
// Build shader
@@ -1842,6 +1853,20 @@ function buildDrawCommands(that, context) {
// Fragment shader uniforms
+ // Custom shader uniforms
+ const customShaderUniforms = customShader.uniforms;
+ uniformMap = that._uniformMap = combine(uniformMap, customShader.uniformMap);
+ for (const uniformName in customShaderUniforms) {
+ if (customShaderUniforms.hasOwnProperty(uniformName)) {
+ const uniform = customShaderUniforms[uniformName];
+ shaderBuilder.addUniform(
+ uniform.type,
+ uniformName,
+ ShaderDestination.FRAGMENT
+ );
+ }
+ }
+
// The reason this uniform is added by shader builder is because some of the
// dynamically generated shader code reads from it.
shaderBuilder.addUniform(
@@ -2159,7 +2184,6 @@ function buildDrawCommands(that, context) {
});
// Create the draw commands
- const uniformMap = that._uniformMap;
const viewportQuadVertexArray = context.getViewportQuadVertexArray();
const drawCommand = new DrawCommand({
vertexArray: viewportQuadVertexArray,
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
index 7c39a62fbec..49fedbd70a5 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspector.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -274,33 +274,36 @@ function VoxelInspector(container, scene) {
clippingPanelContents
);
- // Style
- const stylePanelContents = createSection(
+ // Shader
+ const shaderPanelContents = createSection(
panel,
- "Style",
- "styleVisible",
- "styleVisibleToggle"
+ "Shader",
+ "shaderVisible",
+ "shaderVisibleToggle"
);
- const stylePanelEditor = document.createElement("div");
- stylePanelContents.appendChild(stylePanelEditor);
+ const shaderPanelEditor = document.createElement("div");
+ shaderPanelContents.appendChild(shaderPanelEditor);
- const styleEditor = document.createElement("textarea");
- styleEditor.setAttribute(
+ const shaderEditor = document.createElement("textarea");
+ shaderEditor.setAttribute(
"data-bind",
- "textInput: styleString, event: { keydown: styleEditorKeyPress }"
+ "textInput: shaderString, event: { keydown: shaderEditorKeyPress }"
);
- stylePanelEditor.className = "cesium-cesiumInspector-styleEditor";
- stylePanelEditor.appendChild(styleEditor);
- const compileStyleButton = makeButton("compileStyle", "Compile (Ctrl+Enter)");
- stylePanelEditor.appendChild(compileStyleButton);
+ shaderPanelEditor.className = "cesium-cesiumInspector-styleEditor";
+ shaderPanelEditor.appendChild(shaderEditor);
+ const compileShaderButton = makeButton(
+ "compileShader",
+ "Compile (Ctrl+Enter)"
+ );
+ shaderPanelEditor.appendChild(compileShaderButton);
const compilationText = document.createElement("label");
compilationText.style.display = "block";
compilationText.setAttribute(
"data-bind",
- "text: styleCompilationMessage, style: {color: styleCompilationSuccess ? 'green' : 'red'}"
+ "text: shaderCompilationMessage, style: {color: shaderCompilationSuccess ? 'green' : 'red'}"
);
- stylePanelEditor.appendChild(compilationText);
+ shaderPanelEditor.appendChild(compilationText);
knockout.applyBindings(viewModel, element);
diff --git a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
index 648fa10564a..15cf147fcbf 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
@@ -130,24 +130,24 @@ function VoxelInspectorViewModel(scene) {
toggle: true,
});
addProperty({
- name: "styleVisible",
+ name: "shaderVisible",
initialValue: false,
toggle: true,
});
addProperty({
- name: "styleString",
+ name: "shaderString",
initialValue: "",
getPrimitiveFunction: function () {
const shaderString = that._voxelPrimitive.customShader.fragmentShaderText;
- that.styleString = formatShaderString(shaderString);
+ that.shaderString = formatShaderString(shaderString);
},
});
addProperty({
- name: "styleCompilationMessage",
+ name: "shaderCompilationMessage",
initialValue: "",
});
addProperty({
- name: "styleCompilationSuccess",
+ name: "shaderCompilationSuccess",
initialValue: true,
});
addProperty({
@@ -1027,14 +1027,14 @@ Object.defineProperties(VoxelInspectorViewModel.prototype, {
function (error) {
const shaderString =
that._voxelPrimitive.customShader.fragmentShaderText;
- that.styleString = formatShaderString(shaderString);
+ that.shaderString = formatShaderString(shaderString);
if (!defined(error)) {
- that.styleCompilationMessage = "Shader compiled successfully!";
- that.styleCompilationSuccess = true;
+ that.shaderCompilationMessage = "Shader compiled successfully!";
+ that.shaderCompilationSuccess = true;
} else {
- that.styleCompilationMessage = error.message;
- that.styleCompilationSuccess = false;
+ that.shaderCompilationMessage = error.message;
+ that.shaderCompilationSuccess = false;
}
}
);
@@ -1048,22 +1048,24 @@ Object.defineProperties(VoxelInspectorViewModel.prototype, {
});
/**
- * Compiles the style in the style editor.
+ * Compiles the shader in the shader editor.
* @private
*/
-VoxelInspectorViewModel.prototype.compileStyle = function () {
+VoxelInspectorViewModel.prototype.compileShader = function () {
if (defined(this._voxelPrimitive)) {
+ // It's assumed that the same uniforms are going to be used regardless of edits.
this._voxelPrimitive.customShader = new CustomShader({
- fragmentShaderText: this.styleString,
+ fragmentShaderText: this.shaderString,
+ uniforms: this._voxelPrimitive.customShader.uniforms,
});
}
};
/**
- * Handles key press events on the style editor.
+ * Handles key press events on the shader editor.
* @private
*/
-VoxelInspectorViewModel.prototype.styleEditorKeyPress = function (
+VoxelInspectorViewModel.prototype.shaderEditorKeyPress = function (
sender,
event
) {
@@ -1103,7 +1105,7 @@ VoxelInspectorViewModel.prototype.styleEditorKeyPress = function (
textArea.selectionEnd = newEnd;
} else if (event.ctrlKey && (event.keyCode === 10 || event.keyCode === 13)) {
//ctrl + enter
- this.compileStyle();
+ this.compileShader();
}
return true;
};
From 4cc05f124982514dac1ec1db5741342c21766533 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Thu, 28 Apr 2022 19:59:32 -0700
Subject: [PATCH 044/679] fixed disable update setting
---
Source/Scene/VoxelPrimitive.js | 6 +++---
Source/Scene/VoxelTraversal.js | 27 +++++++++++++++++++--------
2 files changed, 22 insertions(+), 11 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index cc0f50b4dfe..060a50acc5f 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1498,12 +1498,13 @@ VoxelPrimitive.prototype.update = function (frameState) {
}
// Update the voxel traversal
- const hasLoadedData = traversal.update(
+ traversal.update(
frameState,
keyframeLocation,
shapeDirty, // recomputeBoundingVolumes
this._disableUpdate // pauseUpdate
);
+ const hasLoadedData = traversal.isRenderable(traversal.rootNode);
if (hasLoadedData && this._debugDraw) {
// Debug draw bounding boxes and other things. Must go after traversal update
@@ -2440,7 +2441,6 @@ const polylineZAxis = new Cartesian3(0.0, 0.0, polylineAxisDistance);
* @private
*/
function debugDraw(that, frameState) {
- const frameNumber = frameState.frameNumber;
const traversal = that._traversal;
const polylines = that._debugPolylines;
polylines.removeAll();
@@ -2474,7 +2474,7 @@ function debugDraw(that, frameState) {
}
function drawTile(tile) {
- if (!tile.isRenderable(frameNumber)) {
+ if (!traversal.isRenderable(tile)) {
return;
}
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index 21d2f3e5150..ef0812f4057 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -93,6 +93,12 @@ function VoxelTraversal(
*/
this._debugPrint = false;
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._frameNumber = 0;
+
const shape = primitive._shape;
const rootLevel = 0;
const rootX = 0;
@@ -276,7 +282,6 @@ VoxelTraversal.simultaneousRequestCountMaximum = 50;
* @param {Number} keyframeLocation
* @param {Boolean} recomputeBoundingVolumes
* @param {Boolean} pauseUpdate
- * @returns {Boolean} True if the voxel grid has any loaded data.
*/
VoxelTraversal.prototype.update = function (
frameState,
@@ -296,10 +301,11 @@ VoxelTraversal.prototype.update = function (
}
if (!pauseUpdate) {
+ this._frameNumber = frameState.frameNumber;
const timestamp0 = getTimestamp();
loadAndUnload(this, frameState);
const timestamp1 = getTimestamp();
- generateOctree(this, frameState);
+ generateOctree(this);
const timestamp2 = getTimestamp();
const debugStatistics = this._debugPrint;
@@ -315,10 +321,15 @@ VoxelTraversal.prototype.update = function (
);
}
}
+};
- const rootNode = this.rootNode;
- const frameNumber = frameState.frameNumber;
- return rootNode.isRenderable(frameNumber);
+/**
+ * Check if a node is renderable.
+ * @param {SpatialNode} tile
+ * @returns {Boolean}
+ */
+VoxelTraversal.prototype.isRenderable = function (tile) {
+ return tile.isRenderable(this._frameNumber);
};
/**
@@ -508,7 +519,7 @@ function mapInfiniteRangeToZeroOne(x) {
* @private
*/
function loadAndUnload(that, frameState) {
- const frameNumber = frameState.frameNumber;
+ const frameNumber = that._frameNumber;
const primitive = that._primitive;
const shape = primitive._shape;
const voxelDimensions = primitive._provider.dimensions;
@@ -1402,10 +1413,10 @@ const GpuOctreeFlag = {
* @param {VoxelTraversal} that
* @param {FrameState} frameState
*/
-function generateOctree(that, frameState) {
+function generateOctree(that) {
const keyframeLocation = that._keyframeLocation;
const useLeafNodes = that._useLeafNodeTexture;
- const frameNumber = frameState.frameNumber;
+ const frameNumber = that._frameNumber;
let internalNodeCount = 0;
let leafNodeCount = 0;
From 060a6e6013b1b7acefd4976cf6ec2c77ebda134f Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Mon, 2 May 2022 10:42:23 -0700
Subject: [PATCH 045/679] added gltf sample data for the shapes
---
.../Cesium3DTiles/Voxel/VoxelBoxGltf/a.bin | Bin 0 -> 32 bytes
.../Voxel/VoxelBoxGltf/schema.json | 14 +++
.../Voxel/VoxelBoxGltf/voxelBox.gltf | 88 +++++++++++++++
.../Voxel/VoxelCylinderGltf/a.bin | Bin 0 -> 32 bytes
.../Voxel/VoxelCylinderGltf/schema.json | 14 +++
.../VoxelCylinderGltf/voxelCylinder.gltf | 88 +++++++++++++++
.../Voxel/VoxelEllipsoidGltf/a.bin | Bin 0 -> 32 bytes
.../Voxel/VoxelEllipsoidGltf/schema.json | 14 +++
.../VoxelEllipsoidGltf/voxelEllipsoid.gltf | 100 ++++++++++++++++++
Source/Scene/GltfVoxelProvider.js | 11 +-
Source/Scene/VoxelEllipsoidShape.js | 1 -
Source/Scene/VoxelPrimitive.js | 3 -
12 files changed, 322 insertions(+), 11 deletions(-)
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/a.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/schema.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/voxelBox.gltf
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/a.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/schema.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/voxelCylinder.gltf
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/a.bin
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/schema.json
create mode 100644 Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/voxelEllipsoid.gltf
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/a.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/schema.json
new file mode 100644
index 00000000000..d6b1e9d87f0
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/schema.json
@@ -0,0 +1,14 @@
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/voxelBox.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/voxelBox.gltf
new file mode 100644
index 00000000000..0f89c2221c6
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelBoxGltf/voxelBox.gltf
@@ -0,0 +1,88 @@
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483648,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/a.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/schema.json
new file mode 100644
index 00000000000..d6b1e9d87f0
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/schema.json
@@ -0,0 +1,14 @@
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/voxelCylinder.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/voxelCylinder.gltf
new file mode 100644
index 00000000000..6ef6eb1759c
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelCylinderGltf/voxelCylinder.gltf
@@ -0,0 +1,88 @@
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483650,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ]
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/a.bin b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/a.bin
new file mode 100644
index 0000000000000000000000000000000000000000..6eac4f3e1f1fcfd58880ad428efeb0df90514357
GIT binary patch
literal 32
QcmZQzKnD%>3=9Yi02c%T{Qv*}
literal 0
HcmV?d00001
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/schema.json b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/schema.json
new file mode 100644
index 00000000000..d6b1e9d87f0
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/schema.json
@@ -0,0 +1,14 @@
+{
+ "id": "voxel",
+ "classes": {
+ "voxel": {
+ "properties": {
+ "a": {
+ "type": "FLOAT32",
+ "required": false
+ }
+ }
+ },
+ "tile": {}
+ }
+}
\ No newline at end of file
diff --git a/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/voxelEllipsoid.gltf b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/voxelEllipsoid.gltf
new file mode 100644
index 00000000000..39b31e4dd3c
--- /dev/null
+++ b/Apps/SampleData/Cesium3DTiles/Voxel/VoxelEllipsoidGltf/voxelEllipsoid.gltf
@@ -0,0 +1,100 @@
+{
+ "asset": {
+ "version": "2.0"
+ },
+ "scene": 0,
+ "scenes": [
+ {
+ "nodes": [
+ 0
+ ]
+ }
+ ],
+ "nodes": [
+ {
+ "mesh": 0
+ }
+ ],
+ "meshes": [
+ {
+ "primitives": [
+ {
+ "mode": 2147483649,
+ "attributes": {
+ "_a": 0
+ },
+ "extensions": {
+ "EXT_primitive_voxels": {
+ "dimensions": [
+ 2,
+ 2,
+ 2
+ ],
+ "bounds": {
+ "min": [
+ -3.141592653589793,
+ -1.5707963267948966,
+ 1000000.0
+ ],
+ "max": [
+ 3.141592653589793,
+ 1.5707963267948966,
+ 2000000.0
+ ]
+ }
+ }
+ }
+ }
+ ]
+ }
+ ],
+ "extensionsUsed": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensionsRequired": [
+ "EXT_primitive_voxels",
+ "EXT_structural_metadata"
+ ],
+ "extensions": {
+ "EXT_structural_metadata": {
+ "schemaUri": "schema.json",
+ "propertyAttributes": [
+ {
+ "class": "voxel",
+ "properties": {
+ "a": {
+ "attribute": "_a"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "accessors": [
+ {
+ "bufferView": 0,
+ "type": "SCALAR",
+ "componentType": 5126,
+ "min": [
+ 0.0
+ ],
+ "max": [
+ 1.0
+ ],
+ "count": 8
+ }
+ ],
+ "bufferViews": [
+ {
+ "buffer": 0,
+ "byteLength": 32
+ }
+ ],
+ "buffers": [
+ {
+ "uri": "a.bin",
+ "byteLength": 32
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Source/Scene/GltfVoxelProvider.js b/Source/Scene/GltfVoxelProvider.js
index 60536d7dc03..cb8299748cc 100644
--- a/Source/Scene/GltfVoxelProvider.js
+++ b/Source/Scene/GltfVoxelProvider.js
@@ -324,19 +324,16 @@ GltfVoxelProvider.prototype.update = function (frameState) {
*/
GltfVoxelProvider.prototype.requestData = function (options) {
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- const tileX = defaultValue(options.tileX, 0);
- const tileY = defaultValue(options.tileY, 0);
- const tileZ = defaultValue(options.tileZ, 0);
- const tileLevel = defaultValue(options.tileLevel, 0);
-
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError(
"requestData must not be called before the provider is ready."
);
}
- if (!(tileLevel === 0 && tileX === 0 && tileY === 0 && tileZ === 0)) {
- throw new DeveloperError("GltfVoxelProvider can only have one tile");
+
+ const tileLevel = defaultValue(options.tileLevel, 0);
+ if (tileLevel > 0) {
+ return undefined;
}
//>>includeEnd('debug');
diff --git a/Source/Scene/VoxelEllipsoidShape.js b/Source/Scene/VoxelEllipsoidShape.js
index 728a846a45c..fd75db09429 100644
--- a/Source/Scene/VoxelEllipsoidShape.js
+++ b/Source/Scene/VoxelEllipsoidShape.js
@@ -468,7 +468,6 @@ VoxelEllipsoidShape.prototype.update = function (
hasLongitudeRangeOverHalfShape;
// Latitude
- const latitudeRangeRender = latitudeMaxRender - latitudeMinRender;
const hasLatitudeMaxUnderHalfRender =
latitudeMaxRender < -flatLatitudeEpsilon;
const hasLatitudeMaxEqualHalfRender =
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 060a50acc5f..cd9b3d33d87 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -2221,9 +2221,6 @@ function buildDrawCommands(that, context) {
that._drawCommand = drawCommand;
that._drawCommandPick = drawCommandPick;
-
- // console.log(drawCommand.shaderProgram._fragmentShaderText);
- console.log("recompile");
}
/**
From 5bb5943adb2d10bb6cb3748052d465f875d5b2ca Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Mon, 2 May 2022 10:50:01 -0700
Subject: [PATCH 046/679] fixed cylinder min radius change
---
Source/Scene/VoxelCylinderShape.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Source/Scene/VoxelCylinderShape.js b/Source/Scene/VoxelCylinderShape.js
index 6cf44efe5ba..9f67926f8b6 100644
--- a/Source/Scene/VoxelCylinderShape.js
+++ b/Source/Scene/VoxelCylinderShape.js
@@ -382,7 +382,7 @@ VoxelCylinderShape.prototype.update = function (
shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MIN"] = intersectionCount;
intersectionCount += 1;
- shaderUniforms.cylinderInverseMinRadiusUv =
+ shaderUniforms.cylinderUvToRenderRadiusMin =
renderMaxRadius / renderMinRadius;
}
if (!isDefaultMaxRadiusRender) {
From 3f5683583eb0dc4816bd272fff19df0cf4c28115 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Mon, 2 May 2022 12:26:18 -0700
Subject: [PATCH 047/679] simplified the shader step calculation
---
Source/Shaders/VoxelFS.glsl | 29 ++++++++++++-----------------
1 file changed, 12 insertions(+), 17 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 802b629d6bb..470966c2c8a 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1551,8 +1551,7 @@ void traverseOctreeDownwards(in vec3 positionUv, inout ivec4 octreeCoords, inout
}
}
-void traverseOctree(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, out float levelStepMult, out ivec4 octreeCoords, out int parentOctreeIndex, out SampleData sampleDatas[SAMPLE_COUNT]) {
- levelStepMult = 1.0;
+void traverseOctree(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, out float stepT, out ivec4 octreeCoords, out int parentOctreeIndex, out SampleData sampleDatas[SAMPLE_COUNT]) {
octreeCoords = ivec4(0);
parentOctreeIndex = 0;
@@ -1564,17 +1563,18 @@ void traverseOctree(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3
if (rootData.flag == OCTREE_FLAG_LEAF) {
// No child data, only the root tile has data
getOctreeLeafData(rootData, sampleDatas);
+ stepT = u_stepSize;
}
else
{
traverseOctreeDownwards(positionUvShapeSpace, octreeCoords, parentOctreeIndex, sampleDatas);
- levelStepMult = 1.0 / pow(2.0, float(octreeCoords.w));
- vec3 boxStart = vec3(octreeCoords.xyz) * levelStepMult;
- positionUvLocal = (positionUvShapeSpace - boxStart) / levelStepMult;
+ float dimAtLevel = pow(2.0, float(octreeCoords.w));
+ positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords);
+ stepT = u_stepSize / dimAtLevel;
}
}
-void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, inout float levelStepMult, inout ivec4 octreeCoords, inout int parentOctreeIndex, inout SampleData sampleDatas[SAMPLE_COUNT]) {
+void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, inout float stepT, inout ivec4 octreeCoords, inout int parentOctreeIndex, inout SampleData sampleDatas[SAMPLE_COUNT]) {
float dimAtLevel = pow(2.0, float(octreeCoords.w));
positionUvShapeSpace = convertUvToShapeUvSpace(positionUv);
positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
@@ -1603,8 +1603,9 @@ void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpac
// Go down tree
traverseOctreeDownwards(positionUvShapeSpace, octreeCoords, parentOctreeIndex, sampleDatas);
- levelStepMult = 1.0 / pow(2.0, float(octreeCoords.w));
- positionUvLocal = positionUvShapeSpace / levelStepMult - vec3(octreeCoords.xyz);
+ float dimAtLevel = pow(2.0, float(octreeCoords.w));
+ positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
+ stepT = u_stepSize / dimAtLevel;
}
}
@@ -1642,14 +1643,11 @@ void main()
// Traverse the tree from the start position
vec3 positionUvShapeSpace;
vec3 positionUvLocal;
- float levelStepMult;
+ float stepT;
ivec4 octreeCoords;
int parentOctreeIndex;
SampleData sampleDatas[SAMPLE_COUNT];
- traverseOctree(positionUv, positionUvShapeSpace, positionUvLocal, levelStepMult, octreeCoords, parentOctreeIndex, sampleDatas);
-
- // Adjust the step size based on the level in the tree
- float stepT = u_stepSize * levelStepMult;
+ traverseOctree(positionUv, positionUvShapeSpace, positionUvLocal, stepT, octreeCoords, parentOctreeIndex, sampleDatas);
#if defined(JITTER)
float noise = hash(screenCoord); // [0,1]
@@ -1744,10 +1742,7 @@ void main()
// Traverse the tree from the current ray position.
// This is similar to traverseOctree but is optimized for the common
// case where the ray is in the same tile as the previous step.
- traverseOctreeFromExisting(positionUv, positionUvShapeSpace, positionUvLocal, levelStepMult, octreeCoords, parentOctreeIndex, sampleDatas);
-
- // Adjust the step size based on the level in the tree
- stepT = u_stepSize * levelStepMult;
+ traverseOctreeFromExisting(positionUv, positionUvShapeSpace, positionUvLocal, stepT, octreeCoords, parentOctreeIndex, sampleDatas);
}
// Convert the alpha from [0,ALPHA_ACCUM_MAX] to [0,1]
From 1e21610bdb15676bce1751bba551011b962b5f41 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 3 May 2022 09:05:15 -0700
Subject: [PATCH 048/679] moved traversal details into a struct
---
Source/Shaders/VoxelFS.glsl | 92 ++++++++++++++++++++-----------------
1 file changed, 50 insertions(+), 42 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 470966c2c8a..cb688e445b7 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -165,6 +165,14 @@ struct OctreeNodeData {
int flag;
};
+struct TraversalData {
+ vec3 positionUvShapeSpace;
+ vec3 positionUvLocal;
+ float stepT;
+ ivec4 octreeCoords;
+ int parentOctreeIndex;
+};
+
struct SampleData {
int megatextureIndex;
int levelsAbove;
@@ -1551,61 +1559,61 @@ void traverseOctreeDownwards(in vec3 positionUv, inout ivec4 octreeCoords, inout
}
}
-void traverseOctree(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, out float stepT, out ivec4 octreeCoords, out int parentOctreeIndex, out SampleData sampleDatas[SAMPLE_COUNT]) {
- octreeCoords = ivec4(0);
- parentOctreeIndex = 0;
+void traverseOctree(in vec3 positionUv, out TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
+ traversalData.octreeCoords = ivec4(0);
+ traversalData.parentOctreeIndex = 0;
// TODO: is it possible for this to be out of bounds, and does it matter?
- positionUvShapeSpace = convertUvToShapeUvSpace(positionUv);
- positionUvLocal = positionUvShapeSpace;
+ traversalData.positionUvShapeSpace = convertUvToShapeUvSpace(positionUv);
+ traversalData.positionUvLocal = traversalData.positionUvShapeSpace;
OctreeNodeData rootData = getOctreeRootData();
if (rootData.flag == OCTREE_FLAG_LEAF) {
// No child data, only the root tile has data
getOctreeLeafData(rootData, sampleDatas);
- stepT = u_stepSize;
+ traversalData.stepT = u_stepSize;
}
else
{
- traverseOctreeDownwards(positionUvShapeSpace, octreeCoords, parentOctreeIndex, sampleDatas);
- float dimAtLevel = pow(2.0, float(octreeCoords.w));
- positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords);
- stepT = u_stepSize / dimAtLevel;
+ traverseOctreeDownwards(traversalData.positionUvShapeSpace, traversalData.octreeCoords, traversalData.parentOctreeIndex, sampleDatas);
+ float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
+ traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords);
+ traversalData.stepT = u_stepSize / dimAtLevel;
}
}
-void traverseOctreeFromExisting(in vec3 positionUv, out vec3 positionUvShapeSpace, out vec3 positionUvLocal, inout float stepT, inout ivec4 octreeCoords, inout int parentOctreeIndex, inout SampleData sampleDatas[SAMPLE_COUNT]) {
- float dimAtLevel = pow(2.0, float(octreeCoords.w));
- positionUvShapeSpace = convertUvToShapeUvSpace(positionUv);
- positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
+void traverseOctreeFromExisting(in vec3 positionUv, inout TraversalData traversalData, inout SampleData sampleDatas[SAMPLE_COUNT]) {
+ float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
+ traversalData.positionUvShapeSpace = convertUvToShapeUvSpace(positionUv);
+ traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords.xyz);
// Note: This code assumes the position is always inside the root tile.
- bool insideTile = octreeCoords.w == 0 || inRange(positionUvLocal, vec3(0.0), vec3(1.0));
+ bool insideTile = traversalData.octreeCoords.w == 0 || inRange(traversalData.positionUvLocal, vec3(0.0), vec3(1.0));
if (!insideTile)
{
// Go up tree
for (int i = 0; i < OCTREE_MAX_LEVELS; ++i)
{
- octreeCoords.xyz /= ivec3(2);
- octreeCoords.w -= 1;
- dimAtLevel /= 2.0;
+ traversalData.octreeCoords.xyz /= ivec3(2);
+ traversalData.octreeCoords.w -= 1;
+ dimAtLevel *= 0.5;
- positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
- insideTile = octreeCoords.w == 0 || inRange(positionUvLocal, vec3(0.0), vec3(1.0));
+ traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords.xyz);
+ insideTile = traversalData.octreeCoords.w == 0 || inRange(traversalData.positionUvLocal, vec3(0.0), vec3(1.0));
if (!insideTile) {
- parentOctreeIndex = getOctreeParentIndex(parentOctreeIndex);
+ traversalData.parentOctreeIndex = getOctreeParentIndex(traversalData.parentOctreeIndex);
} else {
break;
}
}
// Go down tree
- traverseOctreeDownwards(positionUvShapeSpace, octreeCoords, parentOctreeIndex, sampleDatas);
- float dimAtLevel = pow(2.0, float(octreeCoords.w));
- positionUvLocal = positionUvShapeSpace * dimAtLevel - vec3(octreeCoords.xyz);
- stepT = u_stepSize / dimAtLevel;
+ traverseOctreeDownwards(traversalData.positionUvShapeSpace, traversalData.octreeCoords, traversalData.parentOctreeIndex, sampleDatas);
+ float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
+ traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords.xyz);
+ traversalData.stepT = u_stepSize / dimAtLevel;
}
}
@@ -1630,8 +1638,6 @@ void main()
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
- // gl_FragColor = vec4(convertUvToShapeUvSpace(positionUv).yyy, 1.0); return;
-
vec4 colorAccum = vec4(0.0);
#if defined(DESPECKLE)
@@ -1641,18 +1647,20 @@ void main()
#endif
// Traverse the tree from the start position
- vec3 positionUvShapeSpace;
- vec3 positionUvLocal;
- float stepT;
- ivec4 octreeCoords;
- int parentOctreeIndex;
+ TraversalData traversalData;
SampleData sampleDatas[SAMPLE_COUNT];
- traverseOctree(positionUv, positionUvShapeSpace, positionUvLocal, stepT, octreeCoords, parentOctreeIndex, sampleDatas);
+ traverseOctree(positionUv, traversalData, sampleDatas);
+
+ // if (traversalData.octreeCoords.w == 6 && traversalData.octreeCoords.x == 33 && traversalData.octreeCoords.y == 49 && traversalData.octreeCoords.z == 63) {
+ // if (sampleDatas[0].levelsAbove == 1) {
+ // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); return;
+ // }
+ // }
#if defined(JITTER)
float noise = hash(screenCoord); // [0,1]
- currT += noise * stepT;
- positionUv += noise * stepT * viewDirUv;
+ currT += noise * traversalData.stepT;
+ positionUv += noise * traversalData.stepT * viewDirUv;
#endif
FragmentInput fragmentInput;
@@ -1662,16 +1670,16 @@ void main()
for (int stepCount = 0; stepCount < STEP_COUNT_MAX; ++stepCount) {
// Read properties from the megatexture based on the traversal state
- Properties properties = getPropertiesFromMegatextureAtLocalPosition(positionUvLocal, octreeCoords, sampleDatas);
+ Properties properties = getPropertiesFromMegatextureAtLocalPosition(traversalData.positionUvLocal, traversalData.octreeCoords, sampleDatas);
// Prepare the custom shader inputs
copyPropertiesToMetadata(properties, fragmentInput.metadata);
fragmentInput.voxel.positionUv = positionUv;
- fragmentInput.voxel.positionShapeUv = positionUvShapeSpace;
- fragmentInput.voxel.positionUvLocal = positionUvLocal;
+ fragmentInput.voxel.positionShapeUv = traversalData.positionUvShapeSpace;
+ fragmentInput.voxel.positionUvLocal = traversalData.positionUvLocal;
fragmentInput.voxel.viewDirUv = viewDirUv;
fragmentInput.voxel.viewDirWorld = viewDirWorld;
- fragmentInput.voxel.travelDistance = stepT;
+ fragmentInput.voxel.travelDistance = traversalData.stepT;
#if defined(STYLE_USE_POSITION_EC)
styleInput.positionEC = vec3(u_transformPositionUvToView * vec4(positionUv, 1.0));
@@ -1719,8 +1727,8 @@ void main()
}
// Keep raymarching
- currT += stepT;
- positionUv += stepT * viewDirUv;
+ currT += traversalData.stepT;
+ positionUv += traversalData.stepT * viewDirUv;
// Check if there's more intersections.
if (currT > endT) {
@@ -1742,7 +1750,7 @@ void main()
// Traverse the tree from the current ray position.
// This is similar to traverseOctree but is optimized for the common
// case where the ray is in the same tile as the previous step.
- traverseOctreeFromExisting(positionUv, positionUvShapeSpace, positionUvLocal, stepT, octreeCoords, parentOctreeIndex, sampleDatas);
+ traverseOctreeFromExisting(positionUv, traversalData, sampleDatas);
}
// Convert the alpha from [0,ALPHA_ACCUM_MAX] to [0,1]
From 2b49355f9dd6eeaea285bec89c8395d46975e541 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 3 May 2022 09:11:40 -0700
Subject: [PATCH 049/679] one more place for traversal struct
---
Source/Shaders/VoxelFS.glsl | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index cb688e445b7..80c813878cc 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1530,28 +1530,28 @@ int getOctreeParentIndex(int octreeIndex) {
return parentOctreeIndex;
}
-void traverseOctreeDownwards(in vec3 positionUv, inout ivec4 octreeCoords, inout int parentOctreeIndex, out SampleData sampleDatas[SAMPLE_COUNT]) {
- float sizeAtLevel = 1.0 / pow(2.0, float(octreeCoords.w));
- vec3 start = vec3(octreeCoords.xyz) * sizeAtLevel;
+void traverseOctreeDownwards(inout TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
+ float sizeAtLevel = 1.0 / pow(2.0, float(traversalData.octreeCoords.w));
+ vec3 start = vec3(traversalData.octreeCoords.xyz) * sizeAtLevel;
vec3 end = start + vec3(sizeAtLevel);
for (int i = 0; i < OCTREE_MAX_LEVELS; ++i) {
// Find out which octree child contains the position
// 0 if before center, 1 if after
vec3 center = 0.5 * (start + end);
- vec3 childCoord = step(center, positionUv);
+ vec3 childCoord = step(center, traversalData.positionUvShapeSpace);
// Get octree coords for the next level down
- octreeCoords.xyz = octreeCoords.xyz * 2 + ivec3(childCoord);
- octreeCoords.w += 1;
+ traversalData.octreeCoords.xyz = traversalData.octreeCoords.xyz * 2 + ivec3(childCoord);
+ traversalData.octreeCoords.w += 1;
- OctreeNodeData childData = getOctreeChildData(parentOctreeIndex, ivec3(childCoord));
+ OctreeNodeData childData = getOctreeChildData(traversalData.parentOctreeIndex, ivec3(childCoord));
if (childData.flag == OCTREE_FLAG_INTERNAL) {
// keep going deeper
start = mix(start, center, childCoord);
end = mix(center, end, childCoord);
- parentOctreeIndex = childData.data;
+ traversalData.parentOctreeIndex = childData.data;
} else {
getOctreeLeafData(childData, sampleDatas);
return;
@@ -1575,7 +1575,7 @@ void traverseOctree(in vec3 positionUv, out TraversalData traversalData, out Sam
}
else
{
- traverseOctreeDownwards(traversalData.positionUvShapeSpace, traversalData.octreeCoords, traversalData.parentOctreeIndex, sampleDatas);
+ traverseOctreeDownwards(traversalData, sampleDatas);
float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords);
traversalData.stepT = u_stepSize / dimAtLevel;
@@ -1610,7 +1610,7 @@ void traverseOctreeFromExisting(in vec3 positionUv, inout TraversalData traversa
}
// Go down tree
- traverseOctreeDownwards(traversalData.positionUvShapeSpace, traversalData.octreeCoords, traversalData.parentOctreeIndex, sampleDatas);
+ traverseOctreeDownwards(traversalData, sampleDatas);
float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords.xyz);
traversalData.stepT = u_stepSize / dimAtLevel;
From 0e3f1816b53f917bc7f74b2157ca7d2ddf6d3e45 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 3 May 2022 10:59:48 -0700
Subject: [PATCH 050/679] fixed traversal bug for parent with not all children
loaded
---
Source/Shaders/VoxelFS.glsl | 67 ++++++++++++++-----------------------
1 file changed, 26 insertions(+), 41 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 80c813878cc..e6f2182228e 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -175,7 +175,8 @@ struct TraversalData {
struct SampleData {
int megatextureIndex;
- int levelsAbove;
+ int levelsAbove; // TODO find a way to remove this
+ vec3 tileUv;
#if (SAMPLE_COUNT > 1)
float weight;
#endif
@@ -1418,7 +1419,9 @@ Properties getPropertiesFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 vox
}
#endif
-Properties getPropertiesFromMegatextureAtTileUv(vec3 tileUv, int tileIndex) {
+Properties getPropertiesFromMegatextureAtTileUv(in SampleData sampleData) {
+ vec3 tileUv = clamp(sampleData.tileUv, vec3(0.0), vec3(1.0)); // TODO is the clamp necessary?
+ int tileIndex = sampleData.megatextureIndex;
vec3 voxelCoord = tileUv * vec3(u_dimensions);
ivec3 dimensions = u_dimensions;
@@ -1434,34 +1437,15 @@ Properties getPropertiesFromMegatextureAtTileUv(vec3 tileUv, int tileIndex) {
#endif
}
-vec3 computeAncestorUv(vec3 positionUvLocal, int levelsAbove, ivec4 octreeCoords) {
- if (levelsAbove > 0) {
- // In some cases positionUvLocal goes outside the 0 to 1 bounds, such as when sampling neighbor voxels on the edge of a tile.
- // This needs to be handled carefully, especially for mixed resolution, or else the wrong part of the tile is read.
- // https://www.wolframalpha.com/input/?i=sign%28x%29+*+max%280%2C+%28abs%28x-0.5%29-0.5%29%29
- vec3 overflow = sign(positionUvLocal) * max(abs(positionUvLocal - vec3(0.5)) - vec3(0.5), vec3(0.0));
- positionUvLocal = clamp(positionUvLocal, vec3(0.0), vec3(1.0 - czm_epsilon6)); // epsilon to avoid fract(1) = 0 situation
-
- // Calcuate a new local uv relative to the ancestor tile.
- float levelsAboveFactor = 1.0 / pow(2.0, float(levelsAbove));
- positionUvLocal = fract((vec3(octreeCoords.xyz) + positionUvLocal) * levelsAboveFactor) + overflow * levelsAboveFactor;
- } else {
- positionUvLocal = clamp(positionUvLocal, vec3(0.0), vec3(1.0));
- }
- return positionUvLocal;
-}
-
// Convert an array of mixed-resolution sample datas to a final weighted properties.
-Properties getPropertiesFromMegatextureAtLocalPosition(vec3 positionUvLocal, ivec4 octreeCoords, SampleData sampleDatas[SAMPLE_COUNT]) {
+Properties getPropertiesFromMegatextureAtLocalPosition(SampleData sampleDatas[SAMPLE_COUNT]) {
#if (SAMPLE_COUNT == 1)
- vec3 actualUv = computeAncestorUv(positionUvLocal, sampleDatas[0].levelsAbove, octreeCoords);
- return getPropertiesFromMegatextureAtTileUv(actualUv, sampleDatas[0].megatextureIndex);
+ return getPropertiesFromMegatextureAtTileUv(sampleDatas[0]);
#else
// When more than one sample is taken the accumulator needs to start at 0
Properties properties = clearProperties();
for (int i = 0; i < SAMPLE_COUNT; ++i) {
- vec3 actualUv = computeAncestorUv(positionUvLocal, sampleDatas[i].levelsAbove, octreeCoords);
- Properties tempProperties = getPropertiesFromMegatextureAtTileUv(actualUvLocal, sampleDatas[i].megatextureIndex);
+ Properties tempProperties = getPropertiesFromMegatextureAtTileUv(sampleDatas[i]);
properties = sumProperties(properties, tempProperties)
}
return properties;
@@ -1541,19 +1525,32 @@ void traverseOctreeDownwards(inout TraversalData traversalData, out SampleData s
vec3 center = 0.5 * (start + end);
vec3 childCoord = step(center, traversalData.positionUvShapeSpace);
- // Get octree coords for the next level down
- traversalData.octreeCoords.xyz = traversalData.octreeCoords.xyz * 2 + ivec3(childCoord);
- traversalData.octreeCoords.w += 1;
-
OctreeNodeData childData = getOctreeChildData(traversalData.parentOctreeIndex, ivec3(childCoord));
+ // Get octree coords for the next level down
+ ivec4 parentOctreeCoords = traversalData.octreeCoords;
+ traversalData.octreeCoords = ivec4(parentOctreeCoords.xyz * 2 + ivec3(childCoord), parentOctreeCoords.w + 1);
+
if (childData.flag == OCTREE_FLAG_INTERNAL) {
// keep going deeper
start = mix(start, center, childCoord);
end = mix(center, end, childCoord);
traversalData.parentOctreeIndex = childData.data;
} else {
+ float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
+ traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords.xyz);
+ traversalData.stepT = u_stepSize / dimAtLevel;
+
getOctreeLeafData(childData, sampleDatas);
+ for (int i = 0; i < SAMPLE_COUNT; i++) {
+ bool usingParent = sampleDatas[i].levelsAbove == 1;
+ if (usingParent) {
+ float parentDimAtLevel = pow(2.0, float(parentOctreeCoords.w));
+ sampleDatas[i].tileUv = traversalData.positionUvShapeSpace * parentDimAtLevel - vec3(parentOctreeCoords.xyz);
+ } else {
+ sampleDatas[i].tileUv = traversalData.positionUvLocal;
+ }
+ }
return;
}
}
@@ -1576,9 +1573,6 @@ void traverseOctree(in vec3 positionUv, out TraversalData traversalData, out Sam
else
{
traverseOctreeDownwards(traversalData, sampleDatas);
- float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
- traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords);
- traversalData.stepT = u_stepSize / dimAtLevel;
}
}
@@ -1611,9 +1605,6 @@ void traverseOctreeFromExisting(in vec3 positionUv, inout TraversalData traversa
// Go down tree
traverseOctreeDownwards(traversalData, sampleDatas);
- float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
- traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords.xyz);
- traversalData.stepT = u_stepSize / dimAtLevel;
}
}
@@ -1651,12 +1642,6 @@ void main()
SampleData sampleDatas[SAMPLE_COUNT];
traverseOctree(positionUv, traversalData, sampleDatas);
- // if (traversalData.octreeCoords.w == 6 && traversalData.octreeCoords.x == 33 && traversalData.octreeCoords.y == 49 && traversalData.octreeCoords.z == 63) {
- // if (sampleDatas[0].levelsAbove == 1) {
- // gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); return;
- // }
- // }
-
#if defined(JITTER)
float noise = hash(screenCoord); // [0,1]
currT += noise * traversalData.stepT;
@@ -1670,7 +1655,7 @@ void main()
for (int stepCount = 0; stepCount < STEP_COUNT_MAX; ++stepCount) {
// Read properties from the megatexture based on the traversal state
- Properties properties = getPropertiesFromMegatextureAtLocalPosition(traversalData.positionUvLocal, traversalData.octreeCoords, sampleDatas);
+ Properties properties = getPropertiesFromMegatextureAtLocalPosition(sampleDatas);
// Prepare the custom shader inputs
copyPropertiesToMetadata(properties, fragmentInput.metadata);
From c70521a3bc7313fcf3f5ae5edfe3cdb608e4d516 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 3 May 2022 12:29:35 -0700
Subject: [PATCH 051/679] better naming for the megatexture functions
---
Source/Scene/VoxelPrimitive.js | 6 ++---
Source/Scene/VoxelTraversal.js | 4 +--
Source/Shaders/VoxelFS.glsl | 49 ++++++++++++++++------------------
3 files changed, 28 insertions(+), 31 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index cd9b3d33d87..58bebcfba52 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -2141,12 +2141,12 @@ function buildDrawCommands(that, context) {
}
}
- // getPropertiesFrom2DMegatextureAtUv
+ // getPropertiesFromMegatextureAtUv
{
- const functionId = "getPropertiesFrom2DMegatextureAtUv";
+ const functionId = "getPropertiesFromMegatextureAtUv";
shaderBuilder.addFunction(
functionId,
- `${propertiesStructName} getPropertiesFrom2DMegatextureAtUv(vec2 texcoord)`,
+ `${propertiesStructName} getPropertiesFromMegatextureAtUv(vec2 texcoord)`,
ShaderDestination.FRAGMENT
);
shaderBuilder.addFunctionLines(functionId, [
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index ef0812f4057..945d8995043 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -244,7 +244,7 @@ function VoxelTraversal(
*/
this.leafNodeTexelSizeUv = undefined;
- const useLeafNodeTexture = this._useLeafNodeTexture;
+ const useLeafNodeTexture = this.useLeafNodeTexture;
if (useLeafNodeTexture) {
const leafNodeTexelCount = 2;
const leafNodeTextureDimensionX = 1024;
@@ -1415,7 +1415,7 @@ const GpuOctreeFlag = {
*/
function generateOctree(that) {
const keyframeLocation = that._keyframeLocation;
- const useLeafNodes = that._useLeafNodeTexture;
+ const useLeafNodes = that.useLeafNodeTexture;
const frameNumber = that._frameNumber;
let internalNodeCount = 0;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index e6f2182228e..22ddf3c3407 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -104,17 +104,18 @@ void setStatistics(inout Statistics statistics) {
statistics.temperature.min = 20.0;
statistics.temperature.max = 50.0;
}
-Properties getPropertiesFrom2DMegatextureAtUv(vec2 texcoord) {
- Properties properties;
- properties.temperature = texture2D(u_megatextureTextures[0], texcoord).r;
- properties.direction = texture2D(u_megatextureTextures[1], texcoord).rgb;
- return properties;
-}
-Properties getPropertiesFrom3DMegatextureAtUv(vec3 texcoord) {
- Properties properties;
- properties.temperature = texture3D(u_megatextureTextures[0], texcoord).r;
- properties.direction = texture3D(u_megatextureTextures[1], texcoord).rgb;
- return properties;
+Properties getPropertiesFromMegatextureAtUv(vec2 texcoord) {
+ #if defined(MEGATEXTURE_2D)
+ Properties properties;
+ properties.temperature = texture2D(u_megatextureTextures[0], texcoord).r;
+ properties.direction = texture2D(u_megatextureTextures[1], texcoord).rgb;
+ return properties;
+ #else
+ Properties properties;
+ properties.temperature = texture3D(u_megatextureTextures[0], texcoord).r;
+ properties.direction = texture3D(u_megatextureTextures[1], texcoord).rgb;
+ return properties;
+ #endif
}
void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
vec3 direction = fsInput.metadata.direction;
@@ -1383,7 +1384,7 @@ Properties getPropertiesFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxel
When doing nearest neighbor the megatexture only needs to be sampled once at the closest Z slice.
*/
-Properties getPropertiesFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
+Properties getPropertiesFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
{
#if defined(NEAREST_SAMPLING)
// Round to the center of the nearest voxel
@@ -1406,20 +1407,20 @@ Properties getPropertiesFrom2DMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 vox
vec2 uv0 = tileUvOffset + sliceUvOffset0 + voxelUvOffset;
#if defined(NEAREST_SAMPLING)
- return getPropertiesFrom2DMegatextureAtUv(uv0);
+ return getPropertiesFromMegatextureAtUv(uv0);
#else
float sliceLerp = fract(slice);
int sliceIndex1 = intMin(sliceIndex + 1, voxelDims.z - 1);
vec2 sliceUvOffset1 = index1DTo2DTexcoord(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
- Properties properties0 = getPropertiesFrom2DMegatextureAtUv(uv0);
- Properties properties1 = getPropertiesFrom2DMegatextureAtUv(uv1);
+ Properties properties0 = getPropertiesFromMegatextureAtUv(uv0);
+ Properties properties1 = getPropertiesFromMegatextureAtUv(uv1);
return mixProperties(properties0, properties1, sliceLerp);
#endif
}
#endif
-Properties getPropertiesFromMegatextureAtTileUv(in SampleData sampleData) {
+Properties getPropertiesFromMegatexture(in SampleData sampleData) {
vec3 tileUv = clamp(sampleData.tileUv, vec3(0.0), vec3(1.0)); // TODO is the clamp necessary?
int tileIndex = sampleData.megatextureIndex;
vec3 voxelCoord = tileUv * vec3(u_dimensions);
@@ -1430,22 +1431,18 @@ Properties getPropertiesFromMegatextureAtTileUv(in SampleData sampleData) {
voxelCoord += vec3(u_paddingBefore);
#endif
- #if defined(MEGATEXTURE_3D)
- return getPropertiesFrom3DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
- #elif defined(MEGATEXTURE_2D)
- return getPropertiesFrom2DMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
- #endif
+ return getPropertiesFromMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
}
-// Convert an array of mixed-resolution sample datas to a final weighted properties.
-Properties getPropertiesFromMegatextureAtLocalPosition(SampleData sampleDatas[SAMPLE_COUNT]) {
+// Convert an array of sample datas to a final weighted properties.
+Properties getPropertiesFromMegatexture(SampleData sampleDatas[SAMPLE_COUNT]) {
#if (SAMPLE_COUNT == 1)
- return getPropertiesFromMegatextureAtTileUv(sampleDatas[0]);
+ return getPropertiesFromMegatexture(sampleDatas[0]);
#else
// When more than one sample is taken the accumulator needs to start at 0
Properties properties = clearProperties();
for (int i = 0; i < SAMPLE_COUNT; ++i) {
- Properties tempProperties = getPropertiesFromMegatextureAtTileUv(sampleDatas[i]);
+ Properties tempProperties = getPropertiesFromMegatexture(sampleDatas[i]);
properties = sumProperties(properties, tempProperties)
}
return properties;
@@ -1655,7 +1652,7 @@ void main()
for (int stepCount = 0; stepCount < STEP_COUNT_MAX; ++stepCount) {
// Read properties from the megatexture based on the traversal state
- Properties properties = getPropertiesFromMegatextureAtLocalPosition(sampleDatas);
+ Properties properties = getPropertiesFromMegatexture(sampleDatas);
// Prepare the custom shader inputs
copyPropertiesToMetadata(properties, fragmentInput.metadata);
From 20ef9aab24561261bcdaedb793ff8c4a029fc280 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 3 May 2022 12:42:25 -0700
Subject: [PATCH 052/679] more simplification of the megatexture reading
functions
---
Source/Shaders/VoxelFS.glsl | 104 +++++++++++++++++-------------------
1 file changed, 48 insertions(+), 56 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 22ddf3c3407..d340886b53d 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1331,26 +1331,6 @@ vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Int
// --------------------------------------------------------
// Megatexture
// --------------------------------------------------------
-
-// TODO: 3D megatexture has not been implemented yet
-#if defined(MEGATEXTURE_3D)
-Properties getPropertiesFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
-{
- // Tile location
- vec3 tileUvOffset = indexToUv3d(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
-
- // Voxel location
- vec3 voxelUvOffset = clamp(voxelCoord, vec3(0.5), vec3(voxelDims) - vec2(0.5)) * u_megatextureVoxelSizeUv;
-
- // Final location in the megatexture
- vec3 uv = tileUvOffset + voxelUvOffset;
-
- for (int i = 0; i < PROPERTY_COUNT; ++i) {
- vec4 sample = texture3D(u_megatextureTextures[i], uv);
- samples[i] = decodeTextureSample(sample);
- }
-}
-#elif defined(MEGATEXTURE_2D)
/*
How is 3D data stored in a 2D megatexture?
@@ -1384,54 +1364,66 @@ Properties getPropertiesFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxel
When doing nearest neighbor the megatexture only needs to be sampled once at the closest Z slice.
*/
-Properties getPropertiesFromMegatextureAtVoxelCoord(vec3 voxelCoord, ivec3 voxelDims, int tileIndex)
-{
+
+Properties getPropertiesFromMegatexture(in SampleData sampleData) {
+ vec3 tileUv = clamp(sampleData.tileUv, vec3(0.0), vec3(1.0)); // TODO is the clamp necessary?
+ int tileIndex = sampleData.megatextureIndex;
+ vec3 voxelCoord = tileUv * vec3(u_dimensions);
+ ivec3 voxelDimensions = u_dimensions;
+
+ #if defined(PADDING)
+ voxelDimensions += u_paddingBefore + u_paddingAfter;
+ voxelCoord += vec3(u_paddingBefore);
+ #endif
+
#if defined(NEAREST_SAMPLING)
// Round to the center of the nearest voxel
voxelCoord = floor(voxelCoord) + vec3(0.5);
#endif
- // Tile location
- vec2 tileUvOffset = index1DTo2DTexcoord(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
+ #if defined(MEGATEXTURE_2D)
+ // Tile location
+ vec2 tileUvOffset = index1DTo2DTexcoord(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
- // Slice location
- float slice = voxelCoord.z - 0.5;
- int sliceIndex = int(floor(slice));
- int sliceIndex0 = intClamp(sliceIndex, 0, voxelDims.z - 1);
- vec2 sliceUvOffset0 = index1DTo2DTexcoord(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
+ // Slice location
+ float slice = voxelCoord.z - 0.5;
+ int sliceIndex = int(floor(slice));
+ int sliceIndex0 = intClamp(sliceIndex, 0, voxelDimensions.z - 1);
+ vec2 sliceUvOffset0 = index1DTo2DTexcoord(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
- // Voxel location
- vec2 voxelUvOffset = clamp(voxelCoord.xy, vec2(0.5), vec2(voxelDims.xy) - vec2(0.5)) * u_megatextureVoxelSizeUv;
+ // Voxel location
+ vec2 voxelUvOffset = clamp(voxelCoord.xy, vec2(0.5), vec2(voxelDimensions.xy) - vec2(0.5)) * u_megatextureVoxelSizeUv;
- // Final location in the megatexture
- vec2 uv0 = tileUvOffset + sliceUvOffset0 + voxelUvOffset;
+ // Final location in the megatexture
+ vec2 uv0 = tileUvOffset + sliceUvOffset0 + voxelUvOffset;
- #if defined(NEAREST_SAMPLING)
- return getPropertiesFromMegatextureAtUv(uv0);
- #else
- float sliceLerp = fract(slice);
- int sliceIndex1 = intMin(sliceIndex + 1, voxelDims.z - 1);
- vec2 sliceUvOffset1 = index1DTo2DTexcoord(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
- vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
- Properties properties0 = getPropertiesFromMegatextureAtUv(uv0);
- Properties properties1 = getPropertiesFromMegatextureAtUv(uv1);
- return mixProperties(properties0, properties1, sliceLerp);
- #endif
-}
-#endif
+ #if defined(NEAREST_SAMPLING)
+ return getPropertiesFromMegatextureAtUv(uv0);
+ #else
+ float sliceLerp = fract(slice);
+ int sliceIndex1 = intMin(sliceIndex + 1, voxelDimensions.z - 1);
+ vec2 sliceUvOffset1 = index1DTo2DTexcoord(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
+ vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
+ Properties properties0 = getPropertiesFromMegatextureAtUv(uv0);
+ Properties properties1 = getPropertiesFromMegatextureAtUv(uv1);
+ return mixProperties(properties0, properties1, sliceLerp);
+ #endif
+ #elif defined(MEGATEXTURE_3D)
+ // TODO: 3D megatexture has not been implemented yet
+ // Tile location
+ vec3 tileUvOffset = indexToUv3d(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
-Properties getPropertiesFromMegatexture(in SampleData sampleData) {
- vec3 tileUv = clamp(sampleData.tileUv, vec3(0.0), vec3(1.0)); // TODO is the clamp necessary?
- int tileIndex = sampleData.megatextureIndex;
- vec3 voxelCoord = tileUv * vec3(u_dimensions);
- ivec3 dimensions = u_dimensions;
+ // Voxel location
+ vec3 voxelUvOffset = clamp(voxelCoord, vec3(0.5), vec3(voxelDimensions) - vec2(0.5)) * u_megatextureVoxelSizeUv;
- #if defined(PADDING)
- dimensions += u_paddingBefore + u_paddingAfter;
- voxelCoord += vec3(u_paddingBefore);
- #endif
+ // Final location in the megatexture
+ vec3 uv = tileUvOffset + voxelUvOffset;
- return getPropertiesFromMegatextureAtVoxelCoord(voxelCoord, dimensions, tileIndex);
+ for (int i = 0; i < PROPERTY_COUNT; ++i) {
+ vec4 sample = texture3D(u_megatextureTextures[i], uv);
+ samples[i] = decodeTextureSample(sample);
+ }
+ #endif
}
// Convert an array of sample datas to a final weighted properties.
From 5521fd298a2c636b197f8e0b4502c55700b7b750 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 3 May 2022 13:00:59 -0700
Subject: [PATCH 053/679] anticipating a 3d texture fix
---
Source/Shaders/VoxelFS.glsl | 5 +----
1 file changed, 1 insertion(+), 4 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index d340886b53d..4a2229723ca 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1419,10 +1419,7 @@ Properties getPropertiesFromMegatexture(in SampleData sampleData) {
// Final location in the megatexture
vec3 uv = tileUvOffset + voxelUvOffset;
- for (int i = 0; i < PROPERTY_COUNT; ++i) {
- vec4 sample = texture3D(u_megatextureTextures[i], uv);
- samples[i] = decodeTextureSample(sample);
- }
+ return getPropertiesFromMegatextureAtUv(uv);
#endif
}
From 3f8085fd458acecde8f3240cb9b408f18fa8b620 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Wed, 4 May 2022 09:08:45 -0700
Subject: [PATCH 054/679] fixed root tile uvs
---
Source/Shaders/VoxelFS.glsl | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 4a2229723ca..59192a1b492 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1555,6 +1555,9 @@ void traverseOctree(in vec3 positionUv, out TraversalData traversalData, out Sam
// No child data, only the root tile has data
getOctreeLeafData(rootData, sampleDatas);
traversalData.stepT = u_stepSize;
+ for (int i = 0; i < SAMPLE_COUNT; i++) {
+ sampleDatas[i].tileUv = traversalData.positionUvLocal;
+ }
}
else
{
@@ -1570,8 +1573,11 @@ void traverseOctreeFromExisting(in vec3 positionUv, inout TraversalData traversa
// Note: This code assumes the position is always inside the root tile.
bool insideTile = traversalData.octreeCoords.w == 0 || inRange(traversalData.positionUvLocal, vec3(0.0), vec3(1.0));
- if (!insideTile)
- {
+ if (insideTile) {
+ for (int i = 0; i < SAMPLE_COUNT; i++) {
+ sampleDatas[i].tileUv = traversalData.positionUvLocal;
+ }
+ } else {
// Go up tree
for (int i = 0; i < OCTREE_MAX_LEVELS; ++i)
{
@@ -1719,8 +1725,7 @@ void main()
}
// Traverse the tree from the current ray position.
- // This is similar to traverseOctree but is optimized for the common
- // case where the ray is in the same tile as the previous step.
+ // This is similar to traverseOctree but is faster when the ray is in the same tile as the previous step.
traverseOctreeFromExisting(positionUv, traversalData, sampleDatas);
}
From a0883a7e292e41e7e02f328fa985b99f95dc3d81 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Wed, 4 May 2022 09:29:01 -0700
Subject: [PATCH 055/679] small shader name changes
---
Source/Shaders/VoxelFS.glsl | 70 ++++++++++++++++++-------------------
1 file changed, 35 insertions(+), 35 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 59192a1b492..1fe8306e816 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -176,7 +176,7 @@ struct TraversalData {
struct SampleData {
int megatextureIndex;
- int levelsAbove; // TODO find a way to remove this
+ bool usingParentMegatextureIndex;
vec3 tileUv;
#if (SAMPLE_COUNT > 1)
float weight;
@@ -1442,33 +1442,6 @@ Properties getPropertiesFromMegatexture(SampleData sampleDatas[SAMPLE_COUNT]) {
// Tree traversal
// --------------------------------------------------------
-void getOctreeLeafData(OctreeNodeData data, inout SampleData sampleDatas[SAMPLE_COUNT]) {
- #if (SAMPLE_COUNT == 1)
- sampleDatas[0].megatextureIndex = data.data;
- sampleDatas[0].levelsAbove = data.flag == OCTREE_FLAG_PACKED_LEAF_FROM_PARENT ? 1 : 0;
- #else
- int leafIndex = data.data;
- int leafNodeTexelCount = 2;
- // Adding 0.5 moves to the center of the texel
- float leafCoordXStart = float(intMod(leafIndex, u_octreeLeafNodeTilesPerRow) * leafNodeTexelCount) + 0.5;
- float leafCoordY = float(leafIndex / u_octreeLeafNodeTilesPerRow) + 0.5;
-
- vec2 leafUv0 = u_octreeLeafNodeTexelSizeUv * vec2(leafCoordXStart + 0.0, leafCoordY);
- vec2 leafUv1 = u_octreeLeafNodeTexelSizeUv * vec2(leafCoordXStart + 1.0, leafCoordY);
- vec4 leafData0 = texture2D(u_octreeLeafNodeTexture, leafUv0);
- vec4 leafData1 = texture2D(u_octreeLeafNodeTexture, leafUv1);
-
- float lerp = normU8x2_toFloat(leafData0.xy);
-
- sampleDatas[0].megatextureIndex = normU8x2_toInt(leafData1.xy);
- sampleDatas[1].megatextureIndex = normU8x2_toInt(leafData1.zw);
- sampleDatas[0].levelsAbove = normU8_toInt(leafData0.z);
- sampleDatas[1].levelsAbove = normU8_toInt(leafData0.w);
- sampleDatas[0].weight = 1.0 - lerp;
- sampleDatas[1].weight = lerp;
- #endif
-}
-
OctreeNodeData getOctreeRootData() {
vec4 rootData = texture2D(u_octreeInternalNodeTexture, vec2(0.0));
@@ -1500,6 +1473,33 @@ int getOctreeParentIndex(int octreeIndex) {
return parentOctreeIndex;
}
+void getOctreeLeafSampleData(in OctreeNodeData data, inout SampleData sampleDatas[SAMPLE_COUNT]) {
+ #if (SAMPLE_COUNT == 1)
+ sampleDatas[0].megatextureIndex = data.data;
+ sampleDatas[0].usingParentMegatextureIndex = data.flag == OCTREE_FLAG_PACKED_LEAF_FROM_PARENT;
+ #else
+ int leafIndex = data.data;
+ int leafNodeTexelCount = 2;
+ // Adding 0.5 moves to the center of the texel
+ float leafCoordXStart = float(intMod(leafIndex, u_octreeLeafNodeTilesPerRow) * leafNodeTexelCount) + 0.5;
+ float leafCoordY = float(leafIndex / u_octreeLeafNodeTilesPerRow) + 0.5;
+
+ vec2 leafUv0 = u_octreeLeafNodeTexelSizeUv * vec2(leafCoordXStart + 0.0, leafCoordY);
+ vec2 leafUv1 = u_octreeLeafNodeTexelSizeUv * vec2(leafCoordXStart + 1.0, leafCoordY);
+ vec4 leafData0 = texture2D(u_octreeLeafNodeTexture, leafUv0);
+ vec4 leafData1 = texture2D(u_octreeLeafNodeTexture, leafUv1);
+
+ float lerp = normU8x2_toFloat(leafData0.xy);
+
+ sampleDatas[0].megatextureIndex = normU8x2_toInt(leafData1.xy);
+ sampleDatas[1].megatextureIndex = normU8x2_toInt(leafData1.zw);
+ sampleDatas[0].usingParentMegatextureIndex = normU8_toInt(leafData0.z) == 1;
+ sampleDatas[1].usingParentMegatextureIndex = normU8_toInt(leafData0.w) == 1;
+ sampleDatas[0].weight = 1.0 - lerp;
+ sampleDatas[1].weight = lerp;
+ #endif
+}
+
void traverseOctreeDownwards(inout TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
float sizeAtLevel = 1.0 / pow(2.0, float(traversalData.octreeCoords.w));
vec3 start = vec3(traversalData.octreeCoords.xyz) * sizeAtLevel;
@@ -1518,19 +1518,19 @@ void traverseOctreeDownwards(inout TraversalData traversalData, out SampleData s
traversalData.octreeCoords = ivec4(parentOctreeCoords.xyz * 2 + ivec3(childCoord), parentOctreeCoords.w + 1);
if (childData.flag == OCTREE_FLAG_INTERNAL) {
- // keep going deeper
+ // interior tile - keep going deeper
start = mix(start, center, childCoord);
end = mix(center, end, childCoord);
traversalData.parentOctreeIndex = childData.data;
} else {
+ // leaf tile - stop traversing
float dimAtLevel = pow(2.0, float(traversalData.octreeCoords.w));
traversalData.positionUvLocal = traversalData.positionUvShapeSpace * dimAtLevel - vec3(traversalData.octreeCoords.xyz);
traversalData.stepT = u_stepSize / dimAtLevel;
- getOctreeLeafData(childData, sampleDatas);
+ getOctreeLeafSampleData(childData, sampleDatas);
for (int i = 0; i < SAMPLE_COUNT; i++) {
- bool usingParent = sampleDatas[i].levelsAbove == 1;
- if (usingParent) {
+ if (sampleDatas[i].usingParentMegatextureIndex) {
float parentDimAtLevel = pow(2.0, float(parentOctreeCoords.w));
sampleDatas[i].tileUv = traversalData.positionUvShapeSpace * parentDimAtLevel - vec3(parentOctreeCoords.xyz);
} else {
@@ -1542,7 +1542,7 @@ void traverseOctreeDownwards(inout TraversalData traversalData, out SampleData s
}
}
-void traverseOctree(in vec3 positionUv, out TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
+void traverseOctreeFromBeginning(in vec3 positionUv, out TraversalData traversalData, out SampleData sampleDatas[SAMPLE_COUNT]) {
traversalData.octreeCoords = ivec4(0);
traversalData.parentOctreeIndex = 0;
@@ -1553,7 +1553,7 @@ void traverseOctree(in vec3 positionUv, out TraversalData traversalData, out Sam
OctreeNodeData rootData = getOctreeRootData();
if (rootData.flag == OCTREE_FLAG_LEAF) {
// No child data, only the root tile has data
- getOctreeLeafData(rootData, sampleDatas);
+ getOctreeLeafSampleData(rootData, sampleDatas);
traversalData.stepT = u_stepSize;
for (int i = 0; i < SAMPLE_COUNT; i++) {
sampleDatas[i].tileUv = traversalData.positionUvLocal;
@@ -1632,7 +1632,7 @@ void main()
// Traverse the tree from the start position
TraversalData traversalData;
SampleData sampleDatas[SAMPLE_COUNT];
- traverseOctree(positionUv, traversalData, sampleDatas);
+ traverseOctreeFromBeginning(positionUv, traversalData, sampleDatas);
#if defined(JITTER)
float noise = hash(screenCoord); // [0,1]
From b8d627e1a04c47ed09c22b466a4cf836b5a81702 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Wed, 4 May 2022 09:39:39 -0700
Subject: [PATCH 056/679] removed despeckle option - should be replaced with
temporal aa or similar
---
Source/Scene/VoxelPrimitive.js | 32 -----------------
Source/Shaders/VoxelFS.glsl | 34 ++-----------------
.../Widgets/VoxelInspector/VoxelInspector.js | 1 -
.../VoxelInspector/VoxelInspectorViewModel.js | 6 ----
4 files changed, 2 insertions(+), 71 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 58bebcfba52..19c22164756 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -318,12 +318,6 @@ function VoxelPrimitive(options) {
*/
this._stepSizeMultiplier = 1.0;
- /**
- * @type {Boolean}
- * @private
- */
- this._despeckle = false;
-
/**
* @type {Boolean}
* @private
@@ -830,28 +824,6 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
},
- /**
- * Gets or sets whether to reduce thin and noisy details.
- *
- * @memberof VoxelPrimitive.prototype
- * @type {Boolean}
- */
- despeckle: {
- get: function () {
- return this._despeckle;
- },
- set: function (despeckle) {
- //>>includeStart('debug', pragmas.debug);
- Check.typeOf.bool("despeckle", despeckle);
- //>>includeEnd('debug');
-
- if (this._despeckle !== despeckle) {
- this._despeckle = despeckle;
- this._shaderDirty = true;
- }
- },
- },
-
/**
* Gets or sets the minimum bounds. TODO: fill in the rest later
*
@@ -1726,7 +1698,6 @@ function buildDrawCommands(that, context) {
const minimumValues = provider.minimumValues;
const maximumValues = provider.maximumValues;
const keyframeCount = that._keyframeCount;
- const despeckle = that._despeckle;
const jitter = that._jitter;
const nearestSampling = that._nearestSampling;
const customShader = that._customShader;
@@ -1799,9 +1770,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
}
- if (despeckle) {
- shaderBuilder.addDefine("DESPECKLE", undefined, ShaderDestination.FRAGMENT);
- }
if (hasStatistics) {
shaderBuilder.addDefine(
"STATISTICS",
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 1fe8306e816..af6dbe70ff9 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -16,7 +16,6 @@ Below is an example of how this code might look. Properties like "temperature" a
#define INTERSECTION_COUNT ###
#define JITTER
#define NEAREST_SAMPLING
-#define DESPECKLE
#define STATISTICS
#define PADDING
#define PICKING
@@ -1623,12 +1622,6 @@ void main()
vec4 colorAccum = vec4(0.0);
- #if defined(DESPECKLE)
- vec4 colorAccumTemp = vec4(0.0);
- int nonZeroCount = 0;
- int nonZeroMax = 3;
- #endif
-
// Traverse the tree from the start position
TraversalData traversalData;
SampleData sampleDatas[SAMPLE_COUNT];
@@ -1671,31 +1664,8 @@ void main()
color.rgb = max(color.rgb, vec3(0.0));
color.a = clamp(color.a, 0.0, 1.0);
- #if defined(DESPECKLE)
- if (color.a < (1.0 - ALPHA_ACCUM_MAX)) {
- float partialAlpha = float(nonZeroCount) / float(nonZeroMax);
- colorAccum.a += partialAlpha * (colorAccumTemp.a - colorAccum.a);
- colorAccum.rgb += partialAlpha * colorAccumTemp.rgb;
- colorAccumTemp = vec4(0.0);
- nonZeroCount = 0;
- } else {
- nonZeroCount++;
- if (nonZeroCount == 1) {
- colorAccumTemp.a = colorAccum.a;
- }
- colorAccumTemp += (1.0 - colorAccumTemp.a) * vec4(color.rgb * color.a, color.a);
-
- if (nonZeroCount >= nonZeroMax) {
- colorAccum.a = colorAccumTemp.a;
- colorAccum.rgb += colorAccumTemp.rgb;
- colorAccumTemp = vec4(0.0);
- nonZeroCount = 0;
- }
- }
- #else
- // Pre-multiplied alpha blend
- colorAccum += (1.0 - colorAccum.a) * vec4(color.rgb * color.a, color.a);
- #endif
+ // Pre-multiplied alpha blend
+ colorAccum += (1.0 - colorAccum.a) * vec4(color.rgb * color.a, color.a);
// Stop traversing if the alpha has been fully saturated
if (colorAccum.a > ALPHA_ACCUM_MAX) {
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
index 49fedbd70a5..ac2d44ad46d 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspector.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -67,7 +67,6 @@ function VoxelInspector(container, scene) {
displayPanelContents.appendChild(
createCheckbox("Nearest Sampling", "nearestSampling")
);
- displayPanelContents.appendChild(createCheckbox("Despeckle", "despeckle"));
const screenSpaceErrorContainer = document.createElement("div");
screenSpaceErrorContainer.appendChild(
diff --git a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
index 15cf147fcbf..8c51f134f5b 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
@@ -201,12 +201,6 @@ function VoxelInspectorViewModel(scene) {
setPrimitiveFunction: true,
getPrimitiveFunction: true,
});
- addProperty({
- name: "despeckle",
- initialValue: false,
- setPrimitiveFunction: true,
- getPrimitiveFunction: true,
- });
addProperty({
name: "screenSpaceError",
initialValue: 4.0,
From 2b9041aee477f44a26638286f440f4fd2f7bae90 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 6 May 2022 10:53:31 -0700
Subject: [PATCH 057/679] starting smooth blending between levels of the tree
---
Source/Scene/VoxelPrimitive.js | 96 +++++++++++++++++------
Source/Scene/VoxelTraversal.js | 135 ++++++++++++++++++++++-----------
Source/Shaders/VoxelFS.glsl | 17 +++--
3 files changed, 173 insertions(+), 75 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 19c22164756..4bf1c1c5aa3 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -261,12 +261,6 @@ function VoxelPrimitive(options) {
// */
// this._clock = options.clock;
- // /**
- // * @type {Number}
- // * @private
- // */
- // this._keyframeCount = 1;
-
// Transforms and other values that are computed when the shape changes
/**
@@ -312,6 +306,12 @@ function VoxelPrimitive(options) {
*/
this._nearestSampling = false;
+ /**
+ * @type {Boolean}
+ * @private
+ */
+ this._smoothLevelBlend = 0.0;
+
/**
* @type {Number}
* @private
@@ -781,6 +781,28 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
},
+ /**
+ * Gets or sets smooth blending between levels.
+ *
+ * @memberof VoxelPrimitive.prototype
+ * @type {Boolean}
+ */
+ smoothLevelBlend: {
+ get: function () {
+ return this._smoothLevelBlend;
+ },
+ set: function (smoothLevelBlend) {
+ //>>includeStart('debug', pragmas.debug);
+ Check.typeOf.bool("smoothLevelBlend", smoothLevelBlend);
+ //>>includeEnd('debug');
+
+ if (this._smoothLevelBlend !== smoothLevelBlend) {
+ this._smoothLevelBlend = smoothLevelBlend;
+ this._shaderDirty = true;
+ }
+ },
+ },
+
/**
* Gets or sets the screen space error in pixels. If the screen space size
* of a voxel is greater than the screen space error, the tile is subdivided.
@@ -1087,12 +1109,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
});
uniforms.pickColor = Color.clone(this._pickId.color, uniforms.pickColor);
- // const keyframeCount = defaultValue(provider.keyframeCount, 1);
- // // TODO remove?
- // that._keyframeCount = defaultValue(
- // provider.keyframeCount,
- // that._keyframeCount
- // );
// // TODO remove?
// that._timeIntervalCollection = defaultValue(
// provider.timeIntervalCollection,
@@ -1349,7 +1365,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
const types = provider.types;
const componentTypes = provider.componentTypes;
- const keyframeCount = 1; //this._keyframeCount;
// Traversal setup
// It's ok for memory byte length to be undefined.
@@ -1364,6 +1379,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
)
: undefined;
+ const keyframeCount = defaultValue(provider.keyframeCount, 1);
+
this._traversal = new VoxelTraversal(
this,
context,
@@ -1377,7 +1394,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
// Set uniforms that come from the traversal.
// TODO: should this be done in VoxelTraversal?
const traversal = this._traversal;
- const useLeafNodeTexture = traversal.useLeafNodeTexture;
uniforms.octreeInternalNodeTexture = traversal.internalNodeTexture;
uniforms.octreeInternalNodeTexelSizeUv = Cartesian2.clone(
@@ -1386,15 +1402,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
);
uniforms.octreeInternalNodeTilesPerRow = traversal.internalNodeTilesPerRow;
- if (useLeafNodeTexture) {
- uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
- uniforms.octreeLeafNodeTexelSizeUv = Cartesian2.clone(
- traversal.leafNodeTexelSizeUv,
- uniforms.octreeLeafNodeTexelSizeUv
- );
- uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
- }
-
const megatextures = traversal.megatextures;
const megatexture = megatextures[0];
const megatextureLength = megatextures.length;
@@ -1491,6 +1498,16 @@ VoxelPrimitive.prototype.update = function (frameState) {
this._shaderDirty = true;
}
+ const leafNodeTexture = traversal.leafNodeTexture;
+ if (defined(leafNodeTexture)) {
+ uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
+ uniforms.octreeLeafNodeTexelSizeUv = Cartesian2.clone(
+ traversal.leafNodeTexelSizeUv,
+ uniforms.octreeLeafNodeTexelSizeUv
+ );
+ uniforms.octreeLeafNodeTilesPerRow = traversal.leafNodeTilesPerRow;
+ }
+
// Rebuild shaders
if (this._shaderDirty) {
buildDrawCommands(this, context);
@@ -1697,9 +1714,9 @@ function buildDrawCommands(that, context) {
const shapeDefines = shape.shaderDefines;
const minimumValues = provider.minimumValues;
const maximumValues = provider.maximumValues;
- const keyframeCount = that._keyframeCount;
const jitter = that._jitter;
const nearestSampling = that._nearestSampling;
+ const smoothLevelBlend = that._smoothLevelBlend;
const customShader = that._customShader;
const attributeLength = types.length;
const hasStatistics = defined(minimumValues) && defined(maximumValues);
@@ -1770,6 +1787,15 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
}
+
+ if (smoothLevelBlend) {
+ shaderBuilder.addDefine(
+ "SMOOTH_LEVEL_BLEND",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
if (hasStatistics) {
shaderBuilder.addDefine(
"STATISTICS",
@@ -1794,7 +1820,7 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
- const sampleCount = keyframeCount > 1 ? 2 : 1;
+ const sampleCount = smoothLevelBlend ? 2 : 1;
shaderBuilder.addDefine(
"SAMPLE_COUNT",
`${sampleCount}`,
@@ -2043,6 +2069,26 @@ function buildDrawCommands(that, context) {
]);
}
+ // scaleProperties function
+ {
+ const functionId = "scaleProperties";
+ shaderBuilder.addFunction(
+ functionId,
+ `${propertiesStructName} scaleProperties(${propertiesStructName} ${propertiesFieldName}, float scale)`,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addFunctionLines(functionId, [
+ `${propertiesStructName} scaledProperties = ${propertiesFieldName};`,
+ ]);
+ for (let i = 0; i < attributeLength; i++) {
+ const name = names[i];
+ shaderBuilder.addFunctionLines(functionId, [
+ `scaledProperties.${name} *= scale;`,
+ ]);
+ }
+ shaderBuilder.addFunctionLines(functionId, [`return scaledProperties;`]);
+ }
+
// mixProperties
{
const functionId = "mixProperties";
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index 945d8995043..d05b8e3ddcf 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -221,31 +221,52 @@ function VoxelTraversal(
);
/**
- * @type {Boolean}
- * @readonly
- */
- this.useLeafNodeTexture = keyframeCount > 1;
-
- /**
- * @type {Texture|undefined}
+ * Only generated when there are two or more samples.
+ * @type {Texture}
* @readonly
*/
this.leafNodeTexture = undefined;
/**
- * @type {Number|undefined}
+ * Only generated when there are two or more samples.
+ * @type {Number}
* @readonly
*/
this.leafNodeTilesPerRow = undefined;
/**
- * @type {Cartesian2|undefined}
+ * Only generated when there are two or more samples.
+ * @type {Cartesian2}
* @readonly
*/
- this.leafNodeTexelSizeUv = undefined;
+ this.leafNodeTexelSizeUv = new Cartesian2();
+}
+
+VoxelTraversal.simultaneousRequestCountMaximum = 50;
+
+/**
+ * @param {FrameState} frameState
+ * @param {Number} keyframeLocation
+ * @param {Boolean} recomputeBoundingVolumes
+ * @param {Boolean} pauseUpdate
+ */
+VoxelTraversal.prototype.update = function (
+ frameState,
+ keyframeLocation,
+ recomputeBoundingVolumes,
+ pauseUpdate
+) {
+ const primitive = this._primitive;
+ const context = frameState.context;
+ const maximumTileCount = this.megatextures[0].maximumTileCount;
+ const keyframeCount = this._keyframeCount;
- const useLeafNodeTexture = this.useLeafNodeTexture;
- if (useLeafNodeTexture) {
+ const hasSmoothLevelBlend = primitive._smoothLevelBlend;
+ const hasKeyframes = keyframeCount > 1;
+ const sampleCount =
+ (hasSmoothLevelBlend ? 2 : 1) * (hasKeyframes > 1 ? 2 : 1);
+ const useLeafNodeTexture = sampleCount >= 2;
+ if (useLeafNodeTexture && !defined(this.leafNodeTexture)) {
const leafNodeTexelCount = 2;
const leafNodeTextureDimensionX = 1024;
const leafNodeTilesPerRow = Math.floor(
@@ -267,29 +288,16 @@ function VoxelTraversal(
magnificationFilter: TextureMagnificationFilter.NEAREST,
}),
});
- this.leafNodeTexelSizeUv = new Cartesian2(
+ this.leafNodeTexelSizeUv = Cartesian2.fromElements(
1.0 / leafNodeTextureDimensionX,
- 1.0 / leafNodeTextureDimensionY
+ 1.0 / leafNodeTextureDimensionY,
+ this.leafNodeTexelSizeUv
);
this.leafNodeTilesPerRow = leafNodeTilesPerRow;
+ } else if (!useLeafNodeTexture && defined(this.leafNodeTexture)) {
+ this.leafNodeTexture = this.leafNodeTexture.destroy();
}
-}
-
-VoxelTraversal.simultaneousRequestCountMaximum = 50;
-/**
- * @param {FrameState} frameState
- * @param {Number} keyframeLocation
- * @param {Boolean} recomputeBoundingVolumes
- * @param {Boolean} pauseUpdate
- */
-VoxelTraversal.prototype.update = function (
- frameState,
- keyframeLocation,
- recomputeBoundingVolumes,
- pauseUpdate
-) {
- const keyframeCount = this._keyframeCount;
this._keyframeLocation = CesiumMath.clamp(
keyframeLocation,
0.0,
@@ -305,7 +313,7 @@ VoxelTraversal.prototype.update = function (
const timestamp0 = getTimestamp();
loadAndUnload(this, frameState);
const timestamp1 = getTimestamp();
- generateOctree(this);
+ generateOctree(this, sampleCount);
const timestamp2 = getTimestamp();
const debugStatistics = this._debugPrint;
@@ -1412,11 +1420,13 @@ const GpuOctreeFlag = {
*
* @param {VoxelTraversal} that
* @param {FrameState} frameState
+ * @param {Number} sampleCount
*/
-function generateOctree(that) {
+function generateOctree(that, sampleCount) {
+ const primitive = that._primitive;
const keyframeLocation = that._keyframeLocation;
- const useLeafNodes = that.useLeafNodeTexture;
const frameNumber = that._frameNumber;
+ const useLeafNodes = sampleCount > 1;
let internalNodeCount = 0;
let leafNodeCount = 0;
@@ -1475,18 +1485,53 @@ function generateOctree(that) {
// Store the leaf node information instead
// Recursion stops here because there are no renderable children
if (useLeafNodes) {
- const previousKeyframeNode = node.renderableKeyframeNodePrevious;
- const nextKeyframeNode = node.renderableKeyframeNodeNext;
- leafNodeOctreeData[leafNodeCount * 5 + 0] =
- node.renderableKeyframeNodeLerp;
- leafNodeOctreeData[leafNodeCount * 5 + 1] =
- node.level - previousKeyframeNode.spatialNode.level;
- leafNodeOctreeData[leafNodeCount * 5 + 2] =
- node.level - nextKeyframeNode.spatialNode.level;
- leafNodeOctreeData[leafNodeCount * 5 + 3] =
- previousKeyframeNode.megatextureIndex;
- leafNodeOctreeData[leafNodeCount * 5 + 4] =
- nextKeyframeNode.megatextureIndex;
+ const baseIdx = leafNodeCount * 5;
+
+ const useTimeDynamic = false;
+ if (useTimeDynamic) {
+ const previousKeyframeNode = node.renderableKeyframeNodePrevious;
+ const nextKeyframeNode = node.renderableKeyframeNodeNext;
+ const prevKeyframeLevel = previousKeyframeNode.spatialNode.level;
+ const nextKeyframeLevel = nextKeyframeNode.spatialNode.level;
+ const prevKeyframeLevelDifference = node.level - prevKeyframeLevel;
+ const nextKeyframeLevelDifference = node.level - nextKeyframeLevel;
+
+ leafNodeOctreeData[baseIdx + 0] = node.renderableKeyframeNodeLerp;
+ leafNodeOctreeData[baseIdx + 1] = prevKeyframeLevelDifference;
+ leafNodeOctreeData[baseIdx + 2] = nextKeyframeLevelDifference;
+ leafNodeOctreeData[baseIdx + 3] =
+ previousKeyframeNode.megatextureIndex;
+ leafNodeOctreeData[baseIdx + 4] = nextKeyframeNode.megatextureIndex;
+ } else {
+ const keyframeNode = node.renderableKeyframeNodePrevious;
+ const levelDifference = node.level - keyframeNode.spatialNode.level;
+
+ const parentNode = keyframeNode.spatialNode.parent;
+ const parentKeyframeNode = defined(parentNode)
+ ? parentNode.renderableKeyframeNodePrevious
+ : keyframeNode;
+
+ let lodLerp = 0.0;
+ if (node.parent !== undefined) {
+ const sse = node.screenSpaceError;
+ const parentSse = node.parent.screenSpaceError;
+ const targetSse = primitive._screenSpaceError;
+ lodLerp = (targetSse - sse) / (parentSse - sse);
+ lodLerp = CesiumMath.clamp(lodLerp, 0.0, 1.0);
+ }
+
+ const levelDifferenceChild = levelDifference;
+ const levelDifferenceParent = levelDifference + 1;
+ const megatextureIndexChild = keyframeNode.megatextureIndex;
+ const megatextureIndexParent = parentKeyframeNode.megatextureIndex;
+
+ leafNodeOctreeData[baseIdx + 0] = lodLerp;
+ leafNodeOctreeData[baseIdx + 1] = levelDifferenceChild;
+ leafNodeOctreeData[baseIdx + 2] = levelDifferenceParent;
+ leafNodeOctreeData[baseIdx + 3] = megatextureIndexChild;
+ leafNodeOctreeData[baseIdx + 4] = megatextureIndexParent;
+ }
+
internalNodeOctreeData[parentEntryIndex] =
(GpuOctreeFlag.LEAF << 16) | leafNodeCount;
} else {
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index af6dbe70ff9..9d1a7a4faa9 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -88,6 +88,12 @@ Properties sumProperties(Properties propertiesA, Properties propertiesB) {
properties.direction = propertiesA.direction + propertiesB.direction;
return properties;
}
+Properties scaleProperties(Properties properties, float scale) {
+ Properties scaledProperties = properties;
+ scaledProperties.temperature *= scale;
+ scaledProperties.direction *= scale;
+ return scaledProperties;
+}
Properties mixProperties(Properties propertiesA, Properties propertiesB, float mixFactor) {
Properties properties;
properties.temperature = mix(propertiesA.temperature, propertiesB.temperature, mixFactor);
@@ -1430,8 +1436,9 @@ Properties getPropertiesFromMegatexture(SampleData sampleDatas[SAMPLE_COUNT]) {
// When more than one sample is taken the accumulator needs to start at 0
Properties properties = clearProperties();
for (int i = 0; i < SAMPLE_COUNT; ++i) {
- Properties tempProperties = getPropertiesFromMegatexture(sampleDatas[i]);
- properties = sumProperties(properties, tempProperties)
+ Properties tempProperties = getPropertiesFromMegatexture(sampleDatas[i]);
+ tempProperties = scaleProperties(tempProperties, sampleDatas[i].weight);
+ properties = sumProperties(properties, tempProperties);
}
return properties;
#endif
@@ -1620,8 +1627,6 @@ void main()
float endT = entryExitT.y;
vec3 positionUv = viewPosUv + currT * viewDirUv;
- vec4 colorAccum = vec4(0.0);
-
// Traverse the tree from the start position
TraversalData traversalData;
SampleData sampleDatas[SAMPLE_COUNT];
@@ -1638,6 +1643,8 @@ void main()
setStatistics(fragmentInput.metadata.statistics);
#endif
+ vec4 colorAccum = vec4(0.0);
+
for (int stepCount = 0; stepCount < STEP_COUNT_MAX; ++stepCount) {
// Read properties from the megatexture based on the traversal state
Properties properties = getPropertiesFromMegatexture(sampleDatas);
@@ -1652,7 +1659,7 @@ void main()
fragmentInput.voxel.travelDistance = traversalData.stepT;
#if defined(STYLE_USE_POSITION_EC)
- styleInput.positionEC = vec3(u_transformPositionUvToView * vec4(positionUv, 1.0));
+ fragmentInput.voxel.positionEC = vec3(u_transformPositionUvToView * vec4(positionUv, 1.0));
#endif
// Run the custom shader
From f03dd023056f4d65f698f6438a92496463fb232a Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 6 May 2022 12:49:55 -0700
Subject: [PATCH 058/679] more tunable level blending
---
Source/Scene/VoxelPrimitive.js | 56 +++++++------------
Source/Scene/VoxelTraversal.js | 27 ++++++---
Source/Shaders/VoxelFS.glsl | 12 ++--
.../Widgets/VoxelInspector/VoxelInspector.js | 3 +
.../VoxelInspector/VoxelInspectorViewModel.js | 6 ++
5 files changed, 57 insertions(+), 47 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 4bf1c1c5aa3..af08dc9eecf 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -307,10 +307,10 @@ function VoxelPrimitive(options) {
this._nearestSampling = false;
/**
- * @type {Boolean}
+ * @type {Number}
* @private
*/
- this._smoothLevelBlend = 0.0;
+ this._levelBlendFactor = 0.0;
/**
* @type {Number}
@@ -366,24 +366,12 @@ function VoxelPrimitive(options) {
* @private
*/
this._uniforms = {
- /**
- * @ignore
- * @type {Texture}
- */
octreeInternalNodeTexture: undefined,
octreeInternalNodeTilesPerRow: 0,
octreeInternalNodeTexelSizeUv: new Cartesian2(),
- /**
- * @ignore
- * @type {Texture}
- */
octreeLeafNodeTexture: undefined,
octreeLeafNodeTilesPerRow: 0,
octreeLeafNodeTexelSizeUv: new Cartesian2(),
- /**
- * @ignore
- * @type {Texture[]}
- */
megatextureTextures: [],
megatextureSliceDimensions: new Cartesian2(),
megatextureTileDimensions: new Cartesian2(),
@@ -399,7 +387,7 @@ function VoxelPrimitive(options) {
transformNormalLocalToWorld: new Matrix3(),
cameraPositionUv: new Cartesian3(),
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
- stepSize: 1.0,
+ stepSize: 0,
pickColor: new Color(),
};
@@ -782,24 +770,23 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
/**
- * Gets or sets smooth blending between levels.
+ * Controls how quickly to blend between different levels of the tree.
+ * 0.0 means an instantaneous pop.
+ * 1.0 means a full linear blend.
*
* @memberof VoxelPrimitive.prototype
- * @type {Boolean}
+ * @type {Number}
*/
- smoothLevelBlend: {
+ levelBlendFactor: {
get: function () {
- return this._smoothLevelBlend;
+ return this._levelBlendFactor;
},
- set: function (smoothLevelBlend) {
+ set: function (levelBlendFactor) {
//>>includeStart('debug', pragmas.debug);
- Check.typeOf.bool("smoothLevelBlend", smoothLevelBlend);
+ Check.typeOf.number("levelBlendFactor", levelBlendFactor);
//>>includeEnd('debug');
- if (this._smoothLevelBlend !== smoothLevelBlend) {
- this._smoothLevelBlend = smoothLevelBlend;
- this._shaderDirty = true;
- }
+ this._levelBlendFactor = CesiumMath.clamp(levelBlendFactor, 0.0, 1.0);
},
},
@@ -1476,6 +1463,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
keyframeLocation = timeIntervalIndex + t;
}
+ const sampleCountOld = traversal._sampleCount;
+
// Update the voxel traversal
traversal.update(
frameState,
@@ -1483,6 +1472,11 @@ VoxelPrimitive.prototype.update = function (frameState) {
shapeDirty, // recomputeBoundingVolumes
this._disableUpdate // pauseUpdate
);
+
+ if (sampleCountOld !== traversal._sampleCount) {
+ this._shaderDirty = true;
+ }
+
const hasLoadedData = traversal.isRenderable(traversal.rootNode);
if (hasLoadedData && this._debugDraw) {
@@ -1702,6 +1696,7 @@ function getGlslField(type, index) {
*/
function buildDrawCommands(that, context) {
const provider = that._provider;
+ const traversal = that._traversal;
const shapeType = provider.shape;
const names = provider.names;
const types = provider.types;
@@ -1716,7 +1711,7 @@ function buildDrawCommands(that, context) {
const maximumValues = provider.maximumValues;
const jitter = that._jitter;
const nearestSampling = that._nearestSampling;
- const smoothLevelBlend = that._smoothLevelBlend;
+ const sampleCount = traversal._sampleCount;
const customShader = that._customShader;
const attributeLength = types.length;
const hasStatistics = defined(minimumValues) && defined(maximumValues);
@@ -1788,14 +1783,6 @@ function buildDrawCommands(that, context) {
);
}
- if (smoothLevelBlend) {
- shaderBuilder.addDefine(
- "SMOOTH_LEVEL_BLEND",
- undefined,
- ShaderDestination.FRAGMENT
- );
- }
-
if (hasStatistics) {
shaderBuilder.addDefine(
"STATISTICS",
@@ -1820,7 +1807,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
- const sampleCount = smoothLevelBlend ? 2 : 1;
shaderBuilder.addDefine(
"SAMPLE_COUNT",
`${sampleCount}`,
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index d05b8e3ddcf..af50c7e57e1 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -147,6 +147,12 @@ function VoxelTraversal(
*/
this._keyframeCount = keyframeCount;
+ /**
+ * @type {Number}
+ * @private
+ */
+ this._sampleCount = undefined;
+
/**
* @type {Number}
* @private
@@ -261,12 +267,15 @@ VoxelTraversal.prototype.update = function (
const maximumTileCount = this.megatextures[0].maximumTileCount;
const keyframeCount = this._keyframeCount;
- const hasSmoothLevelBlend = primitive._smoothLevelBlend;
+ const levelBlendFactor = primitive._levelBlendFactor;
+ const haslevelBlendFactor = levelBlendFactor > 0.0;
const hasKeyframes = keyframeCount > 1;
const sampleCount =
- (hasSmoothLevelBlend ? 2 : 1) * (hasKeyframes > 1 ? 2 : 1);
- const useLeafNodeTexture = sampleCount >= 2;
- if (useLeafNodeTexture && !defined(this.leafNodeTexture)) {
+ (haslevelBlendFactor ? 2 : 1) * (hasKeyframes > 1 ? 2 : 1);
+ this._sampleCount = sampleCount;
+
+ const useLeafNodes = sampleCount >= 2;
+ if (useLeafNodes && !defined(this.leafNodeTexture)) {
const leafNodeTexelCount = 2;
const leafNodeTextureDimensionX = 1024;
const leafNodeTilesPerRow = Math.floor(
@@ -294,7 +303,7 @@ VoxelTraversal.prototype.update = function (
this.leafNodeTexelSizeUv
);
this.leafNodeTilesPerRow = leafNodeTilesPerRow;
- } else if (!useLeafNodeTexture && defined(this.leafNodeTexture)) {
+ } else if (!useLeafNodes && defined(this.leafNodeTexture)) {
this.leafNodeTexture = this.leafNodeTexture.destroy();
}
@@ -313,7 +322,7 @@ VoxelTraversal.prototype.update = function (
const timestamp0 = getTimestamp();
loadAndUnload(this, frameState);
const timestamp1 = getTimestamp();
- generateOctree(this, sampleCount);
+ generateOctree(this, sampleCount, levelBlendFactor);
const timestamp2 = getTimestamp();
const debugStatistics = this._debugPrint;
@@ -1421,12 +1430,13 @@ const GpuOctreeFlag = {
* @param {VoxelTraversal} that
* @param {FrameState} frameState
* @param {Number} sampleCount
+ * @param {Number} levelBlendFactor
*/
-function generateOctree(that, sampleCount) {
+function generateOctree(that, sampleCount, levelBlendFactor) {
const primitive = that._primitive;
const keyframeLocation = that._keyframeLocation;
const frameNumber = that._frameNumber;
- const useLeafNodes = sampleCount > 1;
+ const useLeafNodes = sampleCount >= 2;
let internalNodeCount = 0;
let leafNodeCount = 0;
@@ -1517,6 +1527,7 @@ function generateOctree(that, sampleCount) {
const parentSse = node.parent.screenSpaceError;
const targetSse = primitive._screenSpaceError;
lodLerp = (targetSse - sse) / (parentSse - sse);
+ lodLerp = (lodLerp + levelBlendFactor - 1.0) / levelBlendFactor;
lodLerp = CesiumMath.clamp(lodLerp, 0.0, 1.0);
}
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 9d1a7a4faa9..9b87e8c4d59 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -313,7 +313,6 @@ uniform float u_stepSize;
#define CYLINDER_INTERSECTION_INDEX_RADIUS_MAX
#define CYLINDER_INTERSECTION_INDEX_RADIUS_MIN
#define CYLINDER_INTERSECTION_INDEX_ANGLE
-
*/
// Cylinder uniforms
@@ -1436,9 +1435,14 @@ Properties getPropertiesFromMegatexture(SampleData sampleDatas[SAMPLE_COUNT]) {
// When more than one sample is taken the accumulator needs to start at 0
Properties properties = clearProperties();
for (int i = 0; i < SAMPLE_COUNT; ++i) {
- Properties tempProperties = getPropertiesFromMegatexture(sampleDatas[i]);
- tempProperties = scaleProperties(tempProperties, sampleDatas[i].weight);
- properties = sumProperties(properties, tempProperties);
+ float weight = sampleDatas[i].weight;
+
+ // Avoid reading the megatexture when the weight is 0 as it can be costly.
+ if (weight > 0.0) {
+ Properties tempProperties = getPropertiesFromMegatexture(sampleDatas[i]);
+ tempProperties = scaleProperties(tempProperties, weight);
+ properties = sumProperties(properties, tempProperties);
+ }
}
return properties;
#endif
diff --git a/Source/Widgets/VoxelInspector/VoxelInspector.js b/Source/Widgets/VoxelInspector/VoxelInspector.js
index ac2d44ad46d..c3d229d728a 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspector.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspector.js
@@ -67,6 +67,9 @@ function VoxelInspector(container, scene) {
displayPanelContents.appendChild(
createCheckbox("Nearest Sampling", "nearestSampling")
);
+ displayPanelContents.appendChild(
+ makeRangeInput("Level Blend Factor", "levelBlendFactor", 0.0, 1.0)
+ );
const screenSpaceErrorContainer = document.createElement("div");
screenSpaceErrorContainer.appendChild(
diff --git a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
index 8c51f134f5b..9d401ea8a67 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
@@ -201,6 +201,12 @@ function VoxelInspectorViewModel(scene) {
setPrimitiveFunction: true,
getPrimitiveFunction: true,
});
+ addProperty({
+ name: "levelBlendFactor",
+ initialValue: 1.0,
+ setPrimitiveFunction: true,
+ getPrimitiveFunction: true,
+ });
addProperty({
name: "screenSpaceError",
initialValue: 4.0,
From dacd0819dcb5c83cccf5cbe790af69b7fce52cb7 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 6 May 2022 13:02:47 -0700
Subject: [PATCH 059/679] more accurate level difference for the blend parent
---
Source/Scene/VoxelTraversal.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index af50c7e57e1..d6653a11f46 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -270,8 +270,7 @@ VoxelTraversal.prototype.update = function (
const levelBlendFactor = primitive._levelBlendFactor;
const haslevelBlendFactor = levelBlendFactor > 0.0;
const hasKeyframes = keyframeCount > 1;
- const sampleCount =
- (haslevelBlendFactor ? 2 : 1) * (hasKeyframes > 1 ? 2 : 1);
+ const sampleCount = (haslevelBlendFactor ? 2 : 1) * (hasKeyframes ? 2 : 1);
this._sampleCount = sampleCount;
const useLeafNodes = sampleCount >= 2;
@@ -1532,7 +1531,7 @@ function generateOctree(that, sampleCount, levelBlendFactor) {
}
const levelDifferenceChild = levelDifference;
- const levelDifferenceParent = levelDifference + 1;
+ const levelDifferenceParent = 1;
const megatextureIndexChild = keyframeNode.megatextureIndex;
const megatextureIndexParent = parentKeyframeNode.megatextureIndex;
From dd31f9ccdc2f925a449d3b73de7508420a819f7d Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 10 May 2022 08:40:43 -0700
Subject: [PATCH 060/679] added back time dynamic
---
Source/Scene/Cesium3DTilesVoxelProvider.js | 19 ++++++++++------
Source/Scene/GltfVoxelProvider.js | 12 +++++++++--
Source/Scene/VoxelPrimitive.js | 24 ++++++---------------
Source/Scene/VoxelProvider.js | 25 ++++++++++++++++++++++
Source/Scene/VoxelTraversal.js | 5 +++--
5 files changed, 57 insertions(+), 28 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
index 8d441a1e72c..7a85e8e7762 100644
--- a/Source/Scene/Cesium3DTilesVoxelProvider.js
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -383,17 +383,12 @@ const scratchImplicitTileCoordinates = new ImplicitTileCoordinates({
* @param {Number} [options.tileX=0] The tile's X coordinate.
* @param {Number} [options.tileY=0] The tile's Y coordinate.
* @param {Number} [options.tileZ=0] The tile's Z coordinate.
+ * @param {Number} [options.keyframe=0] The requested keyframe.
* @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
*
* @exception {DeveloperError} The provider must be ready.
*/
Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- const tileLevel = defaultValue(options.tileLevel, 0);
- const tileX = defaultValue(options.tileX, 0);
- const tileY = defaultValue(options.tileY, 0);
- const tileZ = defaultValue(options.tileZ, 0);
-
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError(
@@ -402,6 +397,18 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
}
//>>includeEnd('debug');
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ const tileLevel = defaultValue(options.tileLevel, 0);
+ const tileX = defaultValue(options.tileX, 0);
+ const tileY = defaultValue(options.tileY, 0);
+ const tileZ = defaultValue(options.tileZ, 0);
+ const keyframe = defaultValue(options.keyframe, 0);
+
+ // 3D Tiles currently doesn't support time-dynamic data.
+ if (keyframe !== 0) {
+ return undefined;
+ }
+
// 1. Load the subtree that the tile belongs to (possibly from the subtree cache)
// 1a. If not in the cache, load the subtree resource
// 1b. If not in the cache, load the subtree object
diff --git a/Source/Scene/GltfVoxelProvider.js b/Source/Scene/GltfVoxelProvider.js
index cb8299748cc..6b8a68f0112 100644
--- a/Source/Scene/GltfVoxelProvider.js
+++ b/Source/Scene/GltfVoxelProvider.js
@@ -317,25 +317,33 @@ GltfVoxelProvider.prototype.update = function (frameState) {
* @param {Number} [options.tileX=0] The tile's X coordinate.
* @param {Number} [options.tileY=0] The tile's Y coordinate.
* @param {Number} [options.tileZ=0] The tile's Z coordinate.
+ * @param {Number} [options.keyframe=0] The requested keyframe.
* @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
*
* @exception {DeveloperError} The provider must be ready.
* @exception {DeveloperError} Only level 0 can be requested.
*/
GltfVoxelProvider.prototype.requestData = function (options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
//>>includeStart('debug', pragmas.debug);
if (!this.ready) {
throw new DeveloperError(
"requestData must not be called before the provider is ready."
);
}
+ //>>includeEnd('debug');
+
+ options = defaultValue(options, defaultValue.EMPTY_OBJECT);
+ // glTF current doesn't support level of detail.
const tileLevel = defaultValue(options.tileLevel, 0);
if (tileLevel > 0) {
return undefined;
}
- //>>includeEnd('debug');
+ // glTF currently doesn't support time-dynamic.
+ const keyframe = defaultValue(options.keyframe, 0);
+ if (keyframe > 0) {
+ return undefined;
+ }
return Promise.resolve(this._data);
};
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index af08dc9eecf..da305df1172 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -249,17 +249,11 @@ function VoxelPrimitive(options) {
*/
this._pickId = undefined;
- // /**
- // * @type {TimeIntervalCollection}
- // * @private
- // */
- // this._timeIntervalCollection = undefined;
-
- // /**
- // * @type {Clock}
- // * @private
- // */
- // this._clock = options.clock;
+ /**
+ * @type {Clock}
+ * @private
+ */
+ this._clock = options.clock;
// Transforms and other values that are computed when the shape changes
@@ -1096,12 +1090,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
});
uniforms.pickColor = Color.clone(this._pickId.color, uniforms.pickColor);
- // // TODO remove?
- // that._timeIntervalCollection = defaultValue(
- // provider.timeIntervalCollection,
- // that._timeIntervalCollection
- // );
-
const dimensions = provider.dimensions;
const shapeType = provider.shape;
@@ -1425,7 +1413,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
if (this._ready && this._shapeVisible) {
const traversal = this._traversal;
const clock = this._clock;
- const timeIntervalCollection = this._timeIntervalCollection;
+ const timeIntervalCollection = provider.timeIntervalCollection;
// Find the keyframe location to render at. Doesn't need to be a whole number.
let keyframeLocation = 0.0;
diff --git a/Source/Scene/VoxelProvider.js b/Source/Scene/VoxelProvider.js
index d4f59eeb3c3..cb1356ddd8b 100644
--- a/Source/Scene/VoxelProvider.js
+++ b/Source/Scene/VoxelProvider.js
@@ -195,6 +195,30 @@ Object.defineProperties(VoxelProvider.prototype, {
maximumTileCount: {
get: DeveloperError.throwInstantiationError,
},
+
+ /**
+ * Gets the number of keyframes in the dataset.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {Number}
+ * @readonly
+ */
+ keyframeCount: {
+ get: DeveloperError.throwInstantiationError,
+ },
+
+ /**
+ * Gets the {@link TimeIntervalCollection} for the dataset, or undefined if it doesn't have timestamps.
+ * This should not be called before {@link VoxelProvider#ready} returns true.
+ *
+ * @memberof VoxelProvider.prototype
+ * @type {TimeIntervalCollection}
+ * @readonly
+ */
+ timeIntervalCollection: {
+ get: DeveloperError.throwInstantiationError,
+ },
});
/**
@@ -206,6 +230,7 @@ Object.defineProperties(VoxelProvider.prototype, {
* @param {Number} [options.tileX=0] The tile's X coordinate.
* @param {Number} [options.tileY=0] The tile's Y coordinate.
* @param {Number} [options.tileZ=0] The tile's Z coordinate.
+ * @param {Number} [options.keyframe=0] The requested keyframe.
* @returns {Promise|undefined} An array of promises for the requested voxel data or undefined if there was a problem loading the data.
*
* @exception {DeveloperError} The provider must be ready.
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index d6653a11f46..ca5e33b18dd 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -426,9 +426,10 @@ function recomputeBoundingVolumesRecursive(that, node) {
function requestTiles(that, keyframeNode) {
const keys = Object.keys(that.megatextures);
const length = keys.length;
+ const keyframe = keyframeNode.keyframe;
for (let i = 0; i < length; i++) {
const metadataName = keys[i];
- requestData(that, keyframeNode, metadataName);
+ requestData(that, keyframeNode, metadataName, keyframe);
}
}
/**
@@ -1496,7 +1497,7 @@ function generateOctree(that, sampleCount, levelBlendFactor) {
if (useLeafNodes) {
const baseIdx = leafNodeCount * 5;
- const useTimeDynamic = false;
+ const useTimeDynamic = true;
if (useTimeDynamic) {
const previousKeyframeNode = node.renderableKeyframeNodePrevious;
const nextKeyframeNode = node.renderableKeyframeNodeNext;
From 8c792a0b3e7c8ceaa3b7a3bd961b6f48b62f43ad Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 13 May 2022 14:38:54 -0700
Subject: [PATCH 061/679] intersection clipping planes working
---
Source/Scene/VoxelPrimitive.js | 111 +++++++++++++++++++++++++++++++++
Source/Scene/VoxelTraversal.js | 2 +-
Source/Shaders/VoxelFS.glsl | 48 ++++++++++++++
3 files changed, 160 insertions(+), 1 deletion(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index da305df1172..55e5a0eef57 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -17,7 +17,9 @@ import Matrix3 from "../Core/Matrix3.js";
import Matrix4 from "../Core/Matrix4.js";
import PrimitiveType from "../Core/PrimitiveType.js";
import BlendingState from "./BlendingState.js";
+import ClippingPlaneCollection from "./ClippingPlaneCollection.js";
import CullFace from "./CullFace.js";
+import getClippingFunction from "./getClippingFunction.js";
import Material from "./Material.js";
import MetadataComponentType from "./MetadataComponentType.js";
import MetadataType from "./MetadataType.js";
@@ -181,6 +183,27 @@ function VoxelPrimitive(options) {
*/
this._maxClippingBoundsOld = new Cartesian3();
+ /**
+ * Clipping planes on the primitive
+ *
+ * @type {ClippingPlaneCollection}
+ * @private
+ */
+ this._clippingPlanes = undefined;
+
+ /**
+ * Keeps track of when the clipping planes change
+ *
+ * @type {Number}
+ * @private
+ */
+ this._clippingPlanesState = 0;
+
+ /**
+ * Keeps track of when the clipping planes are enabled / disabled
+ */
+ this._clippingPlanesEnabled = false;
+
/**
* The primitive's model matrix.
*
@@ -381,6 +404,8 @@ function VoxelPrimitive(options) {
transformNormalLocalToWorld: new Matrix3(),
cameraPositionUv: new Cartesian3(),
ndcSpaceAxisAlignedBoundingBox: new Cartesian4(),
+ clippingPlanesTexture: undefined,
+ clippingPlanesMatrix: new Matrix4(),
stepSize: 0,
pickColor: new Color(),
};
@@ -973,6 +998,22 @@ Object.defineProperties(VoxelPrimitive.prototype, {
},
},
+ /**
+ * The {@link ClippingPlaneCollection} used to selectively disable rendering the primitive.
+ *
+ * @memberof VoxelPrimitive.prototype
+ * @type {ClippingPlaneCollection}
+ */
+ clippingPlanes: {
+ get: function () {
+ return this._clippingPlanes;
+ },
+ set: function (clippingPlanes) {
+ // Don't need to check if undefined, it's handled in the setOwner function
+ ClippingPlaneCollection.setOwner(clippingPlanes, this, "_clippingPlanes");
+ },
+ },
+
/**
* Gets or sets the custom shader. If undefined, {@link VoxelPrimitive.DefaultCustomShader} is set.
*
@@ -1480,6 +1521,45 @@ VoxelPrimitive.prototype.update = function (frameState) {
this._shaderDirty = true;
}
+ // Check if clipping planes changed
+ const clippingPlanes = this._clippingPlanes;
+ if (defined(clippingPlanes)) {
+ clippingPlanes.update(frameState);
+ const clippingPlanesState = clippingPlanes.clippingPlanesState;
+ const clippingPlanesEnabled = clippingPlanes.enabled;
+ if (
+ this._clippingPlanesState !== clippingPlanesState ||
+ this._clippingPlanesEnabled !== clippingPlanesEnabled
+ ) {
+ this._clippingPlanesState = clippingPlanesState;
+ this._clippingPlanesEnabled = clippingPlanesEnabled;
+ if (clippingPlanesEnabled) {
+ uniforms.clippingPlanesTexture = clippingPlanes.texture;
+
+ // Compute the clipping plane's transformation to uv space and then take the inverse
+ // transpose to properly transform the hessian normal form of the plane.
+
+ // transpose(inverse(worldToUv * clippingPlaneLocalToWorld))
+ // transpose(inverse(clippingPlaneLocalToWorld) * inverse(worldToUv))
+ // transpose(inverse(clippingPlaneLocalToWorld) * uvToWorld)
+
+ const transformPositionUvToWorld = this._transformPositionUvToWorld;
+ uniforms.clippingPlanesMatrix = Matrix4.transpose(
+ Matrix4.multiplyTransformation(
+ Matrix4.inverse(
+ clippingPlanes.modelMatrix,
+ uniforms.clippingPlanesMatrix
+ ),
+ transformPositionUvToWorld,
+ uniforms.clippingPlanesMatrix
+ ),
+ uniforms.clippingPlanesMatrix
+ );
+ }
+ this._shaderDirty = true;
+ }
+ }
+
const leafNodeTexture = traversal.leafNodeTexture;
if (defined(leafNodeTexture)) {
uniforms.octreeLeafNodeTexture = traversal.leafNodeTexture;
@@ -1703,6 +1783,12 @@ function buildDrawCommands(that, context) {
const customShader = that._customShader;
const attributeLength = types.length;
const hasStatistics = defined(minimumValues) && defined(maximumValues);
+ const clippingPlanes = that._clippingPlanes;
+ const clippingPlanesLength =
+ defined(clippingPlanes) && clippingPlanes.enabled
+ ? clippingPlanes.length
+ : 0;
+
let uniformMap = that._uniformMap;
// Build shader
@@ -1779,8 +1865,31 @@ function buildDrawCommands(that, context) {
);
}
+ if (clippingPlanesLength > 0) {
+ shaderBuilder.addDefine(
+ "CLIPPING_PLANES",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addDefine(
+ "CLIPPING_PLANES_COUNT",
+ clippingPlanesLength,
+ ShaderDestination.FRAGMENT
+ );
+ }
+
// Count how many intersections the shader will do.
let intersectionCount = shape.shaderMaximumIntersectionsLength;
+
+ if (clippingPlanesLength > 0) {
+ shaderBuilder.addDefine(
+ "CLIPPING_PLANES_INTERSECTION_INDEX",
+ intersectionCount,
+ ShaderDestination.FRAGMENT
+ );
+ intersectionCount += clippingPlanesLength;
+ }
+
if (depthTest) {
shaderBuilder.addDefine(
"DEPTH_INTERSECTION_INDEX",
@@ -1789,6 +1898,7 @@ function buildDrawCommands(that, context) {
);
intersectionCount += 1;
}
+
shaderBuilder.addDefine(
"INTERSECTION_COUNT",
intersectionCount,
@@ -2254,6 +2364,7 @@ VoxelPrimitive.prototype.destroy = function () {
this._pickId = this._pickId && this._pickId.destroy();
this._traversal = this._traversal && this._traversal.destroy();
+ this._clippingPlanes = this._clippingPlanes && this._clippingPlanes.destroy();
return destroyObject(this);
};
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index ca5e33b18dd..8282debb4a4 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -1497,7 +1497,7 @@ function generateOctree(that, sampleCount, levelBlendFactor) {
if (useLeafNodes) {
const baseIdx = leafNodeCount * 5;
- const useTimeDynamic = true;
+ const useTimeDynamic = false;
if (useTimeDynamic) {
const previousKeyframeNode = node.renderableKeyframeNodePrevious;
const nextKeyframeNode = node.renderableKeyframeNodeNext;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 9b87e8c4d59..498de40aeab 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -19,6 +19,9 @@ Below is an example of how this code might look. Properties like "temperature" a
#define STATISTICS
#define PADDING
#define PICKING
+#define CLIPPING_PLANES
+#define CLIPPING_PLANES_COUNT
+#define CLIPPING_PLANES_INTERSECTION_INDEX
// Uniforms
uniform sampler2D u_megatextureTextures[PROPERTY_COUNT];
@@ -199,6 +202,11 @@ uniform float u_stepSize;
uniform vec4 u_pickColor;
#endif
+#if defined(CLIPPING_PLANES)
+ uniform sampler2D u_clippingPlanesTexture;
+ uniform mat4 u_clippingPlanesMatrix;
+#endif
+
#if defined(SHAPE_BOX)
/* Box defines:
#define BOX_INTERSECTION_INDEX ### // always 0
@@ -607,6 +615,26 @@ vec4 intersectHalfPlane(Ray ray, float angle) {
}
#endif
+#if defined(CLIPPING_PLANES)
+// Plane is in Hessian Normal Form
+vec2 intersectPlane(Ray ray, vec4 plane) {
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ vec3 n = plane.xyz; // normal
+ float w = plane.w; // -dot(pointOnPlane, normal)
+
+ float a = dot(o, n);
+ float b = dot(d, n);
+ float t = -(w + a) / b;
+
+ if (dot(d, n) > 0.0) {
+ return vec2(t, +INF_HIT);
+ } else {
+ return vec2(-INF_HIT, t);
+ }
+}
+#endif
+
#if (defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF))) || (defined(SHAPE_CYLINDER) && (defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF) || defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF)))
vec2 intersectHalfSpace(Ray ray, float angle)
{
@@ -1278,6 +1306,21 @@ vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
}
#endif
+#if defined(CLIPPING_PLANES)
+void intersectClippingPlanes(Ray ray, inout Intersections ix) {
+ for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
+ int pixY = i / CLIPPING_PLANES_COUNT;
+ int pixX = i - (pixY * CLIPPING_PLANES_COUNT);
+ vec2 uv = (vec2(pixX, pixY) + 0.5) / float(CLIPPING_PLANES_COUNT);
+ vec4 localPlane = texture2D(u_clippingPlanesTexture, uv);
+ // u_clippingPlanesMatrix bakes in the transformation to UV space.
+ vec4 planeUv = czm_transformPlane(localPlane, u_clippingPlanesMatrix);
+ vec2 intersection = intersectPlane(ray, planeUv);
+ setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + i, intersection);
+ }
+}
+#endif
+
#if defined(DEPTH_TEST)
void intersectDepth(vec2 screenCoord, Ray ray, inout Intersections ix) {
float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, screenCoord));
@@ -1308,6 +1351,11 @@ vec2 intersectScene(vec2 screenCoord, vec3 positionUv, vec3 directionUv, out Int
return vec2(NO_HIT);
}
+ // Clipping planes
+ #if defined(CLIPPING_PLANES)
+ intersectClippingPlanes(ray, ix);
+ #endif
+
// Depth
#if defined(DEPTH_TEST)
intersectDepth(screenCoord, ray, ix);
From df50be7b3073f28e4cb501abd4c7e44d7875ad72 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 13 May 2022 16:26:25 -0700
Subject: [PATCH 062/679] added clipping union but not quite working
---
Source/Scene/VoxelPrimitive.js | 7 ++++++
Source/Shaders/VoxelFS.glsl | 43 ++++++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 55e5a0eef57..c92249fd87f 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1876,6 +1876,13 @@ function buildDrawCommands(that, context) {
clippingPlanesLength,
ShaderDestination.FRAGMENT
);
+ if (clippingPlanes.unionClippingRegions) {
+ shaderBuilder.addDefine(
+ "CLIPPING_PLANES_UNION",
+ undefined,
+ ShaderDestination.FRAGMENT
+ );
+ }
}
// Count how many intersections the shader will do.
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 498de40aeab..a9c2f170ed8 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -20,6 +20,7 @@ Below is an example of how this code might look. Properties like "temperature" a
#define PADDING
#define PICKING
#define CLIPPING_PLANES
+#define CLIPPING_PLANES_UNION
#define CLIPPING_PLANES_COUNT
#define CLIPPING_PLANES_INTERSECTION_INDEX
@@ -1318,6 +1319,48 @@ void intersectClippingPlanes(Ray ray, inout Intersections ix) {
vec2 intersection = intersectPlane(ray, planeUv);
setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + i, intersection);
}
+
+ #if !defined(CLIPPING_PLANES_UNION)
+ // Sort the clipping intersections by t and record whether the interval is
+ // going in the positive or negative direction.
+ vec2 intersections[CLIPPING_PLANES_COUNT];
+ for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
+ vec2 entryExitT = getIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + i);
+ bool goingUpward = entryExitT.y == +INF_HIT;
+ float direction = float(goingUpward); // 0 = downward, 1 = updard
+ float t = goingUpward ? entryExitT.x : entryExitT.y;
+ intersections[i] = vec2(t, direction);
+ }
+
+ // Do bubble sort on the intersections
+ const int sortPasses = CLIPPING_PLANES_COUNT - 1;
+ for (int n = sortPasses; n > 0; --n) {
+ for (int i = 0; i < sortPasses; ++i) {
+ // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to break early instead
+ if (i >= n) { break; }
+ vec2 ix0 = intersections[i + 0];
+ vec2 ix1 = intersections[i + 1];
+ float tmin = min(ix0.x, ix1.x);
+ float tmax = max(ix0.x, ix1.x);
+ float dmin = tmin == ix0.x ? ix0.y : ix1.y;
+ float dmax = tmin == ix0.x ? ix1.y : ix0.y;
+ intersections[i + 0] = vec2(tmin, dmin);
+ intersections[i + 1] = vec2(tmax, dmax);
+ }
+ }
+
+ // Find the intersection intervals
+ for (int i = 0; i < CLIPPING_PLANES_COUNT - 1; i++) {
+ vec2 ix0 = intersections[i + 0];
+ vec2 ix1 = intersections[i + 1];
+
+ // positive direction followed by negative direction
+ bool foundIntersection = ix0.y == 1.0 && ix1.y == 0.0;
+ vec2 entryExitT = foundIntersection ? vec2(ix0.x, ix1.x) : vec2(NO_HIT);
+ setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + i, entryExitT);
+ }
+ #endif
}
#endif
From b0197fff1f917d3a3b5544256f8b18ebc8095c4c Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 13 May 2022 20:24:38 -0700
Subject: [PATCH 063/679] working union and intersection clipping planes
---
Source/Scene/VoxelPrimitive.js | 37 ++++++++++++++++-
Source/Shaders/VoxelFS.glsl | 72 ++++++++++++----------------------
2 files changed, 61 insertions(+), 48 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index c92249fd87f..5cd9297147e 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1788,6 +1788,9 @@ function buildDrawCommands(that, context) {
defined(clippingPlanes) && clippingPlanes.enabled
? clippingPlanes.length
: 0;
+ const clippingPlanesUnion = defined(clippingPlanes)
+ ? clippingPlanes.unionClippingRegions
+ : false;
let uniformMap = that._uniformMap;
@@ -1876,7 +1879,7 @@ function buildDrawCommands(that, context) {
clippingPlanesLength,
ShaderDestination.FRAGMENT
);
- if (clippingPlanes.unionClippingRegions) {
+ if (clippingPlanesUnion) {
shaderBuilder.addDefine(
"CLIPPING_PLANES_UNION",
undefined,
@@ -1894,7 +1897,11 @@ function buildDrawCommands(that, context) {
intersectionCount,
ShaderDestination.FRAGMENT
);
- intersectionCount += clippingPlanesLength;
+ if (clippingPlanesUnion) {
+ intersectionCount += 2;
+ } else {
+ intersectionCount += 1;
+ }
}
if (depthTest) {
@@ -2271,6 +2278,32 @@ function buildDrawCommands(that, context) {
]);
}
+ if (clippingPlanesLength > 0) {
+ // Extract the getClippingPlane function from the getClippingFunction string.
+ // This is a bit of a hack.
+ const functionId = "getClippingPlane";
+ const entireFunction = getClippingFunction(clippingPlanes, context);
+ const functionSignatureBegin = 0;
+ const functionSignatureEnd = entireFunction.indexOf(")") + 1;
+ const functionBodyBegin =
+ entireFunction.indexOf("{", functionSignatureEnd) + 1;
+ const functionBodyEnd = entireFunction.indexOf("}", functionBodyBegin);
+ const functionSignature = entireFunction.slice(
+ functionSignatureBegin,
+ functionSignatureEnd
+ );
+ const functionBody = entireFunction.slice(
+ functionBodyBegin,
+ functionBodyEnd
+ );
+ shaderBuilder.addFunction(
+ functionId,
+ functionSignature,
+ ShaderDestination.FRAGMENT
+ );
+ shaderBuilder.addFunctionLines(functionId, [functionBody]);
+ }
+
// Compile shaders
const shaderBuilderPick = shaderBuilder.clone();
shaderBuilderPick.addDefine("PICKING", undefined, ShaderDestination.FRAGMENT);
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index a9c2f170ed8..f72294d0d9c 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1309,56 +1309,36 @@ vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
#if defined(CLIPPING_PLANES)
void intersectClippingPlanes(Ray ray, inout Intersections ix) {
- for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
- int pixY = i / CLIPPING_PLANES_COUNT;
- int pixX = i - (pixY * CLIPPING_PLANES_COUNT);
- vec2 uv = (vec2(pixX, pixY) + 0.5) / float(CLIPPING_PLANES_COUNT);
- vec4 localPlane = texture2D(u_clippingPlanesTexture, uv);
- // u_clippingPlanesMatrix bakes in the transformation to UV space.
- vec4 planeUv = czm_transformPlane(localPlane, u_clippingPlanesMatrix);
- vec2 intersection = intersectPlane(ray, planeUv);
- setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + i, intersection);
- }
-
- #if !defined(CLIPPING_PLANES_UNION)
- // Sort the clipping intersections by t and record whether the interval is
- // going in the positive or negative direction.
- vec2 intersections[CLIPPING_PLANES_COUNT];
+ #if defined(CLIPPING_PLANES_UNION)
+ float minPositiveT = +INF_HIT;
+ float maxNegativeT = -INF_HIT;
for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
- vec2 entryExitT = getIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + i);
- bool goingUpward = entryExitT.y == +INF_HIT;
- float direction = float(goingUpward); // 0 = downward, 1 = updard
- float t = goingUpward ? entryExitT.x : entryExitT.y;
- intersections[i] = vec2(t, direction);
+ vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i, u_clippingPlanesMatrix);
+ vec2 intersection = intersectPlane(ray, planeUv);
+ if (intersection.y == +INF_HIT) {
+ minPositiveT = min(minPositiveT, intersection.x);
+ } else {
+ maxNegativeT = max(maxNegativeT, intersection.y);
+ }
}
-
- // Do bubble sort on the intersections
- const int sortPasses = CLIPPING_PLANES_COUNT - 1;
- for (int n = sortPasses; n > 0; --n) {
- for (int i = 0; i < sortPasses; ++i) {
- // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
- // loop with non-constant condition, so it has to break early instead
- if (i >= n) { break; }
- vec2 ix0 = intersections[i + 0];
- vec2 ix1 = intersections[i + 1];
- float tmin = min(ix0.x, ix1.x);
- float tmax = max(ix0.x, ix1.x);
- float dmin = tmin == ix0.x ? ix0.y : ix1.y;
- float dmax = tmin == ix0.x ? ix1.y : ix0.y;
- intersections[i + 0] = vec2(tmin, dmin);
- intersections[i + 1] = vec2(tmax, dmax);
+ setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + 0, vec2(-INF_HIT, maxNegativeT));
+ setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + 1, vec2(minPositiveT, +INF_HIT));
+ #else
+ float maxPositiveT = -INF_HIT;
+ float minNegativeT = +INF_HIT;
+ for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
+ vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, i, u_clippingPlanesMatrix);
+ vec2 intersection = intersectPlane(ray, planeUv);
+ if (intersection.y == +INF_HIT) {
+ maxPositiveT = max(maxPositiveT, intersection.x);
+ } else {
+ minNegativeT = min(minNegativeT, intersection.y);
}
}
-
- // Find the intersection intervals
- for (int i = 0; i < CLIPPING_PLANES_COUNT - 1; i++) {
- vec2 ix0 = intersections[i + 0];
- vec2 ix1 = intersections[i + 1];
-
- // positive direction followed by negative direction
- bool foundIntersection = ix0.y == 1.0 && ix1.y == 0.0;
- vec2 entryExitT = foundIntersection ? vec2(ix0.x, ix1.x) : vec2(NO_HIT);
- setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + i, entryExitT);
+ if (maxPositiveT < minNegativeT) {
+ setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX, vec2(maxPositiveT, minNegativeT));
+ } else {
+ setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX, vec2(NO_HIT));
}
#endif
}
From b7f329380b751d0b2b14ba070ccf19d502d25ea8 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 13 May 2022 20:37:18 -0700
Subject: [PATCH 064/679] fast path for one clipping plane
---
Source/Scene/VoxelPrimitive.js | 4 +++-
Source/Shaders/VoxelFS.glsl | 10 ++++++++--
2 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 5cd9297147e..31ad8bdeb7b 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1897,7 +1897,9 @@ function buildDrawCommands(that, context) {
intersectionCount,
ShaderDestination.FRAGMENT
);
- if (clippingPlanesUnion) {
+ if (clippingPlanesLength === 1) {
+ intersectionCount += 1;
+ } else if (clippingPlanesUnion) {
intersectionCount += 2;
} else {
intersectionCount += 1;
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index f72294d0d9c..f0280d4e68f 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1309,7 +1309,13 @@ vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
#if defined(CLIPPING_PLANES)
void intersectClippingPlanes(Ray ray, inout Intersections ix) {
- #if defined(CLIPPING_PLANES_UNION)
+ #if (CLIPPING_PLANES_COUNT == 1)
+ // Union and intersection are the same when there's one clipping plane, and the code
+ // is more simplified.
+ vec4 planeUv = getClippingPlane(u_clippingPlanesTexture, 0, u_clippingPlanesMatrix);
+ vec2 intersection = intersectPlane(ray, planeUv);
+ setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX, intersection);
+ #elif defined(CLIPPING_PLANES_UNION)
float minPositiveT = +INF_HIT;
float maxNegativeT = -INF_HIT;
for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
@@ -1323,7 +1329,7 @@ void intersectClippingPlanes(Ray ray, inout Intersections ix) {
}
setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + 0, vec2(-INF_HIT, maxNegativeT));
setIntersectionPair(ix, CLIPPING_PLANES_INTERSECTION_INDEX + 1, vec2(minPositiveT, +INF_HIT));
- #else
+ #else // intersection
float maxPositiveT = -INF_HIT;
float minNegativeT = +INF_HIT;
for (int i = 0; i < CLIPPING_PLANES_COUNT; i++) {
From 9d81882099cea8209a448ed793387d068e19adc4 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Fri, 13 May 2022 20:49:18 -0700
Subject: [PATCH 065/679] small shader cleaning
---
Source/Shaders/VoxelFS.glsl | 273 ++++++++++++++++++------------------
1 file changed, 133 insertions(+), 140 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index f0280d4e68f..33ee94c9382 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -384,9 +384,6 @@ int intMax(int a, int b) {
int intClamp(int v, int minVal, int maxVal) {
return intMin(intMax(v, minVal), maxVal);
}
-float safeMod(float a, float m) {
- return mod(mod(a, m) + m, m);
-}
bool inRange(float v, float minVal, float maxVal) {
return clamp(v, minVal, maxVal) == v;
}
@@ -412,139 +409,6 @@ vec2 index1DTo2DTexcoord(int index, ivec2 dimensions, vec2 uvScale)
// --------------------------------------------------------
// Intersection tests, shape coordinate conversions, etc
// --------------------------------------------------------
-struct Intersections {
- // Don't access these member variables directly - call the functions instead.
-
- #if (INTERSECTION_COUNT > 1)
- // Store an array of intersections. Each intersection is composed of:
- // x for the T value
- // y for the shape type - which encodes positive vs negative and entering vs exiting
- // For example:
- // y = 0: positive shape entry
- // y = 1: positive shape exit
- // y = 2: negative shape entry
- // y = 3: negative shape exit
- vec2 intersections[INTERSECTION_COUNT * 2];
-
- // Maintain state for future nextIntersection calls
- int index;
- int surroundCount;
- bool surroundIsPositive;
- #else
- // When there's only one positive shape intersection none of the extra stuff is needed.
- float intersections[2];
- #endif
-};
-
-// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#if (INTERSECTION_COUNT > 1)
- #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)].x
-#else
- #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)]
-#endif
-
-// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#define getIntersectionPair(/*inout Intersections*/ ix, /*int*/ index) vec2(getIntersection((ix), (index) * 2 + 0), getIntersection((ix), (index) * 2 + 1))
-
-// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#if (INTERSECTION_COUNT > 1)
- #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = vec2((t), float(!positive) * 2.0 + float(!enter))
-#else
- #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = (t)
-#endif
-
-// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
-#if (INTERSECTION_COUNT > 1)
- #define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = vec2((entryExit).x, float((index) > 0) * 2.0 + 0.0); (ix).intersections[(index) * 2 + 1] = vec2((entryExit).y, float((index) > 0) * 2.0 + 1.0)
-#else
- #define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = (entryExit).x; (ix).intersections[(index) * 2 + 1] = (entryExit).y
-#endif
-
-#if (INTERSECTION_COUNT > 1)
-vec2 nextIntersection(inout Intersections ix) {
- vec2 entryExitT = vec2(NO_HIT);
-
- const int passCount = INTERSECTION_COUNT * 2;
- for (int i = 0; i < passCount; ++i) {
- // The loop should be: for (i = ix.index; i < passCount; ++i) {...} but WebGL1 cannot
- // loop with non-constant condition, so it has to continue instead.
- if (i < ix.index) {
- continue;
- }
-
- vec2 intersect = ix.intersections[i];
- float t = intersect.x;
- bool currShapeIsPositive = intersect.y < 2.0;
- bool enter = mod(intersect.y, 2.0) == 0.0;
-
- ix.surroundCount += enter ? +1 : -1;
- ix.surroundIsPositive = currShapeIsPositive ? enter : ix.surroundIsPositive;
-
- // entering positive or exiting negative
- if (ix.surroundCount == 1 && ix.surroundIsPositive && enter == currShapeIsPositive) {
- entryExitT.x = t;
- }
-
- // exiting positive or entering negative after being inside positive
- // TODO: Can this be simplified?
- bool exitPositive = !enter && currShapeIsPositive && ix.surroundCount == 0;
- bool enterNegativeFromPositive = enter && !currShapeIsPositive && ix.surroundCount == 2 && ix.surroundIsPositive;
- if (exitPositive || enterNegativeFromPositive) {
- entryExitT.y = t;
-
- // entry and exit have been found, so the loop can stop
- if (exitPositive) {
- // After exiting positive shape there is nothing left to intersect, so jump to the end index.
- ix.index = passCount;
- } else {
- // There could be more intersections against the positive shape in the future.
- ix.index = i + 1;
- }
- break;
- }
- }
-
- return entryExitT;
-}
-#endif
-
-#if (INTERSECTION_COUNT > 1)
-void initializeIntersections(inout Intersections ix) {
- // Sort the intersections from min T to max T with bubble sort.
- // Note: If this sorting function changes, some of the intersection test may
- // need to be updated. Search for "bubble sort" to find those areas.
- const int sortPasses = INTERSECTION_COUNT * 2 - 1;
- for (int n = sortPasses; n > 0; --n) {
- for (int i = 0; i < sortPasses; ++i) {
- // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
- // loop with non-constant condition, so it has to break early instead
- if (i >= n) { break; }
-
- vec2 intersect0 = ix.intersections[i + 0];
- vec2 intersect1 = ix.intersections[i + 1];
-
- float t0 = intersect0.x;
- float t1 = intersect1.x;
- float b0 = intersect0.y;
- float b1 = intersect1.y;
-
- float tmin = min(t0, t1);
- float tmax = max(t0, t1);
- float bmin = tmin == t0 ? b0 : b1;
- float bmax = tmin == t0 ? b1 : b0;
-
- ix.intersections[i + 0] = vec2(tmin, bmin);
- ix.intersections[i + 1] = vec2(tmax, bmax);
- }
- }
-
- // Prepare initial state for nextIntersection
- ix.index = 0;
- ix.surroundCount = 0;
- ix.surroundIsPositive = false;
-}
-#endif
-
#if defined(SHAPE_BOX)
vec2 intersectUnitCube(Ray ray) // Unit cube from [-1, +1]
{
@@ -973,6 +837,139 @@ float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
}
#endif
+struct Intersections {
+ // Don't access these member variables directly - call the functions instead.
+
+ #if (INTERSECTION_COUNT > 1)
+ // Store an array of intersections. Each intersection is composed of:
+ // x for the T value
+ // y for the shape type - which encodes positive vs negative and entering vs exiting
+ // For example:
+ // y = 0: positive shape entry
+ // y = 1: positive shape exit
+ // y = 2: negative shape entry
+ // y = 3: negative shape exit
+ vec2 intersections[INTERSECTION_COUNT * 2];
+
+ // Maintain state for future nextIntersection calls
+ int index;
+ int surroundCount;
+ bool surroundIsPositive;
+ #else
+ // When there's only one positive shape intersection none of the extra stuff is needed.
+ float intersections[2];
+ #endif
+};
+
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#if (INTERSECTION_COUNT > 1)
+ #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)].x
+#else
+ #define getIntersection(/*inout Intersections*/ ix, /*int*/ index) (ix).intersections[(index)]
+#endif
+
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#define getIntersectionPair(/*inout Intersections*/ ix, /*int*/ index) vec2(getIntersection((ix), (index) * 2 + 0), getIntersection((ix), (index) * 2 + 1))
+
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#if (INTERSECTION_COUNT > 1)
+ #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = vec2((t), float(!positive) * 2.0 + float(!enter))
+#else
+ #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*enter*/ enter) (ix).intersections[(index)] = (t)
+#endif
+
+// Using a define instead of a real function because WebGL1 cannot access array with non-constant index.
+#if (INTERSECTION_COUNT > 1)
+ #define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = vec2((entryExit).x, float((index) > 0) * 2.0 + 0.0); (ix).intersections[(index) * 2 + 1] = vec2((entryExit).y, float((index) > 0) * 2.0 + 1.0)
+#else
+ #define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = (entryExit).x; (ix).intersections[(index) * 2 + 1] = (entryExit).y
+#endif
+
+#if (INTERSECTION_COUNT > 1)
+void initializeIntersections(inout Intersections ix) {
+ // Sort the intersections from min T to max T with bubble sort.
+ // Note: If this sorting function changes, some of the intersection test may
+ // need to be updated. Search for "bubble sort" to find those areas.
+ const int sortPasses = INTERSECTION_COUNT * 2 - 1;
+ for (int n = sortPasses; n > 0; --n) {
+ for (int i = 0; i < sortPasses; ++i) {
+ // The loop should be: for (i = 0; i < n; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to break early instead
+ if (i >= n) { break; }
+
+ vec2 intersect0 = ix.intersections[i + 0];
+ vec2 intersect1 = ix.intersections[i + 1];
+
+ float t0 = intersect0.x;
+ float t1 = intersect1.x;
+ float b0 = intersect0.y;
+ float b1 = intersect1.y;
+
+ float tmin = min(t0, t1);
+ float tmax = max(t0, t1);
+ float bmin = tmin == t0 ? b0 : b1;
+ float bmax = tmin == t0 ? b1 : b0;
+
+ ix.intersections[i + 0] = vec2(tmin, bmin);
+ ix.intersections[i + 1] = vec2(tmax, bmax);
+ }
+ }
+
+ // Prepare initial state for nextIntersection
+ ix.index = 0;
+ ix.surroundCount = 0;
+ ix.surroundIsPositive = false;
+}
+#endif
+
+#if (INTERSECTION_COUNT > 1)
+vec2 nextIntersection(inout Intersections ix) {
+ vec2 entryExitT = vec2(NO_HIT);
+
+ const int passCount = INTERSECTION_COUNT * 2;
+ for (int i = 0; i < passCount; ++i) {
+ // The loop should be: for (i = ix.index; i < passCount; ++i) {...} but WebGL1 cannot
+ // loop with non-constant condition, so it has to continue instead.
+ if (i < ix.index) {
+ continue;
+ }
+
+ vec2 intersect = ix.intersections[i];
+ float t = intersect.x;
+ bool currShapeIsPositive = intersect.y < 2.0;
+ bool enter = mod(intersect.y, 2.0) == 0.0;
+
+ ix.surroundCount += enter ? +1 : -1;
+ ix.surroundIsPositive = currShapeIsPositive ? enter : ix.surroundIsPositive;
+
+ // entering positive or exiting negative
+ if (ix.surroundCount == 1 && ix.surroundIsPositive && enter == currShapeIsPositive) {
+ entryExitT.x = t;
+ }
+
+ // exiting positive or entering negative after being inside positive
+ // TODO: Can this be simplified?
+ bool exitPositive = !enter && currShapeIsPositive && ix.surroundCount == 0;
+ bool enterNegativeFromPositive = enter && !currShapeIsPositive && ix.surroundCount == 2 && ix.surroundIsPositive;
+ if (exitPositive || enterNegativeFromPositive) {
+ entryExitT.y = t;
+
+ // entry and exit have been found, so the loop can stop
+ if (exitPositive) {
+ // After exiting positive shape there is nothing left to intersect, so jump to the end index.
+ ix.index = passCount;
+ } else {
+ // There could be more intersections against the positive shape in the future.
+ ix.index = i + 1;
+ }
+ break;
+ }
+ }
+
+ return entryExitT;
+}
+#endif
+
#if defined(SHAPE_BOX)
void intersectShape(Ray ray, inout Intersections ix)
{
@@ -1739,10 +1736,6 @@ void main()
fragmentInput.voxel.viewDirWorld = viewDirWorld;
fragmentInput.voxel.travelDistance = traversalData.stepT;
- #if defined(STYLE_USE_POSITION_EC)
- fragmentInput.voxel.positionEC = vec3(u_transformPositionUvToView * vec4(positionUv, 1.0));
- #endif
-
// Run the custom shader
czm_modelMaterial materialOutput;
fragmentMain(fragmentInput, materialOutput);
From 46c6e2f2fe93203746d8b23160b83df5af72b722 Mon Sep 17 00:00:00 2001
From: Ian Lilley
Date: Tue, 17 May 2022 09:15:25 -0700
Subject: [PATCH 066/679] cant use scratch variable in promise chain
---
Source/Scene/Cesium3DTilesVoxelProvider.js | 25 ++++++++--------------
1 file changed, 9 insertions(+), 16 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
index 7a85e8e7762..e1f868ca25b 100644
--- a/Source/Scene/Cesium3DTilesVoxelProvider.js
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -365,15 +365,6 @@ function Cesium3DTilesVoxelProvider(options) {
});
}
-const scratchImplicitTileCoordinates = new ImplicitTileCoordinates({
- subdivisionScheme: ImplicitSubdivisionScheme.OCTREE, // not known yet
- subtreeLevels: 1, // not known yet
- level: 0,
- x: 0,
- y: 0,
- z: 0,
-});
-
/**
* Requests the data for a given tile. The data is a flattened 3D array ordered by X, then Y, then Z.
* This function should not be called before {@link VoxelProvider#ready} returns true.
@@ -419,13 +410,15 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
const types = this.types;
const componentTypes = this.componentTypes;
- const tileCoordinates = scratchImplicitTileCoordinates;
- tileCoordinates.subdivisionScheme = implicitTileset.subdivisionScheme;
- tileCoordinates.subtreeLevels = implicitTileset.subtreeLevels;
- tileCoordinates.level = tileLevel;
- tileCoordinates.x = tileX;
- tileCoordinates.y = tileY;
- tileCoordinates.z = tileZ;
+ // Can't use a scratch variable here because the object is used inside the promise chain.
+ const tileCoordinates = new ImplicitTileCoordinates({
+ subdivisionScheme: implicitTileset.subdivisionScheme,
+ subtreeLevels: implicitTileset.subtreeLevels,
+ level: tileLevel,
+ x: tileX,
+ y: tileY,
+ z: tileZ,
+ });
// First load the subtree to check if the tile is available.
// If the subtree has been requested previously it might still be in the cache.
From a27d7338f5e0df14a21c97261fd565375e049d52 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Wed, 1 Jun 2022 18:23:12 -0400
Subject: [PATCH 067/679] Use newer property name
---
Source/Scene/Cesium3DTilesVoxelProvider.js | 2 +-
Source/Scene/GltfVoxelProvider.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
index e1f868ca25b..128dbbe4213 100644
--- a/Source/Scene/Cesium3DTilesVoxelProvider.js
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -563,7 +563,7 @@ function getGltfLoader(implicitTileset, tileCoord) {
const gltfLoader = new GltfLoader({
gltfResource: gltfResource,
releaseGltfJson: false,
- loadAsTypedArray: true,
+ loadAttributesAsTypedArray: true,
});
gltfLoader.load();
diff --git a/Source/Scene/GltfVoxelProvider.js b/Source/Scene/GltfVoxelProvider.js
index 6b8a68f0112..e3a3f4f24e2 100644
--- a/Source/Scene/GltfVoxelProvider.js
+++ b/Source/Scene/GltfVoxelProvider.js
@@ -199,7 +199,7 @@ function GltfVoxelProvider(options) {
const loader = new GltfLoader({
gltfResource: gltfResource,
releaseGltfJson: true,
- loadAsTypedArray: true,
+ loadAttributesAsTypedArray: true,
});
loader.load();
promise = loader.promise;
From 0d8432f841cc9e22eeddd2bb711c125873e1e03a Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 25 Jul 2022 13:31:32 -0400
Subject: [PATCH 068/679] Fix variable name
---
Source/Scene/VoxelPrimitive.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 31ad8bdeb7b..976638f3fcf 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -520,12 +520,12 @@ Object.defineProperties(VoxelPrimitive.prototype, {
//>>includeStart('debug', pragmas.debug);
if (!this._ready) {
throw new DeveloperError(
- "orientedBoudingBox must not be called before the primitive is ready."
+ "orientedBoundingBox must not be called before the primitive is ready."
);
}
//>>includeEnd('debug');
- return this._shape.orientedBoudingBox;
+ return this._shape.orientedBoundingBox;
},
},
From 3d1a8ad08329f301d7242b7a003cf25bbc12a0fb Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 25 Jul 2022 17:54:52 -0400
Subject: [PATCH 069/679] Don't request tile multiple times
---
Source/Scene/VoxelTraversal.js | 25 ++-----------------------
1 file changed, 2 insertions(+), 23 deletions(-)
diff --git a/Source/Scene/VoxelTraversal.js b/Source/Scene/VoxelTraversal.js
index 8282debb4a4..df2472a339d 100644
--- a/Source/Scene/VoxelTraversal.js
+++ b/Source/Scene/VoxelTraversal.js
@@ -413,35 +413,15 @@ function recomputeBoundingVolumesRecursive(that, node) {
}
}
-/**
- * Call requestData for each metadata
- *
- * @function
- *
- * @param {VoxelTraversal} that
- * @param {KeyframeNode} keyframeNode
- *
- * @private
- */
-function requestTiles(that, keyframeNode) {
- const keys = Object.keys(that.megatextures);
- const length = keys.length;
- const keyframe = keyframeNode.keyframe;
- for (let i = 0; i < length; i++) {
- const metadataName = keys[i];
- requestData(that, keyframeNode, metadataName, keyframe);
- }
-}
/**
* @function
*
* @param {VoxelTraversal} that
* @param {KeyframeNode} keyframeNode
- * @param {String} metadataName
*
* @private
*/
-function requestData(that, keyframeNode, metadataName) {
+function requestData(that, keyframeNode) {
if (
that._simultaneousRequestCount >=
VoxelTraversal.simultaneousRequestCountMaximum
@@ -503,7 +483,6 @@ function requestData(that, keyframeNode, metadataName) {
tileY: tileY,
tileZ: tileZ,
keyframe: keyframe,
- metadataName: metadataName,
});
if (defined(promise)) {
@@ -802,7 +781,7 @@ function loadAndUnload(that, frameState) {
continue;
}
if (highPriorityKeyframeNode.state === VoxelTraversal.LoadState.UNLOADED) {
- requestTiles(that, highPriorityKeyframeNode);
+ requestData(that, highPriorityKeyframeNode);
}
if (highPriorityKeyframeNode.state === VoxelTraversal.LoadState.RECEIVED) {
let addNodeIndex = 0;
From f1b5f8dbd9ececb4fe5a4f8427cbfed15e831168 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 25 Jul 2022 19:44:13 -0400
Subject: [PATCH 070/679] Get availability of subtree root tile from parent
subtree to avoid 404s
---
Source/Scene/Cesium3DTilesVoxelProvider.js | 26 +++++++++++++++++++---
1 file changed, 23 insertions(+), 3 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
index 128dbbe4213..1779ba02642 100644
--- a/Source/Scene/Cesium3DTilesVoxelProvider.js
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -10,7 +10,6 @@ import Matrix4 from "../Core/Matrix4.js";
import Resource from "../Core/Resource.js";
import Cesium3DTilesetMetadata from "./Cesium3DTilesetMetadata.js";
import GltfLoader from "./GltfLoader.js";
-import ImplicitSubdivisionScheme from "./ImplicitSubdivisionScheme.js";
import ImplicitSubtree from "./ImplicitSubtree.js";
import ImplicitTileCoordinates from "./ImplicitTileCoordinates.js";
import ImplicitTileset from "./ImplicitTileset.js";
@@ -422,7 +421,18 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
// First load the subtree to check if the tile is available.
// If the subtree has been requested previously it might still be in the cache.
- const subtreeCoord = tileCoordinates.getSubtreeCoordinates();
+ let subtreeCoord;
+
+ const isSubtreeRoot =
+ tileCoordinates.isSubtreeRoot() && tileCoordinates.level > 0;
+
+ if (isSubtreeRoot) {
+ // Check availability from the parent subtree so that we don't try fetching a non-existent subtree
+ subtreeCoord = tileCoordinates.getParentSubtreeCoordinates();
+ } else {
+ subtreeCoord = tileCoordinates.getSubtreeCoordinates();
+ }
+
let subtree = subtreeCache.find(subtreeCoord);
const that = this;
@@ -467,7 +477,17 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
return subtreePromise
.then(function (subtree) {
const subtreeLoaderIndex = that._subtreeLoaders.indexOf(subtree);
- if (!subtree.tileIsAvailableAtCoordinates(tileCoordinates)) {
+
+ let available = false;
+ if (isSubtreeRoot) {
+ available = subtree.childSubtreeIsAvailableAtCoordinates(
+ tileCoordinates
+ );
+ } else {
+ available = subtree.tileIsAvailableAtCoordinates(tileCoordinates);
+ }
+
+ if (!available) {
if (subtreeLoaderIndex !== -1) {
that._subtreeLoaders.splice(subtreeLoaderIndex);
}
From 2718e5b2df7bdeff3b2c6708af5df6e22da97103 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Mon, 25 Jul 2022 20:48:07 -0400
Subject: [PATCH 071/679] Fix splice bug that caused non-deterministic loading
---
Source/Scene/Cesium3DTilesVoxelProvider.js | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Source/Scene/Cesium3DTilesVoxelProvider.js b/Source/Scene/Cesium3DTilesVoxelProvider.js
index 1779ba02642..0dd6b3e63e5 100644
--- a/Source/Scene/Cesium3DTilesVoxelProvider.js
+++ b/Source/Scene/Cesium3DTilesVoxelProvider.js
@@ -270,7 +270,7 @@ function Cesium3DTilesVoxelProvider(options) {
return rootGltfLoader.promise;
})
.then(function (rootGltfLoader) {
- that._gltfLoaders.splice(that._gltfLoaders.indexOf(rootGltfLoader));
+ that._gltfLoaders.splice(that._gltfLoaders.indexOf(rootGltfLoader), 1);
const gltfPrimitive = rootGltfLoader.components.nodes[0].primitives[0];
const voxel = gltfPrimitive.voxel;
const primitiveType = gltfPrimitive.primitiveType;
@@ -468,7 +468,8 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
that._subtreeLoaders.push(subtree);
}
that._subtreeResourceLoaders.splice(
- that._subtreeResourceLoaders.indexOf(subtreeResource)
+ that._subtreeResourceLoaders.indexOf(subtreeResource),
+ 1
);
return subtree.readyPromise;
});
@@ -489,7 +490,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
if (!available) {
if (subtreeLoaderIndex !== -1) {
- that._subtreeLoaders.splice(subtreeLoaderIndex);
+ that._subtreeLoaders.splice(subtreeLoaderIndex, 1);
}
return Promise.reject("Tile is not available");
}
@@ -498,7 +499,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
that._gltfLoaders.push(gltfLoader);
if (subtreeLoaderIndex !== -1) {
- that._subtreeLoaders.splice(subtreeLoaderIndex);
+ that._subtreeLoaders.splice(subtreeLoaderIndex, 1);
}
return gltfLoader.promise;
@@ -527,7 +528,7 @@ Cesium3DTilesVoxelProvider.prototype.requestData = function (options) {
);
}
- that._gltfLoaders.splice(that._gltfLoaders.indexOf(gltfLoader));
+ that._gltfLoaders.splice(that._gltfLoaders.indexOf(gltfLoader), 1);
return Promise.resolve(data);
});
};
From dcb828e53ff88c48dc9621b11c03fb514dcf99cc Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Thu, 28 Jul 2022 20:41:13 -0400
Subject: [PATCH 072/679] Fix non-uniform scale
---
Source/Scene/VoxelBoxShape.js | 13 ++++++-------
Source/Scene/VoxelPrimitive.js | 13 ++-----------
Source/Shaders/VoxelVS.glsl | 1 -
3 files changed, 8 insertions(+), 19 deletions(-)
diff --git a/Source/Scene/VoxelBoxShape.js b/Source/Scene/VoxelBoxShape.js
index 782caeb50c7..aa53a13d58c 100644
--- a/Source/Scene/VoxelBoxShape.js
+++ b/Source/Scene/VoxelBoxShape.js
@@ -101,7 +101,7 @@ function VoxelBoxShape() {
this.shaderMaximumIntersectionsLength = undefined;
}
-const scratchTranslation = new Cartesian3();
+const scratchCenter = new Cartesian3();
const scratchScale = new Cartesian3();
const scratchRotation = new Matrix3();
const scratchTransformLocalToBounds = new Matrix4();
@@ -502,13 +502,12 @@ function getBoxChunkObb(minimumBounds, maximumBounds, matrix, result) {
result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
} else {
let scale = Matrix4.getScale(matrix, scratchScale);
- const translation = Matrix4.getTranslation(matrix, scratchTranslation);
- result.center = Cartesian3.fromElements(
- translation.x + scale.x * 0.5 * (minimumBounds.x + maximumBounds.x),
- translation.y + scale.y * 0.5 * (maximumBounds.y + minimumBounds.y),
- translation.z + scale.z * 0.5 * (maximumBounds.z + minimumBounds.z),
- result.center
+ const localCenter = Cartesian3.midpoint(
+ minimumBounds,
+ maximumBounds,
+ scratchCenter
);
+ result.center = Matrix4.multiplyByPoint(matrix, localCenter, result.center);
scale = Cartesian3.fromElements(
scale.x * 0.5 * (maximumBounds.x - minimumBounds.x),
scale.y * 0.5 * (maximumBounds.y - minimumBounds.y),
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 976638f3fcf..9fa8c17fa01 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1072,9 +1072,7 @@ const scratchIntersect = new Cartesian4();
const scratchNdcAabb = new Cartesian4();
const scratchScale = new Cartesian3();
const scratchLocalScale = new Cartesian3();
-const scratchInverseLocalScale = new Cartesian3();
const scratchRotation = new Matrix3();
-const scratchInverseRotation = new Matrix3();
const scratchRotationAndLocalScale = new Matrix3();
const scratchTransformPositionWorldToLocal = new Matrix4();
const scratchTransformPositionLocalToWorld = new Matrix4();
@@ -1323,7 +1321,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
scratchRotation
);
// Note that inverse(rotation) is the same as transpose(rotation)
- const inverseRotation = Matrix3.transpose(rotation, scratchInverseRotation);
const scale = Matrix4.getScale(transformPositionLocalToWorld, scratchScale);
const maximumScaleComponent = Cartesian3.maximumComponent(scale);
const localScale = Cartesian3.divideByScalar(
@@ -1331,11 +1328,6 @@ VoxelPrimitive.prototype.update = function (frameState) {
maximumScaleComponent,
scratchLocalScale
);
- const inverseLocalScale = Cartesian3.divideComponents(
- Cartesian3.ONE,
- localScale,
- scratchInverseLocalScale
- );
const rotationAndLocalScale = Matrix3.multiplyByScale(
rotation,
localScale,
@@ -1356,9 +1348,8 @@ VoxelPrimitive.prototype.update = function (frameState) {
transformPositionUvToLocal,
this._transformPositionUvToWorld
);
- this._transformDirectionWorldToLocal = Matrix3.setScale(
- inverseRotation,
- inverseLocalScale,
+ this._transformDirectionWorldToLocal = Matrix4.getMatrix3(
+ transformPositionWorldToLocal,
this._transformDirectionWorldToLocal
);
this._transformNormalLocalToWorld = Matrix3.inverseTranspose(
diff --git a/Source/Shaders/VoxelVS.glsl b/Source/Shaders/VoxelVS.glsl
index c83efe4ea4e..6c29c959b1c 100644
--- a/Source/Shaders/VoxelVS.glsl
+++ b/Source/Shaders/VoxelVS.glsl
@@ -8,5 +8,4 @@ void main() {
vec2 translation = 0.5 * (aabbMax + aabbMin);
vec2 scale = 0.5 * (aabbMax - aabbMin);
gl_Position = vec4(position * scale + translation, 0.0, 1.0);
- gl_Position = vec4(position, 0.0, 1.0);
}
From 3ef8e885d052dadd1c27e783587ddad62a36a1f5 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 29 Jul 2022 11:31:19 -0400
Subject: [PATCH 073/679] Sample parent tile correctly in
traverseOctreeFromExisting
---
Source/Shaders/VoxelFS.glsl | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 33ee94c9382..39c3b2f350b 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1659,7 +1659,15 @@ void traverseOctreeFromExisting(in vec3 positionUv, inout TraversalData traversa
if (insideTile) {
for (int i = 0; i < SAMPLE_COUNT; i++) {
- sampleDatas[i].tileUv = traversalData.positionUvLocal;
+ if (sampleDatas[i].usingParentMegatextureIndex) {
+ ivec4 parentOctreeCoords;
+ parentOctreeCoords.xyz = traversalData.octreeCoords.xyz / ivec3(2);
+ parentOctreeCoords.w = traversalData.octreeCoords.w - 1;
+ float parentDimAtLevel = pow(2.0, float(parentOctreeCoords.w));
+ sampleDatas[i].tileUv = traversalData.positionUvShapeSpace * parentDimAtLevel - vec3(parentOctreeCoords.xyz);
+ } else {
+ sampleDatas[i].tileUv = traversalData.positionUvLocal;
+ }
}
} else {
// Go up tree
From f8b1c3907f244ae14c80b305b2643f81d9bb2f32 Mon Sep 17 00:00:00 2001
From: Sean Lilley
Date: Fri, 5 Aug 2022 14:59:35 -0400
Subject: [PATCH 074/679] Fix flat clipping bug
---
Source/Shaders/VoxelFS.glsl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 39c3b2f350b..fdb5bb9768b 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1260,7 +1260,7 @@ vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
// Compute radius
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
- float radius = 1.0;
+ float radius = length(positionLocal.xy); // [0,1]
#else
float radius = length(positionLocal.xy); // [0,1]
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
@@ -1270,7 +1270,7 @@ vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
// Compute height
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT)
- float height = 1.0;
+ float height = positionUv.z; // [0,1]
#else
float height = positionUv.z; // [0,1]
#if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
From fe1caf6b309c7422e99a07f794e68237407b892d Mon Sep 17 00:00:00 2001
From: Jeshurun Hembd
Date: Fri, 5 Aug 2022 10:23:48 -0400
Subject: [PATCH 075/679] Fix ambiguous function call error on Windows in
VoxelFS.glsl
---
Source/Shaders/VoxelFS.glsl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index fdb5bb9768b..5ea5529cd17 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1502,7 +1502,7 @@ Properties getPropertiesFromMegatexture(in SampleData sampleData) {
}
// Convert an array of sample datas to a final weighted properties.
-Properties getPropertiesFromMegatexture(SampleData sampleDatas[SAMPLE_COUNT]) {
+Properties accumulatePropertiesFromMegatexture(SampleData sampleDatas[SAMPLE_COUNT]) {
#if (SAMPLE_COUNT == 1)
return getPropertiesFromMegatexture(sampleDatas[0]);
#else
@@ -1733,7 +1733,7 @@ void main()
for (int stepCount = 0; stepCount < STEP_COUNT_MAX; ++stepCount) {
// Read properties from the megatexture based on the traversal state
- Properties properties = getPropertiesFromMegatexture(sampleDatas);
+ Properties properties = accumulatePropertiesFromMegatexture(sampleDatas);
// Prepare the custom shader inputs
copyPropertiesToMetadata(properties, fragmentInput.metadata);
From f3aaa21f948f829eb6740df0bfcaa7c65e9e1577 Mon Sep 17 00:00:00 2001
From: Jeshurun Hembd
Date: Mon, 8 Aug 2022 22:29:39 -0400
Subject: [PATCH 076/679] Fix broken links to ModelExperimental
---
Source/Scene/GltfVoxelProvider.js | 4 ++--
Source/Scene/VoxelPrimitive.js | 2 +-
Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/Source/Scene/GltfVoxelProvider.js b/Source/Scene/GltfVoxelProvider.js
index e3a3f4f24e2..71b4c213926 100644
--- a/Source/Scene/GltfVoxelProvider.js
+++ b/Source/Scene/GltfVoxelProvider.js
@@ -9,7 +9,7 @@ import Resource from "../Core/Resource.js";
import GltfLoader from "./GltfLoader.js";
import MetadataComponentType from "./MetadataComponentType.js";
import MetadataType from "./MetadataType.js";
-import ModelExperimentalUtility from "./ModelExperimental/ModelExperimentalUtility.js";
+import ModelUtility from "./Model/ModelUtility.js";
import VoxelShapeType from "./VoxelShapeType.js";
/**
@@ -211,7 +211,7 @@ function GltfVoxelProvider(options) {
.then(function (loader) {
const gltf = loader.components;
const node = gltf.nodes[0];
- const modelMatrix = ModelExperimentalUtility.getNodeTransform(node);
+ const modelMatrix = ModelUtility.getNodeTransform(node);
const gltfPrimitive = node.primitives[0];
const voxel = gltfPrimitive.voxel;
const primitiveType = gltfPrimitive.primitiveType;
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 9fa8c17fa01..658415888fc 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -26,7 +26,7 @@ import MetadataType from "./MetadataType.js";
import PolylineCollection from "./PolylineCollection.js";
import VoxelShapeType from "./VoxelShapeType.js";
import VoxelTraversal from "./VoxelTraversal.js";
-import CustomShader from "./ModelExperimental/CustomShader.js";
+import CustomShader from "./Model/CustomShader.js";
import DrawCommand from "../Renderer/DrawCommand.js";
import Pass from "../Renderer/Pass.js";
import RenderState from "../Renderer/RenderState.js";
diff --git a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
index 9d401ea8a67..60b4f2e31f2 100644
--- a/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
+++ b/Source/Widgets/VoxelInspector/VoxelInspectorViewModel.js
@@ -6,7 +6,7 @@ import destroyObject from "../../Core/destroyObject.js";
import HeadingPitchRoll from "../../Core/HeadingPitchRoll.js";
import Matrix3 from "../../Core/Matrix3.js";
import Matrix4 from "../../Core/Matrix4.js";
-import CustomShader from "../../Scene/ModelExperimental/CustomShader.js";
+import CustomShader from "../../Scene/Model/CustomShader.js";
import VoxelShapeType from "../../Scene/VoxelShapeType.js";
import knockout from "../../ThirdParty/knockout.js";
From 23629c047099097d0279d93c9fa651a9d8709010 Mon Sep 17 00:00:00 2001
From: Jeshurun Hembd
Date: Mon, 15 Aug 2022 18:58:14 -0400
Subject: [PATCH 077/679] Fix some of the spec errors
---
Source/Scene/Model/ModelUtility.js | 1 +
Specs/Scene/Cesium3DTilesVoxelProviderSpec.js | 9 +-
Specs/Scene/GltfVoxelProviderSpec.js | 9 +-
Specs/Scene/VoxelBoxShapeSpec.js | 233 ++++++++++++++----
Specs/Scene/VoxelCylinderShapeSpec.js | 8 +-
Specs/Scene/VoxelEllipsoidShapeSpec.js | 28 ++-
Specs/Scene/VoxelTraversalSpec.js | 11 +-
7 files changed, 220 insertions(+), 79 deletions(-)
diff --git a/Source/Scene/Model/ModelUtility.js b/Source/Scene/Model/ModelUtility.js
index 8c374caee8e..ee534bef9db 100644
--- a/Source/Scene/Model/ModelUtility.js
+++ b/Source/Scene/Model/ModelUtility.js
@@ -356,6 +356,7 @@ ModelUtility.supportedExtensions = {
KHR_mesh_quantization: true,
KHR_texture_basisu: true,
KHR_texture_transform: true,
+ EXT_primitive_voxels: true,
};
/**
diff --git a/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js b/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
index 3d62e19ad39..a1dc45defe9 100644
--- a/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
+++ b/Specs/Scene/Cesium3DTilesVoxelProviderSpec.js
@@ -36,7 +36,8 @@ describe(
});
it("constructor works", function () {
- const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const url =
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json";
const provider = new Cesium3DTilesVoxelProvider({
url: url,
});
@@ -77,7 +78,8 @@ describe(
});
it("requestData works for root tile", function () {
- const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const url =
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json";
const provider = new Cesium3DTilesVoxelProvider({
url: url,
});
@@ -110,7 +112,8 @@ describe(
});
it("requestData throws if the provider is not ready", function () {
- const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const url =
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json";
const provider = new Cesium3DTilesVoxelProvider({
url: url,
});
diff --git a/Specs/Scene/GltfVoxelProviderSpec.js b/Specs/Scene/GltfVoxelProviderSpec.js
index adbf9367169..28e0a15a0df 100644
--- a/Specs/Scene/GltfVoxelProviderSpec.js
+++ b/Specs/Scene/GltfVoxelProviderSpec.js
@@ -35,7 +35,7 @@ describe(
it("constructor works", function () {
const url =
- "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/0/0/0/0/tile.gltf";
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf";
const provider = new GltfVoxelProvider({
gltf: url,
});
@@ -74,7 +74,7 @@ describe(
it("requestData works", function () {
const url =
- "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/0/0/0/0/tile.gltf";
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf";
const provider = new GltfVoxelProvider({
gltf: url,
});
@@ -109,7 +109,7 @@ describe(
it("requestData throws for non-root tiles", function () {
const url =
- "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/0/0/0/0/tile.gltf";
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/0/0/0/0/tile.gltf";
const provider = new GltfVoxelProvider({
gltf: url,
});
@@ -124,7 +124,8 @@ describe(
});
it("requestData throws if the provider is not ready", function () {
- const url = "./Data/Cesium3DTiles/Voxel/SimpleWithMetadata/tileset.json";
+ const url =
+ "./Data/Cesium3DTiles/Voxel/VoxelEllipsoid3DTiles/tileset.json";
const provider = new GltfVoxelProvider({
gltf: url,
});
diff --git a/Specs/Scene/VoxelBoxShapeSpec.js b/Specs/Scene/VoxelBoxShapeSpec.js
index c162dd4b896..a9461f403a0 100644
--- a/Specs/Scene/VoxelBoxShapeSpec.js
+++ b/Specs/Scene/VoxelBoxShapeSpec.js
@@ -12,7 +12,7 @@ import {
describe("Scene/VoxelBoxShape", function () {
it("constructs", function () {
const shape = new VoxelBoxShape();
- expect(shape.isVisible).toEqual(false);
+ expect(shape.shapeTransform).toEqual(new Matrix4());
});
it("update works with model matrix", function () {
@@ -51,7 +51,13 @@ describe("Scene/VoxelBoxShape", function () {
Cartesian3.magnitude(scale)
);
- shape.update(modelMatrix, minBounds, maxBounds);
+ const visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
expect(shape.orientedBoundingBox.center).toEqual(
expectedOrientedBoundingBox.center
@@ -63,7 +69,7 @@ describe("Scene/VoxelBoxShape", function () {
expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
expect(shape.boundTransform).toEqual(modelMatrix);
expect(shape.shapeTransform).toEqual(modelMatrix);
- expect(shape.isVisible).toBeTrue();
+ expect(visible).toBeTrue();
});
it("update works with non-default minimum and maximum bounds", function () {
@@ -79,7 +85,13 @@ describe("Scene/VoxelBoxShape", function () {
);
const minBounds = new Cartesian3(-0.75, -0.75, -0.75);
const maxBounds = new Cartesian3(-0.25, -0.25, -0.25);
- shape.update(modelMatrix, minBounds, maxBounds);
+ const visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
const expectedTranslation = new Cartesian3(0.75, 1.75, 2.75);
const expectedScale = new Cartesian3(0.5, 0.75, 1.0);
@@ -103,7 +115,7 @@ describe("Scene/VoxelBoxShape", function () {
expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
expect(shape.boundTransform).toEqual(expectedModelMatrix);
expect(shape.shapeTransform).toEqual(expectedModelMatrix);
- expect(shape.isVisible).toBeTrue();
+ expect(visible).toBeTrue();
});
it("update is visible with zero scale for one component", function () {
@@ -115,6 +127,7 @@ describe("Scene/VoxelBoxShape", function () {
let scale;
let modelMatrix;
+ let visible;
// 0 scale for X
scale = new Cartesian3(0.0, 2.0, 2.0);
@@ -123,8 +136,14 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeTrue();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeTrue();
// 0 scale for Y
scale = Cartesian3.fromElements(2.0, 0.0, 2.0, scale);
@@ -133,8 +152,14 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeTrue();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeTrue();
// 0 scale for Z
scale = Cartesian3.fromElements(2.0, 2.0, 0.0, scale);
@@ -143,8 +168,14 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeTrue();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeTrue();
});
it("update is invisible with zero scale for two or more components", function () {
@@ -156,6 +187,7 @@ describe("Scene/VoxelBoxShape", function () {
let scale;
let modelMatrix;
+ let visible;
// 0 scale for X and Y
scale = new Cartesian3(0.0, 0.0, 2.0);
@@ -164,8 +196,14 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
// 0 scale for X and Z
scale = Cartesian3.fromElements(0.0, 2.0, 0.0, scale);
@@ -174,8 +212,14 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
// 0 scale for Y and Z
scale = Cartesian3.fromElements(2.0, 0.0, 0.0, scale);
@@ -184,8 +228,14 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
// 0 scale for X, Y, and Z
scale = Cartesian3.fromElements(0.0, 0.0, 0.0, scale);
@@ -194,8 +244,14 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
});
it("update is visible with zero bounds for one component", function () {
@@ -214,19 +270,29 @@ describe("Scene/VoxelBoxShape", function () {
let expectedScale;
let actualScale;
let actualTranslation;
+ let visible;
+
+ const clipMinBounds = new Cartesian3(-1.0, -1.0, -1.0);
+ const clipMaxBounds = new Cartesian3(1.0, 1.0, 1.0);
// 0 in X bound
minBounds = new Cartesian3(0.0, -1.0, -1.0);
maxBounds = new Cartesian3(0.0, +1.0, +1.0);
expectedScale = new Cartesian3(0.0, 1.0, 1.0);
- shape.update(modelMatrix, minBounds, maxBounds);
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+ );
actualScale = Matrix4.getScale(shape.shapeTransform, new Cartesian3());
actualTranslation = Matrix4.getTranslation(
shape.shapeTransform,
new Cartesian3()
);
- expect(shape.isVisible).toBeTrue();
+ expect(visible).toBeTrue();
expect(actualScale).toEqual(expectedScale);
expect(actualTranslation).toEqual(translation);
@@ -235,13 +301,19 @@ describe("Scene/VoxelBoxShape", function () {
maxBounds = new Cartesian3(+1.0, 0.0, +1.0);
expectedScale = new Cartesian3(1.0, 0.0, 1.0);
- shape.update(modelMatrix, minBounds, maxBounds);
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+ );
actualScale = Matrix4.getScale(shape.shapeTransform, new Cartesian3());
actualTranslation = Matrix4.getTranslation(
shape.shapeTransform,
new Cartesian3()
);
- expect(shape.isVisible).toBeTrue();
+ expect(visible).toBeTrue();
expect(actualScale).toEqual(expectedScale);
expect(actualTranslation).toEqual(translation);
@@ -250,13 +322,19 @@ describe("Scene/VoxelBoxShape", function () {
maxBounds = new Cartesian3(+1.0, +1.0, 0.0);
expectedScale = new Cartesian3(1.0, 1.0, 0.0);
- shape.update(modelMatrix, minBounds, maxBounds);
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+ );
actualScale = Matrix4.getScale(shape.shapeTransform, new Cartesian3());
actualTranslation = Matrix4.getTranslation(
shape.shapeTransform,
new Cartesian3()
);
- expect(shape.isVisible).toBeTrue();
+ expect(visible).toBeTrue();
expect(actualScale).toEqual(expectedScale);
expect(actualTranslation).toEqual(translation);
});
@@ -274,30 +352,55 @@ describe("Scene/VoxelBoxShape", function () {
let minBounds;
let maxBounds;
+ let visible;
// 0 in X and Y bounds
minBounds = new Cartesian3(0.0, 0.0, -1.0);
maxBounds = new Cartesian3(0.0, 0.0, +1.0);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
// 0 in X and Z bounds
minBounds = new Cartesian3(0.0, -1.0, 0.0);
maxBounds = new Cartesian3(0.0, +1.0, 0.0);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
// 0 in Y and Z bounds
minBounds = new Cartesian3(-1.0, 0.0, 0.0);
maxBounds = new Cartesian3(+1.0, 0.0, 0.0);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
// 0 in X, Y, and Z bounds
minBounds = new Cartesian3(0.0, 0.0, 0.0);
maxBounds = new Cartesian3(0.0, 0.0, 0.0);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
+ expect(visible).toBeFalse();
});
it("update is invisible when minimum bounds exceed maximum bounds", function () {
@@ -311,26 +414,48 @@ describe("Scene/VoxelBoxShape", function () {
scale
);
+ let visible;
+
let minBounds;
let maxBounds;
+ const clipMinBounds = new Cartesian3(-1.0, -1.0, -1.0);
+ const clipMaxBounds = new Cartesian3(2.0, 2.0, 2.0);
// Exceeds X
minBounds = new Cartesian3(+2.0, -1.0, -1.0);
maxBounds = new Cartesian3(+1.0, +1.0, +1.0);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+ );
+ expect(visible).toBeFalse();
// Exceeds Y
minBounds = new Cartesian3(-1.0, +2.0, -1.0);
maxBounds = new Cartesian3(+1.0, +1.0, +1.0);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+ );
+ expect(visible).toBeFalse();
// Exceeds Z
minBounds = new Cartesian3(-1.0, -1.0, +2.0);
maxBounds = new Cartesian3(+1.0, +1.0, +1.0);
- shape.update(modelMatrix, minBounds, maxBounds);
- expect(shape.isVisible).toBeFalse();
+ visible = shape.update(
+ modelMatrix,
+ minBounds,
+ maxBounds,
+ clipMinBounds,
+ clipMaxBounds
+ );
+ expect(visible).toBeFalse();
});
it("update throws with no model matrix parameter", function () {
@@ -353,10 +478,17 @@ describe("Scene/VoxelBoxShape", function () {
rotation,
scale
);
+ const minBounds = VoxelBoxShape.DefaultMinBounds;
const maxBounds = VoxelBoxShape.DefaultMaxBounds;
expect(function () {
- return shape.update(modelMatrix, undefined, maxBounds);
+ return shape.update(
+ modelMatrix,
+ undefined,
+ maxBounds,
+ minBounds,
+ maxBounds
+ );
}).toThrowDeveloperError();
});
@@ -371,9 +503,16 @@ describe("Scene/VoxelBoxShape", function () {
scale
);
const minBounds = VoxelBoxShape.DefaultMinBounds;
+ const maxBounds = VoxelBoxShape.DefaultMaxBounds;
expect(function () {
- return shape.update(modelMatrix, minBounds, undefined);
+ return shape.update(
+ modelMatrix,
+ minBounds,
+ undefined,
+ minBounds,
+ maxBounds
+ );
}).toThrowDeveloperError();
});
@@ -389,7 +528,7 @@ describe("Scene/VoxelBoxShape", function () {
);
const minBounds = VoxelBoxShape.DefaultMinBounds;
const maxBounds = VoxelBoxShape.DefaultMaxBounds;
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const tileLevel = 0;
const tileX = 0;
@@ -419,7 +558,7 @@ describe("Scene/VoxelBoxShape", function () {
);
const minBounds = VoxelBoxShape.DefaultMinBounds;
const maxBounds = VoxelBoxShape.DefaultMaxBounds;
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const expectedScale = new Cartesian3(0.5, 0.5, 0.5);
let expectedTranslation;
@@ -577,7 +716,7 @@ describe("Scene/VoxelBoxShape", function () {
);
const minBounds = VoxelBoxShape.DefaultMinBounds;
const maxBounds = VoxelBoxShape.DefaultMaxBounds;
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const tileLevel = 0;
const tileX = 0;
@@ -637,7 +776,7 @@ describe("Scene/VoxelBoxShape", function () {
);
const minBounds = VoxelBoxShape.DefaultMinBounds;
const maxBounds = VoxelBoxShape.DefaultMaxBounds;
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const tileLevel = 0;
const tileX = 0;
@@ -667,7 +806,7 @@ describe("Scene/VoxelBoxShape", function () {
);
const minBounds = VoxelBoxShape.DefaultMinBounds;
const maxBounds = VoxelBoxShape.DefaultMaxBounds;
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const dimensions = new Cartesian3(32, 32, 16);
const stepSize = shape.computeApproximateStepSize(dimensions);
@@ -687,7 +826,7 @@ describe("Scene/VoxelBoxShape", function () {
);
const minBounds = VoxelBoxShape.DefaultMinBounds;
const maxBounds = VoxelBoxShape.DefaultMaxBounds;
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
expect(function () {
return shape.computeApproximateStepSize(undefined);
diff --git a/Specs/Scene/VoxelCylinderShapeSpec.js b/Specs/Scene/VoxelCylinderShapeSpec.js
index 481f3a18c6c..2486f04f483 100644
--- a/Specs/Scene/VoxelCylinderShapeSpec.js
+++ b/Specs/Scene/VoxelCylinderShapeSpec.js
@@ -14,7 +14,7 @@ describe(
function () {
it("constructs", function () {
const shape = new VoxelCylinderShape();
- expect(shape.isVisible).toEqual(false);
+ expect(shape.shapeTransform).toEqual(new Matrix4());
});
it("update works with model matrix", function () {
@@ -37,7 +37,7 @@ describe(
const minBounds = VoxelCylinderShape.DefaultMinBounds;
const maxBounds = VoxelCylinderShape.DefaultMaxBounds;
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const expectedOrientedBoundingBox = new OrientedBoundingBox(
translation,
@@ -92,7 +92,7 @@ describe(
const maxAngle = 0.0;
const minBounds = new Cartesian3(minRadius, minHeight, minAngle);
const maxBounds = new Cartesian3(maxRadius, maxHeight, maxAngle);
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const expectedMinX = translation.x - maxRadius * scale.x;
const expectedMaxX = translation.x + maxRadius * scale.x;
@@ -172,7 +172,7 @@ describe(
defaultMaxBounds.y,
maxAngle
);
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
const expectedScale = new Cartesian3(0.5, 1.0, 1.0);
const expectedTranslation = new Cartesian3(-0.5, 0.0, 0.0);
diff --git a/Specs/Scene/VoxelEllipsoidShapeSpec.js b/Specs/Scene/VoxelEllipsoidShapeSpec.js
index 57c55b57f31..9d2736ca341 100644
--- a/Specs/Scene/VoxelEllipsoidShapeSpec.js
+++ b/Specs/Scene/VoxelEllipsoidShapeSpec.js
@@ -12,7 +12,7 @@ import {
describe("Scene/VoxelEllipsoidShape", function () {
it("constructs", function () {
const shape = new VoxelEllipsoidShape();
- expect(shape.isVisible).toEqual(false);
+ expect(shape.shapeTransform).toEqual(new Matrix4());
});
it("update works with model matrix", function () {
@@ -28,8 +28,16 @@ describe("Scene/VoxelEllipsoidShape", function () {
scale
);
- const minBounds = VoxelEllipsoidShape.DefaultMinBounds;
- const maxBounds = VoxelEllipsoidShape.DefaultMaxBounds;
+ const minBounds = new Cartesian3(
+ -CesiumMath.PI,
+ -CesiumMath.PI_OVER_TWO,
+ 0.0
+ );
+ const maxBounds = new Cartesian3(
+ +CesiumMath.PI,
+ +CesiumMath.PI_OVER_TWO,
+ 100000
+ );
const maxHeight = maxBounds.z;
const expectedOrientedBoundingBox = new OrientedBoundingBox(
@@ -52,14 +60,14 @@ describe("Scene/VoxelEllipsoidShape", function () {
new BoundingSphere()
);
- shape.update(modelMatrix, minBounds, maxBounds);
+ shape.update(modelMatrix, minBounds, maxBounds, minBounds, maxBounds);
expect(shape.orientedBoundingBox.center).toEqual(
expectedOrientedBoundingBox.center
);
expect(shape.orientedBoundingBox.halfAxes).toEqualEpsilon(
expectedOrientedBoundingBox.halfAxes,
- CesiumMath.EPSILON12
+ CesiumMath.EPSILON9
);
expect(shape.boundingSphere).toEqual(expectedBoundingSphere);
@@ -69,10 +77,7 @@ describe("Scene/VoxelEllipsoidShape", function () {
expect(
Matrix4.getMatrix3(shape.boundTransform, new Matrix3())
- ).toEqualEpsilon(
- expectedOrientedBoundingBox.halfAxes,
- CesiumMath.EPSILON12
- );
+ ).toEqualEpsilon(expectedOrientedBoundingBox.halfAxes, CesiumMath.EPSILON9);
expect(
Matrix4.getTranslation(shape.shapeTransform, new Cartesian3())
@@ -80,10 +85,7 @@ describe("Scene/VoxelEllipsoidShape", function () {
expect(
Matrix4.getMatrix3(shape.shapeTransform, new Matrix3())
- ).toEqualEpsilon(
- expectedOrientedBoundingBox.halfAxes,
- CesiumMath.EPSILON12
- );
+ ).toEqualEpsilon(expectedOrientedBoundingBox.halfAxes, CesiumMath.EPSILON9);
// expect(shape.boundTransform).toEqual(modelMatrix);
// expect(shape.shapeTransform).toEqual(modelMatrix);
diff --git a/Specs/Scene/VoxelTraversalSpec.js b/Specs/Scene/VoxelTraversalSpec.js
index de7f2b2e48a..b7f5619da5a 100644
--- a/Specs/Scene/VoxelTraversalSpec.js
+++ b/Specs/Scene/VoxelTraversalSpec.js
@@ -109,9 +109,7 @@ describe(
const keyframeCount = 1;
const voxelDimensions = provider.voxelDimensions;
const neighborEdgeCount = provider.neighborEdgeCount;
- const channelCount = provider.channelCount;
- const minimumValues = provider.minimumValues;
- const maximumValues = provider.maximumValues;
+ // TODO: not available from the dummy provider (nor from the real providers)
const datatypes = provider.datatypes;
const textureMemory = 500;
@@ -131,13 +129,10 @@ describe(
return new VoxelTraversal(
primitive,
context,
- keyframeCount,
voxelDimensions,
- neighborEdgeCount,
- channelCount,
- minimumValues,
- maximumValues,
datatypes,
+ datatypes,
+ keyframeCount,
textureMemory
);
});
From d7930089e0c82fff226d5ba8dda9941117d4250a Mon Sep 17 00:00:00 2001
From: Jeshurun Hembd
Date: Mon, 15 Aug 2022 19:13:44 -0400
Subject: [PATCH 078/679] Restrict to Box shapes for debug
---
Source/Shaders/VoxelFS.glsl | 928 +-----------------------------------
1 file changed, 1 insertion(+), 927 deletions(-)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 5ea5529cd17..1aaa42f62ef 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1,142 +1,3 @@
-/*
-Don't delete this comment!
-Some shader code is dynamically generated in VoxelPrimitive.js to support custom shaders with arbitrary metadata.
-Below is an example of how this code might look. Properties like "temperature" and "direction" are just examples.
-
-// Defines
-#define PROPERTY_COUNT ###
-#define SAMPLE_COUNT ###
-#define SHAPE_BOX
-#define SHAPE_ELLIPSOID
-#define SHAPE_CYLINDER
-#define MEGATEXTURE_2D
-#define MEGATEXTURE_3D
-#define DEPTH_TEST
-#define DEPTH_INTERSECTION_INDEX ###
-#define INTERSECTION_COUNT ###
-#define JITTER
-#define NEAREST_SAMPLING
-#define STATISTICS
-#define PADDING
-#define PICKING
-#define CLIPPING_PLANES
-#define CLIPPING_PLANES_UNION
-#define CLIPPING_PLANES_COUNT
-#define CLIPPING_PLANES_INTERSECTION_INDEX
-
-// Uniforms
-uniform sampler2D u_megatextureTextures[PROPERTY_COUNT];
-
-// Structs
-struct PropertyStatistics_temperature {
- float min;
- float max;
-};
-struct PropertyStatistics_direction {
- vec3 min;
- vec3 max;
-};
-struct Statistics {
- PropertyStatistics_temperature temperature;
- PropertyStatistics_direction direction;
-};
-struct Metadata {
- Statistics statistics;
- float temperature;
- vec3 direction;
-};
-struct VoxelProperty_temperature {
- vec3 partialDerivativeLocal;
- vec3 partialDerivativeWorld;
- vec3 partialDerivativeView;
- bool partialDerivativeValid;
-};
-struct VoxelProperty_direction {
- mat3 partialDerivativeLocal;
- mat3 partialDerivativeWorld;
- mat3 partialDerivativeView;
- bool partialDerivativeValid;
-};
-struct Voxel {
- VoxelProperty_temperature temperature;
- VoxelProperty_direction direction;
- vec3 positionEC;
- vec3 positionUv;
- vec3 positionShapeUv;
- vec3 positionUvLocal;
- vec3 viewDirUv;
- vec3 viewDirWorld;
- float travelDistance;
-};
-struct FragmentInput {
- Metadata metadata;
- Voxel voxel;
-};
-struct Properties {
- // This struct is similar to Metadata but is not part of the custom shader API and
- // is intended to be used internally as a lightweight way to pass around properties.
- float temperature;
- vec3 direction;
-};
-
-// Functions
-Properties clearProperties() {
- Properties properties;
- properties.temperature = 0.0;
- properties.direction = vec3(0.0);
- return properties;
-}
-Properties sumProperties(Properties propertiesA, Properties propertiesB) {
- Properties properties;
- properties.temperature = propertiesA.temperature + propertiesB.temperature;
- properties.direction = propertiesA.direction + propertiesB.direction;
- return properties;
-}
-Properties scaleProperties(Properties properties, float scale) {
- Properties scaledProperties = properties;
- scaledProperties.temperature *= scale;
- scaledProperties.direction *= scale;
- return scaledProperties;
-}
-Properties mixProperties(Properties propertiesA, Properties propertiesB, float mixFactor) {
- Properties properties;
- properties.temperature = mix(propertiesA.temperature, propertiesB.temperature, mixFactor);
- properties.direction = mix(propertiesA.direction, propertiesB.direction, mixFactor);
- return properties;
-}
-void copyPropertiesToMetadata(in Properties properties, inout Metadata metadata) {
- metadata.temperature = properties.temperature;
- metadata.direction = properties.direction;
-}
-void setStatistics(inout Statistics statistics) {
- // Assume the "direction" property has no min/max
- statistics.temperature.min = 20.0;
- statistics.temperature.max = 50.0;
-}
-Properties getPropertiesFromMegatextureAtUv(vec2 texcoord) {
- #if defined(MEGATEXTURE_2D)
- Properties properties;
- properties.temperature = texture2D(u_megatextureTextures[0], texcoord).r;
- properties.direction = texture2D(u_megatextureTextures[1], texcoord).rgb;
- return properties;
- #else
- Properties properties;
- properties.temperature = texture3D(u_megatextureTextures[0], texcoord).r;
- properties.direction = texture3D(u_megatextureTextures[1], texcoord).rgb;
- return properties;
- #endif
-}
-void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
- vec3 direction = fsInput.metadata.direction;
- float temperature = fsInput.metadata.temperature;
- float minTemperature = fsInput.metadata.statistics.temperature.min;
- float maxTemperature = fsInput.metadata.statistics.temperature.max;
-
- material.diffuse = abs(direction);
- material.alpha = (temperature - minTemperature) / (maxTemperature - minTemperature);
-}
-*/
-
// These octree flags must be in sync with GpuOctreeFlag in VoxelTraversal.js
#define OCTREE_FLAG_INTERNAL 0
#define OCTREE_FLAG_LEAF 1
@@ -233,125 +94,6 @@ uniform float u_stepSize;
#endif
#endif
-#if defined(SHAPE_ELLIPSOID)
- /* Ellipsoid defines:
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY
- #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE
- #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO
- #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF
- #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO
- #define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE
- #define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO
- #define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN
- #define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO
- #define ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN
- #define ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO
- #define ELLIPSOID_IS_SPHERE
- #define ELLIPSOID_INTERSECTION_INDEX_LONGITUDE
- #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX
- #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN
- #define ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX
- #define ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN
- */
-
- // Ellipsoid uniforms
- uniform vec3 u_ellipsoidRadiiUv; // [0,1]
- #if !defined(ELLIPSOID_IS_SPHERE)
- uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
- #endif
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE)
- uniform vec2 u_ellipsoidRenderLongitudeMinMax;
- #endif
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY) || defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
- uniform vec3 u_ellipsoidShapeUvLongitudeMinMaxMid;
- #endif
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
- uniform vec2 u_ellipsoidUvToShapeUvLongitude; // x = scale, y = offset
- #endif
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
- uniform vec2 u_ellipsoidUvToShapeUvLatitude; // x = scale, y = offset
- #endif
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF)
- uniform vec2 u_ellipsoidRenderLatitudeCosSqrHalfMinMax;
- #endif
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN) && !defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
- uniform float u_ellipsoidInverseHeightDifferenceUv;
- uniform vec2 u_ellipseInnerRadiiUv; // [0,1]
- #endif
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN)
- uniform float u_ellipsoidInverseInnerScaleUv;
- #endif
-#endif
-
-#if defined(SHAPE_CYLINDER)
- /* Cylinder defines:
- #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN
- #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX
- #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT
- #define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT
- #define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT
- #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE
- #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF
- #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF
- #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF
- #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO
-
- #define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS
- #define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT
- #define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT
- #define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT
- #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE
- #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO
- #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY
- #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY
- #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED
-
- #define CYLINDER_INTERSECTION_INDEX_RADIUS_MAX
- #define CYLINDER_INTERSECTION_INDEX_RADIUS_MIN
- #define CYLINDER_INTERSECTION_INDEX_ANGLE
- */
-
- // Cylinder uniforms
- #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT)
- uniform vec3 u_cylinderUvToRenderBoundsScale;
- uniform vec3 u_cylinderUvToRenderBoundsTranslate;
- #endif
- #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN) && !defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
- uniform float u_cylinderUvToRenderRadiusMin;
- #endif
- #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE)
- uniform vec2 u_cylinderRenderAngleMinMax;
- #endif
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
- uniform vec2 u_cylinderUvToShapeUvRadius; // x = scale, y = offset
- #endif
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
- uniform vec2 u_cylinderUvToShapeUvHeight; // x = scale, y = offset
- #endif
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
- uniform vec2 u_cylinderUvToShapeUvAngle; // x = scale, y = offset
- #endif
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
- uniform vec2 u_cylinderShapeUvAngleMinMax;
- #endif
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
- uniform float u_cylinderShapeUvAngleEmptyMid;
- #endif
-#endif
-
// --------------------------------------------------------
// Misc math
// --------------------------------------------------------
@@ -369,9 +111,6 @@ float hash(vec2 p)
}
#endif
-float signNoZero(float v) {
- return (v < 0.0) ? -1.0 : 1.0;
-}
int intMod(int a, int b) {
return a - (b * (a / b));
}
@@ -448,38 +187,6 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
}
#endif
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF))
-vec2 intersectZPlane(Ray ray)
-{
- float o = ray.pos.z;
- float d = ray.dir.z;
- float t = -o / d;
- float s = sign(o);
-
- if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
- else return vec2(-INF_HIT, t);
-}
-#endif
-
-#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO))
-vec4 intersectHalfPlane(Ray ray, float angle) {
- vec2 o = ray.pos.xy;
- vec2 d = ray.dir.xy;
- vec2 planeDirection = vec2(cos(angle), sin(angle));
- vec2 planeNormal = vec2(planeDirection.y, -planeDirection.x);
-
- float a = dot(o, planeNormal);
- float b = dot(d, planeNormal);
- float t = -a / b;
-
- vec2 p = o + t * d;
- bool outside = dot(p, planeDirection) < 0.0;
- if (outside) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
-
- return vec4(-INF_HIT, t, t, +INF_HIT);
-}
-#endif
-
#if defined(CLIPPING_PLANES)
// Plane is in Hessian Normal Form
vec2 intersectPlane(Ray ray, vec4 plane) {
@@ -500,343 +207,6 @@ vec2 intersectPlane(Ray ray, vec4 plane) {
}
#endif
-#if (defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF))) || (defined(SHAPE_CYLINDER) && (defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF) || defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF)))
-vec2 intersectHalfSpace(Ray ray, float angle)
-{
- vec2 o = ray.pos.xy;
- vec2 d = ray.dir.xy;
- vec2 n = vec2(sin(angle), -cos(angle));
-
- float a = dot(o, n);
- float b = dot(d, n);
- float t = -a / b;
- float s = sign(a);
-
- if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
- else return vec2(-INF_HIT, t);
-}
-#endif
-
-#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF))
-vec2 intersectRegularWedge(Ray ray, float minAngle, float maxAngle)
-{
- vec2 o = ray.pos.xy;
- vec2 d = ray.dir.xy;
- vec2 n1 = vec2(sin(minAngle), -cos(minAngle));
- vec2 n2 = vec2(-sin(maxAngle), cos(maxAngle));
-
- float a1 = dot(o, n1);
- float a2 = dot(o, n2);
- float b1 = dot(d, n1);
- float b2 = dot(d, n2);
-
- float t1 = -a1 / b1;
- float t2 = -a2 / b2;
- float s1 = sign(a1);
- float s2 = sign(a2);
-
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
- float smin = tmin == t1 ? s1 : s2;
- float smax = tmin == t1 ? s2 : s1;
-
- bool e = tmin >= 0.0;
- bool f = tmax >= 0.0;
- bool g = smin >= 0.0;
- bool h = smax >= 0.0;
-
- if (e != g && f == h) return vec2(tmin, tmax);
- else if (e == g && f == h) return vec2(-INF_HIT, tmin);
- else if (e != g && f != h) return vec2(tmax, +INF_HIT);
- else return vec2(NO_HIT);
-}
-#endif
-
-#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF))
-vec4 intersectFlippedWedge(Ray ray, float minAngle, float maxAngle)
-{
- vec2 planeIntersectMin = intersectHalfSpace(ray, minAngle);
- vec2 planeIntersectMax = intersectHalfSpace(ray, maxAngle + czm_pi);
- return vec4(planeIntersectMin, planeIntersectMax);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID)
-vec2 intersectUnitSphere(Ray ray)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
-
- float b = dot(d, o);
- float c = dot(o, o) - 1.0;
- float det = b * b - c;
-
- if (det < 0.0) {
- return vec2(NO_HIT);
- }
-
- det = sqrt(det);
- float t1 = -b - det;
- float t2 = -b + det;
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
-
- return vec2(tmin, tmax);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID)
-vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
-
- float a = dot(d, d);
- float b = dot(d, o);
- float c = dot(o, o) - 1.0;
- float det = b * b - a * c;
-
- if (det < 0.0) {
- return vec2(NO_HIT);
- }
-
- det = sqrt(det);
- float t1 = (-b - det) / a;
- float t2 = (-b + det) / a;
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
-
- return vec2(tmin, tmax);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE)
-vec2 intersectDoubleEndedCone(Ray ray, float cosSqrHalfAngle)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
- float a = d.z * d.z - dot(d, d) * cosSqrHalfAngle;
- float b = d.z * o.z - dot(o, d) * cosSqrHalfAngle;
- float c = o.z * o.z - dot(o, o) * cosSqrHalfAngle;
- float det = b * b - a * c;
-
- if (det < 0.0) {
- return vec2(NO_HIT);
- }
-
- det = sqrt(det);
- float t1 = (-b - det) / a;
- float t2 = (-b + det) / a;
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
- return vec2(tmin, tmax);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF))
-vec4 intersectFlippedCone(Ray ray, float cosSqrHalfAngle) {
- vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
-
- if (intersect.x == NO_HIT) {
- return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
- }
-
- vec3 o = ray.pos;
- vec3 d = ray.dir;
- float tmin = intersect.x;
- float tmax = intersect.y;
- float zmin = o.z + tmin * d.z;
- float zmax = o.z + tmax * d.z;
-
- // One interval
- if (zmin < 0.0 && zmax < 0.0) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
- else if (zmin < 0.0) return vec4(-INF_HIT, tmax, NO_HIT, NO_HIT);
- else if (zmax < 0.0) return vec4(tmin, +INF_HIT, NO_HIT, NO_HIT);
- // Two intervals
- else return vec4(-INF_HIT, tmin, tmax, +INF_HIT);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF))
-vec2 intersectRegularCone(Ray ray, float cosSqrHalfAngle) {
- vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
-
- if (intersect.x == NO_HIT) {
- return vec2(NO_HIT);
- }
-
- vec3 o = ray.pos;
- vec3 d = ray.dir;
- float tmin = intersect.x;
- float tmax = intersect.y;
- float zmin = o.z + tmin * d.z;
- float zmax = o.z + tmax * d.z;
-
- if (zmin < 0.0 && zmax < 0.0) return vec2(NO_HIT);
- else if (zmin < 0.0) return vec2(tmax, +INF_HIT);
- else if (zmax < 0.0) return vec2(-INF_HIT, tmin);
- else return vec2(tmin, tmax);
-}
-#endif
-
-#if defined(SHAPE_CYLINDER)
-vec2 intersectUnitCylinder(Ray ray)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
-
- float a = dot(d.xy, d.xy);
- float b = dot(o.xy, d.xy);
- float c = dot(o.xy, o.xy) - 1.0;
- float det = b * b - a * c;
-
- if (det < 0.0) {
- return vec2(NO_HIT);
- }
-
- det = sqrt(det);
- float ta = (-b - det) / a;
- float tb = (-b + det) / a;
- float t1 = min(ta, tb);
- float t2 = max(ta, tb);
-
- float z1 = o.z + t1 * d.z;
- float z2 = o.z + t2 * d.z;
-
- if (abs(z1) >= 1.0)
- {
- float tCap = (sign(z1) - o.z) / d.z;
- t1 = abs(b + a * tCap) < det ? tCap : NO_HIT;
- }
-
- if (abs(z2) >= 1.0)
- {
- float tCap = (sign(z2) - o.z) / d.z;
- t2 = abs(b + a * tCap) < det ? tCap : NO_HIT;
- }
-
- return vec2(t1, t2);
-}
-#endif
-
-#if defined(SHAPE_CYLINDER)
-vec2 intersectUnitCircle(Ray ray) {
- vec3 o = ray.pos;
- vec3 d = ray.dir;
-
- float t = -o.z / d.z;
- vec2 zPlanePos = o.xy + d.xy * t;
- float distSqr = dot(zPlanePos, zPlanePos);
-
- if (distSqr > 1.0) {
- return vec2(NO_HIT);
- }
-
- return vec2(t, t);
-}
-#endif
-
-#if defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN)
-vec2 intersectInfiniteUnitCylinder(Ray ray)
-{
- vec3 o = ray.pos;
- vec3 d = ray.dir;
-
- float a = dot(d.xy, d.xy);
- float b = dot(o.xy, d.xy);
- float c = dot(o.xy, o.xy) - 1.0;
- float det = b * b - a * c;
-
- if (det < 0.0) {
- return vec2(NO_HIT);
- }
-
- det = sqrt(det);
- float t1 = (-b - det) / a;
- float t2 = (-b + det) / a;
- float tmin = min(t1, t2);
- float tmax = max(t1, t2);
-
- return vec2(tmin, tmax);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID)
-// robust iterative solution without trig functions
-// https://github.com/0xfaded/ellipse_demo/issues/1
-// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
-// Pro: Good when radii.x ~= radii.y
-// Con: Breaks at pos.x ~= 0.0, especially inside the ellipse
-// Con: Inaccurate with exterior points and thin ellipses
-float ellipseDistanceIterative (vec2 pos, vec2 radii) {
- vec2 p = abs(pos);
- vec2 invRadii = 1.0 / radii;
- vec2 a = vec2(1.0, -1.0) * (radii.x * radii.x - radii.y * radii.y) * invRadii;
- vec2 t = vec2(0.70710678118); // sqrt(2) / 2
- vec2 v = radii * t;
-
- const int iterations = 3;
- for (int i = 0; i < iterations; ++i) {
- vec2 e = a * pow(t, vec2(3.0));
- vec2 q = normalize(p - e) * length(v - e);
- t = normalize((q + e) * invRadii);
- v = radii * t;
- }
- return length(v * sign(pos) - pos) * sign(p.y - v.y);
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID)
-// From: https://www.shadertoy.com/view/4sS3zz
-// Pro: Accurate in most cases
-// Con: Breaks if radii.x ~= radii.y
-float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
- vec2 p = pos;
- vec2 ab = radii;
-
- p = abs(p);
- if (p.x > p.y) {
- p = p.yx;
- ab = ab.yx;
- }
-
- float l = ab.y * ab.y - ab.x * ab.x;
- float m = ab.x * p.x / l;
- float n = ab.y * p.y / l;
- float m2 = m * m;
- float n2 = n * n;
- float c = (m2 + n2 - 1.0) / 3.0;
- float c3 = c * c * c;
- float d = c3 + m2 * n2;
- float q = d + m2 * n2;
- float g = m + m * n2;
-
- float co;
-
- if (d < 0.0) {
- float h = acos(q / c3) / 3.0;
- float s = cos(h) + 2.0;
- float t = sin(h) * sqrt(3.0);
- float rx = sqrt(m2 - c * (s + t));
- float ry = sqrt(m2 - c * (s - t));
- co = ry + sign(l) * rx + abs(g) / (rx * ry);
- } else {
- float h = 2.0 * m * n * sqrt(d);
- float s = signNoZero(q + h) * pow(abs(q + h), 1.0 / 3.0);
- float t = signNoZero(q - h) * pow(abs(q - h), 1.0 / 3.0);
- float rx = -(s + t) - c * 4.0 + 2.0 * m2;
- float ry = (s - t) * sqrt(3.0);
- float rm = sqrt(rx * rx + ry * ry);
- co = ry / sqrt(rm - rx) + 2.0 * g / rm;
- }
-
- co = (co - m) / 2.0;
- float si = sqrt(max(1.0 - co * co, 0.0));
- vec2 r = ab * vec2(co, si);
- return length(r - p) * signNoZero(p.y - r.y);
-}
-#endif
-
struct Intersections {
// Don't access these member variables directly - call the functions instead.
@@ -1008,302 +378,6 @@ vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
}
#endif
-#if defined(SHAPE_ELLIPSOID)
-void intersectShape(in Ray ray, inout Intersections ix) {
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir *= 2.0;
-
- // Outer ellipsoid
- vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX, outerIntersect);
-
- // Exit early if the outer ellipsoid was missed.
- if (outerIntersect.x == NO_HIT) {
- return;
- }
-
- // Inner ellipsoid
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
- // When the ellipsoid is perfectly thin it's necessary to sandwich the
- // inner ellipsoid intersection inside the outer ellipsoid intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the ellipsoid to be invisible because it will think the ray
- // is still inside the inner (negative) ellipsoid after exiting the
- // outer (positive) ellipsoid.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If initializeIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
- setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
- setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
- setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
- setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN)
- Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
- vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN, innerIntersect);
- #endif
-
- // Flip the ray because the intersection function expects a cone growing towards +Z.
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF)
- Ray flippedRay = ray;
- flippedRay.dir.z *= -1.0;
- flippedRay.pos.z *= -1.0;
- #endif
-
- // Bottom cone
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF)
- vec2 bottomConeIntersection = intersectRegularCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection);
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF)
- vec2 bottomConeIntersection = intersectZPlane(flippedRay);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection);
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF)
- vec4 bottomConeIntersection = intersectFlippedCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 0, bottomConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 1, bottomConeIntersection.zw);
- #endif
-
- // Top cone
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF)
- vec4 topConeIntersection = intersectFlippedCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 0, topConeIntersection.xy);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 1, topConeIntersection.zw);
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF)
- vec2 topConeIntersection = intersectZPlane(ray);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection);
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF)
- vec2 topConeIntersection = intersectRegularCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection);
- #endif
-
- // Wedge
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)
- vec4 wedgeIntersect = intersectHalfPlane(ray, u_ellipsoidRenderLongitudeMinMax.x);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 1, wedgeIntersect.zw);
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF)
- vec2 wedgeIntersect = intersectRegularWedge(ray, u_ellipsoidRenderLongitudeMinMax.x, u_ellipsoidRenderLongitudeMinMax.y);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE, wedgeIntersect);
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF)
- vec2 wedgeIntersect = intersectHalfSpace(ray, u_ellipsoidRenderLongitudeMinMax.x);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE, wedgeIntersect);
- #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF)
- vec4 wedgeIntersect = intersectFlippedWedge(ray, u_ellipsoidRenderLongitudeMinMax.x, u_ellipsoidRenderLongitudeMinMax.y);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 1, wedgeIntersect.zw);
- #endif
-}
-#endif
-
-#if defined(SHAPE_ELLIPSOID)
-vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
- // Compute position and normal.
- // Convert positionUv [0,1] to local space [-1,+1] to "normalized" cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height).
- // A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
- vec3 positionLocal = positionUv * 2.0 - 1.0;
- #if defined(ELLIPSOID_IS_SPHERE)
- vec3 posEllipsoid = positionLocal * u_ellipsoidRadiiUv.x;
- vec3 normal = normalize(posEllipsoid);
- #else
- vec3 posEllipsoid = positionLocal * u_ellipsoidRadiiUv;
- vec3 normal = normalize(posEllipsoid * u_ellipsoidInverseRadiiSquaredUv); // geodetic surface normal
- #endif
-
- // Compute longitude
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)
- float longitude = 1.0;
- #else
- float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
-
- // Correct the angle when max < min
- // Technically this should compare against min longitude - but it has precision problems so compare against the middle of empty space.
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
- longitude += float(longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z);
- #endif
-
- // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY)
- longitude = longitude > u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.x : longitude;
- #endif
- #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY)
- longitude = longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.y : longitude;
- #endif
-
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
- longitude = longitude * u_ellipsoidUvToShapeUvLongitude.x + u_ellipsoidUvToShapeUvLongitude.y;
- #endif
- #endif
-
- // Compute latitude
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO)
- float latitude = 1.0;
- #else
- float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi;
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
- latitude = latitude * u_ellipsoidUvToShapeUvLatitude.x + u_ellipsoidUvToShapeUvLatitude.y;
- #endif
- #endif
-
- // Compute height
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
- // TODO: This breaks down when minBounds == maxBounds. To fix it, this
- // function would have to know if ray is intersecting the front or back of the shape
- // and set the shape space position to 1 (front) or 0 (back) accordingly.
- float height = 1.0;
- #else
- #if defined(ELLIPSOID_IS_SPHERE)
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN)
- float height = (length(posEllipsoid) - u_ellipseInnerRadiiUv.x) * u_ellipsoidInverseHeightDifferenceUv;
- #else
- float height = length(posEllipsoid);
- #endif
- #else
- #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN)
- // Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84).
- // This is an optimization so that math can be done with ellipses instead of ellipsoids.
- vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
- float height = ellipseDistanceIterative(posEllipse, u_ellipseInnerRadiiUv) * u_ellipsoidInverseHeightDifferenceUv;
- #else
- // TODO: this is probably not correct
- float height = length(posEllipsoid);
- #endif
- #endif
- #endif
-
- return vec3(longitude, latitude, height);
-}
-#endif
-
-#if defined(SHAPE_CYLINDER)
-void intersectShape(Ray ray, inout Intersections ix)
-{
- #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT)
- ray.pos = ray.pos * u_cylinderUvToRenderBoundsScale + u_cylinderUvToRenderBoundsTranslate;
- ray.dir *= u_cylinderUvToRenderBoundsScale;
- #else
- // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
- // Direction is scaled as well to be in sync with position.
- ray.pos = ray.pos * 2.0 - 1.0;
- ray.dir *= 2.0;
- #endif
-
- #if defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT)
- vec2 outerIntersect = intersectUnitCircle(ray);
- #else
- vec2 outerIntersect = intersectUnitCylinder(ray);
- #endif
-
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MAX, outerIntersect);
-
- if (outerIntersect.x == NO_HIT) {
- return;
- }
-
- #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
- // When the cylinder is perfectly thin it's necessary to sandwich the
- // inner cylinder intersection inside the outer cylinder intersection.
-
- // Without this special case,
- // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
- // [outerMin, innerMin, outerMax, innerMax] which will cause the back
- // side of the cylinder to be invisible because it will think the ray
- // is still inside the inner (negative) cylinder after exiting the
- // outer (positive) cylinder.
-
- // With this special case,
- // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
- // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
-
- // Note: If initializeIntersections() changes its sorting function
- // from bubble sort to something else, this code may need to change.
- vec2 innerIntersect = intersectInfiniteUnitCylinder(ray);
- setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
- setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
- setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
- setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
- #elif defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN)
- Ray innerRay = Ray(ray.pos * u_cylinderUvToRenderRadiusMin, ray.dir * u_cylinderUvToRenderRadiusMin);
- vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MIN, innerIntersect);
- #endif
-
- #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF)
- vec2 wedgeIntersect = intersectRegularWedge(ray, u_cylinderRenderAngleMinMax.x, u_cylinderRenderAngleMinMax.y);
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE, wedgeIntersect);
- #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF)
- vec4 wedgeIntersect = intersectFlippedWedge(ray, u_cylinderRenderAngleMinMax.x, u_cylinderRenderAngleMinMax.y);
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 1, wedgeIntersect.zw);
- #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF)
- vec2 wedgeIntersect = intersectHalfSpace(ray, u_cylinderRenderAngleMinMax.x);
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE, wedgeIntersect);
- #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO)
- vec4 wedgeIntersect = intersectHalfPlane(ray, u_cylinderRenderAngleMinMax.x);
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 0, wedgeIntersect.xy);
- setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 1, wedgeIntersect.zw);
- #endif
-}
-#endif
-
-#if defined(SHAPE_CYLINDER)
-vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
- vec3 positionLocal = positionUv * 2.0 - 1.0; // [-1,+1]
-
- // Compute radius
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
- float radius = length(positionLocal.xy); // [0,1]
- #else
- float radius = length(positionLocal.xy); // [0,1]
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
- radius = radius * u_cylinderUvToShapeUvRadius.x + u_cylinderUvToShapeUvRadius.y; // x = scale, y = offset
- #endif
- #endif
-
- // Compute height
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT)
- float height = positionUv.z; // [0,1]
- #else
- float height = positionUv.z; // [0,1]
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
- height = height * u_cylinderUvToShapeUvHeight.x + u_cylinderUvToShapeUvHeight.y; // x = scale, y = offset
- #endif
- #endif
-
- // Compute angle
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO) || defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO)
- float angle = 1.0;
- #else
- float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
- // Comparing against u_cylinderShapeUvAngleMinMax has precision problems. u_cylinderShapeUvAngleEmptyMid is more conservative.
- angle += float(angle < u_cylinderShapeUvAngleEmptyMid);
- #endif
-
- // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
- #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY)
- angle = angle > u_cylinderShapeUvAngleEmptyMid ? u_cylinderShapeUvAngleMinMax.x : angle;
- #elif defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
- angle = angle < u_cylinderShapeUvAngleEmptyMid ? u_cylinderShapeUvAngleMinMax.y : angle;
- #endif
-
- angle = angle * u_cylinderUvToShapeUvAngle.x + u_cylinderUvToShapeUvAngle.y; // x = scale, y = offset
- #endif
- #endif
-
- return vec3(radius, height, angle);
-}
-#endif
-
#if defined(CLIPPING_PLANES)
void intersectClippingPlanes(Ray ray, inout Intersections ix) {
#if (CLIPPING_PLANES_COUNT == 1)
@@ -1800,4 +874,4 @@ void main()
#else
gl_FragColor = colorAccum;
#endif
-}
\ No newline at end of file
+}
From ccb159359e4ddb7b3624d79592548be6f7cf4691 Mon Sep 17 00:00:00 2001
From: Jeshurun Hembd
Date: Tue, 16 Aug 2022 18:32:18 -0400
Subject: [PATCH 079/679] Assume MEGATEXTURE_2D
---
Source/Scene/VoxelPrimitive.js | 6 -
Source/Shaders/VoxelFS.glsl | 986 +++++++++++++++++++++++++++++++--
2 files changed, 944 insertions(+), 48 deletions(-)
diff --git a/Source/Scene/VoxelPrimitive.js b/Source/Scene/VoxelPrimitive.js
index 658415888fc..9100de0ab00 100644
--- a/Source/Scene/VoxelPrimitive.js
+++ b/Source/Scene/VoxelPrimitive.js
@@ -1809,12 +1809,6 @@ function buildDrawCommands(that, context) {
ShaderDestination.FRAGMENT
);
- shaderBuilder.addDefine(
- "MEGATEXTURE_2D",
- undefined,
- ShaderDestination.FRAGMENT
- );
-
if (
!Cartesian3.equals(paddingBefore, Cartesian3.ZERO) ||
!Cartesian3.equals(paddingAfter, Cartesian3.ZERO)
diff --git a/Source/Shaders/VoxelFS.glsl b/Source/Shaders/VoxelFS.glsl
index 1aaa42f62ef..2306c9cd513 100644
--- a/Source/Shaders/VoxelFS.glsl
+++ b/Source/Shaders/VoxelFS.glsl
@@ -1,3 +1,134 @@
+/*
+Don't delete this comment!
+Some shader code is dynamically generated in VoxelPrimitive.js to support custom shaders with arbitrary metadata.
+Below is an example of how this code might look. Properties like "temperature" and "direction" are just examples.
+
+// Defines
+#define PROPERTY_COUNT ###
+#define SAMPLE_COUNT ###
+#define SHAPE_BOX
+#define SHAPE_ELLIPSOID
+#define SHAPE_CYLINDER
+#define MEGATEXTURE_3D
+#define DEPTH_TEST
+#define DEPTH_INTERSECTION_INDEX ###
+#define INTERSECTION_COUNT ###
+#define JITTER
+#define NEAREST_SAMPLING
+#define STATISTICS
+#define PADDING
+#define PICKING
+#define CLIPPING_PLANES
+#define CLIPPING_PLANES_UNION
+#define CLIPPING_PLANES_COUNT
+#define CLIPPING_PLANES_INTERSECTION_INDEX
+
+// Uniforms
+uniform sampler2D u_megatextureTextures[PROPERTY_COUNT];
+
+// Structs
+struct PropertyStatistics_temperature {
+ float min;
+ float max;
+};
+struct PropertyStatistics_direction {
+ vec3 min;
+ vec3 max;
+};
+struct Statistics {
+ PropertyStatistics_temperature temperature;
+ PropertyStatistics_direction direction;
+};
+struct Metadata {
+ Statistics statistics;
+ float temperature;
+ vec3 direction;
+};
+struct VoxelProperty_temperature {
+ vec3 partialDerivativeLocal;
+ vec3 partialDerivativeWorld;
+ vec3 partialDerivativeView;
+ bool partialDerivativeValid;
+};
+struct VoxelProperty_direction {
+ mat3 partialDerivativeLocal;
+ mat3 partialDerivativeWorld;
+ mat3 partialDerivativeView;
+ bool partialDerivativeValid;
+};
+struct Voxel {
+ VoxelProperty_temperature temperature;
+ VoxelProperty_direction direction;
+ vec3 positionEC;
+ vec3 positionUv;
+ vec3 positionShapeUv;
+ vec3 positionUvLocal;
+ vec3 viewDirUv;
+ vec3 viewDirWorld;
+ float travelDistance;
+};
+struct FragmentInput {
+ Metadata metadata;
+ Voxel voxel;
+};
+struct Properties {
+ // This struct is similar to Metadata but is not part of the custom shader API and
+ // is intended to be used internally as a lightweight way to pass around properties.
+ float temperature;
+ vec3 direction;
+};
+
+// Functions
+Properties clearProperties() {
+ Properties properties;
+ properties.temperature = 0.0;
+ properties.direction = vec3(0.0);
+ return properties;
+}
+Properties sumProperties(Properties propertiesA, Properties propertiesB) {
+ Properties properties;
+ properties.temperature = propertiesA.temperature + propertiesB.temperature;
+ properties.direction = propertiesA.direction + propertiesB.direction;
+ return properties;
+}
+Properties scaleProperties(Properties properties, float scale) {
+ Properties scaledProperties = properties;
+ scaledProperties.temperature *= scale;
+ scaledProperties.direction *= scale;
+ return scaledProperties;
+}
+Properties mixProperties(Properties propertiesA, Properties propertiesB, float mixFactor) {
+ Properties properties;
+ properties.temperature = mix(propertiesA.temperature, propertiesB.temperature, mixFactor);
+ properties.direction = mix(propertiesA.direction, propertiesB.direction, mixFactor);
+ return properties;
+}
+void copyPropertiesToMetadata(in Properties properties, inout Metadata metadata) {
+ metadata.temperature = properties.temperature;
+ metadata.direction = properties.direction;
+}
+void setStatistics(inout Statistics statistics) {
+ // Assume the "direction" property has no min/max
+ statistics.temperature.min = 20.0;
+ statistics.temperature.max = 50.0;
+}
+Properties getPropertiesFromMegatextureAtUv(vec2 texcoord) {
+ Properties properties;
+ properties.temperature = texture2D(u_megatextureTextures[0], texcoord).r;
+ properties.direction = texture2D(u_megatextureTextures[1], texcoord).rgb;
+ return properties;
+}
+void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
+ vec3 direction = fsInput.metadata.direction;
+ float temperature = fsInput.metadata.temperature;
+ float minTemperature = fsInput.metadata.statistics.temperature.min;
+ float maxTemperature = fsInput.metadata.statistics.temperature.max;
+
+ material.diffuse = abs(direction);
+ material.alpha = (temperature - minTemperature) / (maxTemperature - minTemperature);
+}
+*/
+
// These octree flags must be in sync with GpuOctreeFlag in VoxelTraversal.js
#define OCTREE_FLAG_INTERNAL 0
#define OCTREE_FLAG_LEAF 1
@@ -16,13 +147,11 @@ uniform ivec3 u_dimensions; // does not include padding
uniform ivec3 u_paddingAfter;
#endif
-#if defined(MEGATEXTURE_2D)
- uniform ivec2 u_megatextureSliceDimensions; // number of slices per tile, in two dimensions
- uniform ivec2 u_megatextureTileDimensions; // number of tiles per megatexture, in two dimensions
- uniform vec2 u_megatextureVoxelSizeUv;
- uniform vec2 u_megatextureSliceSizeUv;
- uniform vec2 u_megatextureTileSizeUv;
-#endif
+uniform ivec2 u_megatextureSliceDimensions; // number of slices per tile, in two dimensions
+uniform ivec2 u_megatextureTileDimensions; // number of tiles per megatexture, in two dimensions
+uniform vec2 u_megatextureVoxelSizeUv;
+uniform vec2 u_megatextureSliceSizeUv;
+uniform vec2 u_megatextureTileSizeUv;
uniform sampler2D u_octreeInternalNodeTexture;
uniform vec2 u_octreeInternalNodeTexelSizeUv;
@@ -94,6 +223,125 @@ uniform float u_stepSize;
#endif
#endif
+#if defined(SHAPE_ELLIPSOID)
+ /* Ellipsoid defines:
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN
+ #define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN
+ #define ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO
+ #define ELLIPSOID_IS_SPHERE
+ #define ELLIPSOID_INTERSECTION_INDEX_LONGITUDE
+ #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX
+ #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN
+ #define ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX
+ #define ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN
+ */
+
+ // Ellipsoid uniforms
+ uniform vec3 u_ellipsoidRadiiUv; // [0,1]
+ #if !defined(ELLIPSOID_IS_SPHERE)
+ uniform vec3 u_ellipsoidInverseRadiiSquaredUv;
+ #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE)
+ uniform vec2 u_ellipsoidRenderLongitudeMinMax;
+ #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY) || defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
+ uniform vec3 u_ellipsoidShapeUvLongitudeMinMaxMid;
+ #endif
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
+ uniform vec2 u_ellipsoidUvToShapeUvLongitude; // x = scale, y = offset
+ #endif
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
+ uniform vec2 u_ellipsoidUvToShapeUvLatitude; // x = scale, y = offset
+ #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF)
+ uniform vec2 u_ellipsoidRenderLatitudeCosSqrHalfMinMax;
+ #endif
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN) && !defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
+ uniform float u_ellipsoidInverseHeightDifferenceUv;
+ uniform vec2 u_ellipseInnerRadiiUv; // [0,1]
+ #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN)
+ uniform float u_ellipsoidInverseInnerScaleUv;
+ #endif
+#endif
+
+#if defined(SHAPE_CYLINDER)
+ /* Cylinder defines:
+ #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN
+ #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX
+ #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT
+ #define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT
+ #define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF
+ #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO
+
+ #define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS
+ #define CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT
+ #define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT
+ #define CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY
+ #define CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED
+
+ #define CYLINDER_INTERSECTION_INDEX_RADIUS_MAX
+ #define CYLINDER_INTERSECTION_INDEX_RADIUS_MIN
+ #define CYLINDER_INTERSECTION_INDEX_ANGLE
+ */
+
+ // Cylinder uniforms
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT)
+ uniform vec3 u_cylinderUvToRenderBoundsScale;
+ uniform vec3 u_cylinderUvToRenderBoundsTranslate;
+ #endif
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN) && !defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
+ uniform float u_cylinderUvToRenderRadiusMin;
+ #endif
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE)
+ uniform vec2 u_cylinderRenderAngleMinMax;
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
+ uniform vec2 u_cylinderUvToShapeUvRadius; // x = scale, y = offset
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
+ uniform vec2 u_cylinderUvToShapeUvHeight; // x = scale, y = offset
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
+ uniform vec2 u_cylinderUvToShapeUvAngle; // x = scale, y = offset
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
+ uniform vec2 u_cylinderShapeUvAngleMinMax;
+ #endif
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY) || defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
+ uniform float u_cylinderShapeUvAngleEmptyMid;
+ #endif
+#endif
+
// --------------------------------------------------------
// Misc math
// --------------------------------------------------------
@@ -111,6 +359,9 @@ float hash(vec2 p)
}
#endif
+float signNoZero(float v) {
+ return (v < 0.0) ? -1.0 : 1.0;
+}
int intMod(int a, int b) {
return a - (b * (a / b));
}
@@ -187,6 +438,38 @@ vec2 intersectUnitSquare(Ray ray) // Unit square from [-1, +1]
}
#endif
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF))
+vec2 intersectZPlane(Ray ray)
+{
+ float o = ray.pos.z;
+ float d = ray.dir.z;
+ float t = -o / d;
+ float s = sign(o);
+
+ if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
+ else return vec2(-INF_HIT, t);
+}
+#endif
+
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO))
+vec4 intersectHalfPlane(Ray ray, float angle) {
+ vec2 o = ray.pos.xy;
+ vec2 d = ray.dir.xy;
+ vec2 planeDirection = vec2(cos(angle), sin(angle));
+ vec2 planeNormal = vec2(planeDirection.y, -planeDirection.x);
+
+ float a = dot(o, planeNormal);
+ float b = dot(d, planeNormal);
+ float t = -a / b;
+
+ vec2 p = o + t * d;
+ bool outside = dot(p, planeDirection) < 0.0;
+ if (outside) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
+
+ return vec4(-INF_HIT, t, t, +INF_HIT);
+}
+#endif
+
#if defined(CLIPPING_PLANES)
// Plane is in Hessian Normal Form
vec2 intersectPlane(Ray ray, vec4 plane) {
@@ -207,6 +490,343 @@ vec2 intersectPlane(Ray ray, vec4 plane) {
}
#endif
+#if (defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF))) || (defined(SHAPE_CYLINDER) && (defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF) || defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF)))
+vec2 intersectHalfSpace(Ray ray, float angle)
+{
+ vec2 o = ray.pos.xy;
+ vec2 d = ray.dir.xy;
+ vec2 n = vec2(sin(angle), -cos(angle));
+
+ float a = dot(o, n);
+ float b = dot(d, n);
+ float t = -a / b;
+ float s = sign(a);
+
+ if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT);
+ else return vec2(-INF_HIT, t);
+}
+#endif
+
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF))
+vec2 intersectRegularWedge(Ray ray, float minAngle, float maxAngle)
+{
+ vec2 o = ray.pos.xy;
+ vec2 d = ray.dir.xy;
+ vec2 n1 = vec2(sin(minAngle), -cos(minAngle));
+ vec2 n2 = vec2(-sin(maxAngle), cos(maxAngle));
+
+ float a1 = dot(o, n1);
+ float a2 = dot(o, n2);
+ float b1 = dot(d, n1);
+ float b2 = dot(d, n2);
+
+ float t1 = -a1 / b1;
+ float t2 = -a2 / b2;
+ float s1 = sign(a1);
+ float s2 = sign(a2);
+
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+ float smin = tmin == t1 ? s1 : s2;
+ float smax = tmin == t1 ? s2 : s1;
+
+ bool e = tmin >= 0.0;
+ bool f = tmax >= 0.0;
+ bool g = smin >= 0.0;
+ bool h = smax >= 0.0;
+
+ if (e != g && f == h) return vec2(tmin, tmax);
+ else if (e == g && f == h) return vec2(-INF_HIT, tmin);
+ else if (e != g && f != h) return vec2(tmax, +INF_HIT);
+ else return vec2(NO_HIT);
+}
+#endif
+
+#if (defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF)) || (defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF))
+vec4 intersectFlippedWedge(Ray ray, float minAngle, float maxAngle)
+{
+ vec2 planeIntersectMin = intersectHalfSpace(ray, minAngle);
+ vec2 planeIntersectMax = intersectHalfSpace(ray, maxAngle + czm_pi);
+ return vec4(planeIntersectMin, planeIntersectMax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+vec2 intersectUnitSphere(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float b = dot(d, o);
+ float c = dot(o, o) - 1.0;
+ float det = b * b - c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT);
+ }
+
+ det = sqrt(det);
+ float t1 = -b - det;
+ float t2 = -b + det;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+vec2 intersectUnitSphereUnnormalizedDirection(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float a = dot(d, d);
+ float b = dot(d, o);
+ float c = dot(o, o) - 1.0;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE)
+vec2 intersectDoubleEndedCone(Ray ray, float cosSqrHalfAngle)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ float a = d.z * d.z - dot(d, d) * cosSqrHalfAngle;
+ float b = d.z * o.z - dot(o, d) * cosSqrHalfAngle;
+ float c = o.z * o.z - dot(o, o) * cosSqrHalfAngle;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF))
+vec4 intersectFlippedCone(Ray ray, float cosSqrHalfAngle) {
+ vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
+
+ if (intersect.x == NO_HIT) {
+ return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
+ }
+
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ float tmin = intersect.x;
+ float tmax = intersect.y;
+ float zmin = o.z + tmin * d.z;
+ float zmax = o.z + tmax * d.z;
+
+ // One interval
+ if (zmin < 0.0 && zmax < 0.0) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT);
+ else if (zmin < 0.0) return vec4(-INF_HIT, tmax, NO_HIT, NO_HIT);
+ else if (zmax < 0.0) return vec4(tmin, +INF_HIT, NO_HIT, NO_HIT);
+ // Two intervals
+ else return vec4(-INF_HIT, tmin, tmax, +INF_HIT);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID) && (defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF))
+vec2 intersectRegularCone(Ray ray, float cosSqrHalfAngle) {
+ vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle);
+
+ if (intersect.x == NO_HIT) {
+ return vec2(NO_HIT);
+ }
+
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+ float tmin = intersect.x;
+ float tmax = intersect.y;
+ float zmin = o.z + tmin * d.z;
+ float zmax = o.z + tmax * d.z;
+
+ if (zmin < 0.0 && zmax < 0.0) return vec2(NO_HIT);
+ else if (zmin < 0.0) return vec2(tmax, +INF_HIT);
+ else if (zmax < 0.0) return vec2(-INF_HIT, tmin);
+ else return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+vec2 intersectUnitCylinder(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float a = dot(d.xy, d.xy);
+ float b = dot(o.xy, d.xy);
+ float c = dot(o.xy, o.xy) - 1.0;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT);
+ }
+
+ det = sqrt(det);
+ float ta = (-b - det) / a;
+ float tb = (-b + det) / a;
+ float t1 = min(ta, tb);
+ float t2 = max(ta, tb);
+
+ float z1 = o.z + t1 * d.z;
+ float z2 = o.z + t2 * d.z;
+
+ if (abs(z1) >= 1.0)
+ {
+ float tCap = (sign(z1) - o.z) / d.z;
+ t1 = abs(b + a * tCap) < det ? tCap : NO_HIT;
+ }
+
+ if (abs(z2) >= 1.0)
+ {
+ float tCap = (sign(z2) - o.z) / d.z;
+ t2 = abs(b + a * tCap) < det ? tCap : NO_HIT;
+ }
+
+ return vec2(t1, t2);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+vec2 intersectUnitCircle(Ray ray) {
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float t = -o.z / d.z;
+ vec2 zPlanePos = o.xy + d.xy * t;
+ float distSqr = dot(zPlanePos, zPlanePos);
+
+ if (distSqr > 1.0) {
+ return vec2(NO_HIT);
+ }
+
+ return vec2(t, t);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER) && defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN)
+vec2 intersectInfiniteUnitCylinder(Ray ray)
+{
+ vec3 o = ray.pos;
+ vec3 d = ray.dir;
+
+ float a = dot(d.xy, d.xy);
+ float b = dot(o.xy, d.xy);
+ float c = dot(o.xy, o.xy) - 1.0;
+ float det = b * b - a * c;
+
+ if (det < 0.0) {
+ return vec2(NO_HIT);
+ }
+
+ det = sqrt(det);
+ float t1 = (-b - det) / a;
+ float t2 = (-b + det) / a;
+ float tmin = min(t1, t2);
+ float tmax = max(t1, t2);
+
+ return vec2(tmin, tmax);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+// robust iterative solution without trig functions
+// https://github.com/0xfaded/ellipse_demo/issues/1
+// https://stackoverflow.com/questions/22959698/distance-from-given-point-to-given-ellipse
+// Pro: Good when radii.x ~= radii.y
+// Con: Breaks at pos.x ~= 0.0, especially inside the ellipse
+// Con: Inaccurate with exterior points and thin ellipses
+float ellipseDistanceIterative (vec2 pos, vec2 radii) {
+ vec2 p = abs(pos);
+ vec2 invRadii = 1.0 / radii;
+ vec2 a = vec2(1.0, -1.0) * (radii.x * radii.x - radii.y * radii.y) * invRadii;
+ vec2 t = vec2(0.70710678118); // sqrt(2) / 2
+ vec2 v = radii * t;
+
+ const int iterations = 3;
+ for (int i = 0; i < iterations; ++i) {
+ vec2 e = a * pow(t, vec2(3.0));
+ vec2 q = normalize(p - e) * length(v - e);
+ t = normalize((q + e) * invRadii);
+ v = radii * t;
+ }
+ return length(v * sign(pos) - pos) * sign(p.y - v.y);
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+// From: https://www.shadertoy.com/view/4sS3zz
+// Pro: Accurate in most cases
+// Con: Breaks if radii.x ~= radii.y
+float ellipseDistanceAnalytical(vec2 pos, vec2 radii) {
+ vec2 p = pos;
+ vec2 ab = radii;
+
+ p = abs(p);
+ if (p.x > p.y) {
+ p = p.yx;
+ ab = ab.yx;
+ }
+
+ float l = ab.y * ab.y - ab.x * ab.x;
+ float m = ab.x * p.x / l;
+ float n = ab.y * p.y / l;
+ float m2 = m * m;
+ float n2 = n * n;
+ float c = (m2 + n2 - 1.0) / 3.0;
+ float c3 = c * c * c;
+ float d = c3 + m2 * n2;
+ float q = d + m2 * n2;
+ float g = m + m * n2;
+
+ float co;
+
+ if (d < 0.0) {
+ float h = acos(q / c3) / 3.0;
+ float s = cos(h) + 2.0;
+ float t = sin(h) * sqrt(3.0);
+ float rx = sqrt(m2 - c * (s + t));
+ float ry = sqrt(m2 - c * (s - t));
+ co = ry + sign(l) * rx + abs(g) / (rx * ry);
+ } else {
+ float h = 2.0 * m * n * sqrt(d);
+ float s = signNoZero(q + h) * pow(abs(q + h), 1.0 / 3.0);
+ float t = signNoZero(q - h) * pow(abs(q - h), 1.0 / 3.0);
+ float rx = -(s + t) - c * 4.0 + 2.0 * m2;
+ float ry = (s - t) * sqrt(3.0);
+ float rm = sqrt(rx * rx + ry * ry);
+ co = ry / sqrt(rm - rx) + 2.0 * g / rm;
+ }
+
+ co = (co - m) / 2.0;
+ float si = sqrt(max(1.0 - co * co, 0.0));
+ vec2 r = ab * vec2(co, si);
+ return length(r - p) * signNoZero(p.y - r.y);
+}
+#endif
+
struct Intersections {
// Don't access these member variables directly - call the functions instead.
@@ -378,6 +998,302 @@ vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
}
#endif
+#if defined(SHAPE_ELLIPSOID)
+void intersectShape(in Ray ray, inout Intersections ix) {
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir *= 2.0;
+
+ // Outer ellipsoid
+ vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(ray);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX, outerIntersect);
+
+ // Exit early if the outer ellipsoid was missed.
+ if (outerIntersect.x == NO_HIT) {
+ return;
+ }
+
+ // Inner ellipsoid
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
+ // When the ellipsoid is perfectly thin it's necessary to sandwich the
+ // inner ellipsoid intersection inside the outer ellipsoid intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the ellipsoid to be invisible because it will think the ray
+ // is still inside the inner (negative) ellipsoid after exiting the
+ // outer (positive) ellipsoid.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If initializeIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, outerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, outerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN)
+ Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv);
+ vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN, innerIntersect);
+ #endif
+
+ // Flip the ray because the intersection function expects a cone growing towards +Z.
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF)
+ Ray flippedRay = ray;
+ flippedRay.dir.z *= -1.0;
+ flippedRay.pos.z *= -1.0;
+ #endif
+
+ // Bottom cone
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF)
+ vec2 bottomConeIntersection = intersectRegularCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF)
+ vec2 bottomConeIntersection = intersectZPlane(flippedRay);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF)
+ vec4 bottomConeIntersection = intersectFlippedCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 0, bottomConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 1, bottomConeIntersection.zw);
+ #endif
+
+ // Top cone
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF)
+ vec4 topConeIntersection = intersectFlippedCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 0, topConeIntersection.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 1, topConeIntersection.zw);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF)
+ vec2 topConeIntersection = intersectZPlane(ray);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF)
+ vec2 topConeIntersection = intersectRegularCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection);
+ #endif
+
+ // Wedge
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)
+ vec4 wedgeIntersect = intersectHalfPlane(ray, u_ellipsoidRenderLongitudeMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 1, wedgeIntersect.zw);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_UNDER_HALF)
+ vec2 wedgeIntersect = intersectRegularWedge(ray, u_ellipsoidRenderLongitudeMinMax.x, u_ellipsoidRenderLongitudeMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE, wedgeIntersect);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_HALF)
+ vec2 wedgeIntersect = intersectHalfSpace(ray, u_ellipsoidRenderLongitudeMinMax.x);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE, wedgeIntersect);
+ #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_OVER_HALF)
+ vec4 wedgeIntersect = intersectFlippedWedge(ray, u_ellipsoidRenderLongitudeMinMax.x, u_ellipsoidRenderLongitudeMinMax.y);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE + 1, wedgeIntersect.zw);
+ #endif
+}
+#endif
+
+#if defined(SHAPE_ELLIPSOID)
+vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
+ // Compute position and normal.
+ // Convert positionUv [0,1] to local space [-1,+1] to "normalized" cartesian space [-a,+a] where a = (radii + height) / (max(radii) + height).
+ // A point on the largest ellipsoid axis would be [-1,+1] and everything else would be smaller.
+ vec3 positionLocal = positionUv * 2.0 - 1.0;
+ #if defined(ELLIPSOID_IS_SPHERE)
+ vec3 posEllipsoid = positionLocal * u_ellipsoidRadiiUv.x;
+ vec3 normal = normalize(posEllipsoid);
+ #else
+ vec3 posEllipsoid = positionLocal * u_ellipsoidRadiiUv;
+ vec3 normal = normalize(posEllipsoid * u_ellipsoidInverseRadiiSquaredUv); // geodetic surface normal
+ #endif
+
+ // Compute longitude
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_RANGE_EQUAL_ZERO)
+ float longitude = 1.0;
+ #else
+ float longitude = (atan(normal.y, normal.x) + czm_pi) / czm_twoPi;
+
+ // Correct the angle when max < min
+ // Technically this should compare against min longitude - but it has precision problems so compare against the middle of empty space.
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE_MIN_MAX_REVERSED)
+ longitude += float(longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z);
+ #endif
+
+ // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MIN_DISCONTINUITY)
+ longitude = longitude > u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.x : longitude;
+ #endif
+ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE_MAX_DISCONTINUITY)
+ longitude = longitude < u_ellipsoidShapeUvLongitudeMinMaxMid.z ? u_ellipsoidShapeUvLongitudeMinMaxMid.y : longitude;
+ #endif
+
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LONGITUDE)
+ longitude = longitude * u_ellipsoidUvToShapeUvLongitude.x + u_ellipsoidUvToShapeUvLongitude.y;
+ #endif
+ #endif
+
+ // Compute latitude
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_RANGE_EQUAL_ZERO)
+ float latitude = 1.0;
+ #else
+ float latitude = (asin(normal.z) + czm_piOverTwo) / czm_pi;
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE)
+ latitude = latitude * u_ellipsoidUvToShapeUvLatitude.x + u_ellipsoidUvToShapeUvLatitude.y;
+ #endif
+ #endif
+
+ // Compute height
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_RANGE_EQUAL_ZERO)
+ // TODO: This breaks down when minBounds == maxBounds. To fix it, this
+ // function would have to know if ray is intersecting the front or back of the shape
+ // and set the shape space position to 1 (front) or 0 (back) accordingly.
+ float height = 1.0;
+ #else
+ #if defined(ELLIPSOID_IS_SPHERE)
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN)
+ float height = (length(posEllipsoid) - u_ellipseInnerRadiiUv.x) * u_ellipsoidInverseHeightDifferenceUv;
+ #else
+ float height = length(posEllipsoid);
+ #endif
+ #else
+ #if defined(ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_MIN)
+ // Convert the 3D position to a 2D position relative to the ellipse (radii.x, radii.z) (assuming radii.x == radii.y which is true for WGS84).
+ // This is an optimization so that math can be done with ellipses instead of ellipsoids.
+ vec2 posEllipse = vec2(length(posEllipsoid.xy), posEllipsoid.z);
+ float height = ellipseDistanceIterative(posEllipse, u_ellipseInnerRadiiUv) * u_ellipsoidInverseHeightDifferenceUv;
+ #else
+ // TODO: this is probably not correct
+ float height = length(posEllipsoid);
+ #endif
+ #endif
+ #endif
+
+ return vec3(longitude, latitude, height);
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+void intersectShape(Ray ray, inout Intersections ix)
+{
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT)
+ ray.pos = ray.pos * u_cylinderUvToRenderBoundsScale + u_cylinderUvToRenderBoundsTranslate;
+ ray.dir *= u_cylinderUvToRenderBoundsScale;
+ #else
+ // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1].
+ // Direction is scaled as well to be in sync with position.
+ ray.pos = ray.pos * 2.0 - 1.0;
+ ray.dir *= 2.0;
+ #endif
+
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT)
+ vec2 outerIntersect = intersectUnitCircle(ray);
+ #else
+ vec2 outerIntersect = intersectUnitCylinder(ray);
+ #endif
+
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MAX, outerIntersect);
+
+ if (outerIntersect.x == NO_HIT) {
+ return;
+ }
+
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
+ // When the cylinder is perfectly thin it's necessary to sandwich the
+ // inner cylinder intersection inside the outer cylinder intersection.
+
+ // Without this special case,
+ // [outerMin, outerMax, innerMin, innerMax] will bubble sort to
+ // [outerMin, innerMin, outerMax, innerMax] which will cause the back
+ // side of the cylinder to be invisible because it will think the ray
+ // is still inside the inner (negative) cylinder after exiting the
+ // outer (positive) cylinder.
+
+ // With this special case,
+ // [outerMin, innerMin, innerMax, outerMax] will bubble sort to
+ // [outerMin, innerMin, innerMax, outerMax] which will work correctly.
+
+ // Note: If initializeIntersections() changes its sorting function
+ // from bubble sort to something else, this code may need to change.
+ vec2 innerIntersect = intersectInfiniteUnitCylinder(ray);
+ setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter
+ setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter
+ setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit
+ setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN)
+ Ray innerRay = Ray(ray.pos * u_cylinderUvToRenderRadiusMin, ray.dir * u_cylinderUvToRenderRadiusMin);
+ vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MIN, innerIntersect);
+ #endif
+
+ #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF)
+ vec2 wedgeIntersect = intersectRegularWedge(ray, u_cylinderRenderAngleMinMax.x, u_cylinderRenderAngleMinMax.y);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE, wedgeIntersect);
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF)
+ vec4 wedgeIntersect = intersectFlippedWedge(ray, u_cylinderRenderAngleMinMax.x, u_cylinderRenderAngleMinMax.y);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 1, wedgeIntersect.zw);
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_HALF)
+ vec2 wedgeIntersect = intersectHalfSpace(ray, u_cylinderRenderAngleMinMax.x);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE, wedgeIntersect);
+ #elif defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO)
+ vec4 wedgeIntersect = intersectHalfPlane(ray, u_cylinderRenderAngleMinMax.x);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 0, wedgeIntersect.xy);
+ setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_ANGLE + 1, wedgeIntersect.zw);
+ #endif
+}
+#endif
+
+#if defined(SHAPE_CYLINDER)
+vec3 convertUvToShapeUvSpace(in vec3 positionUv) {
+ vec3 positionLocal = positionUv * 2.0 - 1.0; // [-1,+1]
+
+ // Compute radius
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT)
+ float radius = length(positionLocal.xy); // [0,1]
+ #else
+ float radius = length(positionLocal.xy); // [0,1]
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_RADIUS)
+ radius = radius * u_cylinderUvToShapeUvRadius.x + u_cylinderUvToShapeUvRadius.y; // x = scale, y = offset
+ #endif
+ #endif
+
+ // Compute height
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT)
+ float height = positionUv.z; // [0,1]
+ #else
+ float height = positionUv.z; // [0,1]
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT)
+ height = height * u_cylinderUvToShapeUvHeight.x + u_cylinderUvToShapeUvHeight.y; // x = scale, y = offset
+ #endif
+ #endif
+
+ // Compute angle
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_ZERO) || defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_ZERO)
+ float angle = 1.0;
+ #else
+ float angle = (atan(positionLocal.y, positionLocal.x) + czm_pi) / czm_twoPi; // [0,1]
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE)
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED)
+ // Comparing against u_cylinderShapeUvAngleMinMax has precision problems. u_cylinderShapeUvAngleEmptyMid is more conservative.
+ angle += float(angle < u_cylinderShapeUvAngleEmptyMid);
+ #endif
+
+ // Avoid flickering from reading voxels from both sides of the -pi/+pi discontinuity.
+ #if defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY)
+ angle = angle > u_cylinderShapeUvAngleEmptyMid ? u_cylinderShapeUvAngleMinMax.x : angle;
+ #elif defined(CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY)
+ angle = angle < u_cylinderShapeUvAngleEmptyMid ? u_cylinderShapeUvAngleMinMax.y : angle;
+ #endif
+
+ angle = angle * u_cylinderUvToShapeUvAngle.x + u_cylinderUvToShapeUvAngle.y; // x = scale, y = offset
+ #endif
+ #endif
+
+ return vec3(radius, height, angle);
+}
+#endif
+
#if defined(CLIPPING_PLANES)
void intersectClippingPlanes(Ray ray, inout Intersections ix) {
#if (CLIPPING_PLANES_COUNT == 1)
@@ -533,45 +1449,31 @@ Properties getPropertiesFromMegatexture(in SampleData sampleData) {
voxelCoord = floor(voxelCoord) + vec3(0.5);
#endif
- #if defined(MEGATEXTURE_2D)
- // Tile location
- vec2 tileUvOffset = index1DTo2DTexcoord(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
-
- // Slice location
- float slice = voxelCoord.z - 0.5;
- int sliceIndex = int(floor(slice));
- int sliceIndex0 = intClamp(sliceIndex, 0, voxelDimensions.z - 1);
- vec2 sliceUvOffset0 = index1DTo2DTexcoord(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
-
- // Voxel location
- vec2 voxelUvOffset = clamp(voxelCoord.xy, vec2(0.5), vec2(voxelDimensions.xy) - vec2(0.5)) * u_megatextureVoxelSizeUv;
-
- // Final location in the megatexture
- vec2 uv0 = tileUvOffset + sliceUvOffset0 + voxelUvOffset;
+ // Tile location
+ vec2 tileUvOffset = index1DTo2DTexcoord(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
- #if defined(NEAREST_SAMPLING)
- return getPropertiesFromMegatextureAtUv(uv0);
- #else
- float sliceLerp = fract(slice);
- int sliceIndex1 = intMin(sliceIndex + 1, voxelDimensions.z - 1);
- vec2 sliceUvOffset1 = index1DTo2DTexcoord(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
- vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
- Properties properties0 = getPropertiesFromMegatextureAtUv(uv0);
- Properties properties1 = getPropertiesFromMegatextureAtUv(uv1);
- return mixProperties(properties0, properties1, sliceLerp);
- #endif
- #elif defined(MEGATEXTURE_3D)
- // TODO: 3D megatexture has not been implemented yet
- // Tile location
- vec3 tileUvOffset = indexToUv3d(tileIndex, u_megatextureTileDimensions, u_megatextureTileSizeUv);
+ // Slice location
+ float slice = voxelCoord.z - 0.5;
+ int sliceIndex = int(floor(slice));
+ int sliceIndex0 = intClamp(sliceIndex, 0, voxelDimensions.z - 1);
+ vec2 sliceUvOffset0 = index1DTo2DTexcoord(sliceIndex0, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
- // Voxel location
- vec3 voxelUvOffset = clamp(voxelCoord, vec3(0.5), vec3(voxelDimensions) - vec2(0.5)) * u_megatextureVoxelSizeUv;
+ // Voxel location
+ vec2 voxelUvOffset = clamp(voxelCoord.xy, vec2(0.5), vec2(voxelDimensions.xy) - vec2(0.5)) * u_megatextureVoxelSizeUv;
- // Final location in the megatexture
- vec3 uv = tileUvOffset + voxelUvOffset;
+ // Final location in the megatexture
+ vec2 uv0 = tileUvOffset + sliceUvOffset0 + voxelUvOffset;
- return getPropertiesFromMegatextureAtUv(uv);
+ #if defined(NEAREST_SAMPLING)
+ return getPropertiesFromMegatextureAtUv(uv0);
+ #else
+ float sliceLerp = fract(slice);
+ int sliceIndex1 = intMin(sliceIndex + 1, voxelDimensions.z - 1);
+ vec2 sliceUvOffset1 = index1DTo2DTexcoord(sliceIndex1, u_megatextureSliceDimensions, u_megatextureSliceSizeUv);
+ vec2 uv1 = tileUvOffset + sliceUvOffset1 + voxelUvOffset;
+ Properties properties0 = getPropertiesFromMegatextureAtUv(uv0);
+ Properties properties1 = getPropertiesFromMegatextureAtUv(uv1);
+ return mixProperties(properties0, properties1, sliceLerp);
#endif
}
From 8991a4ae9aaf85f6add03c01e62b9c48d61edaca Mon Sep 17 00:00:00 2001
From: Jeshurun Hembd
Date: Thu, 18 Aug 2022 12:20:45 -0400
Subject: [PATCH 080/679] Break VoxelFS.glsl into smaller components
---
Apps/Sandcastle/gallery/Voxels.html | 6 +-
Apps/Sandcastle/gallery/VoxelsBox.html | 391 ++++
Source/Scene/VoxelDrawCommands.js | 763 +++++++
Source/Scene/VoxelPrimitive.js | 724 +------
Source/Shaders/VoxelFS.glsl | 1779 -----------------
Source/Shaders/Voxels/IntersectBox.glsl | 80 +
.../Voxels/IntersectClippingPlanes.glsl | 71 +
Source/Shaders/Voxels/IntersectCylinder.glsl | 259 +++
Source/Shaders/Voxels/IntersectDepth.glsl | 24 +
Source/Shaders/Voxels/IntersectEllipsoid.glsl | 320 +++
Source/Shaders/Voxels/Intersection.glsl | 52 +
Source/Shaders/Voxels/IntersectionUtils.glsl | 147 ++
Source/Shaders/Voxels/Megatexture.glsl | 138 ++
Source/Shaders/Voxels/Octree.glsl | 227 +++
Source/Shaders/Voxels/VoxelFS.glsl | 132 ++
Source/Shaders/{ => Voxels}/VoxelVS.glsl | 0
Source/Shaders/Voxels/convertUvToBox.glsl | 17 +
.../Shaders/Voxels/convertUvToCylinder.glsl | 81 +
.../Shaders/Voxels/convertUvToEllipsoid.glsl | 135 ++
19 files changed, 2843 insertions(+), 2503 deletions(-)
create mode 100644 Apps/Sandcastle/gallery/VoxelsBox.html
create mode 100644 Source/Scene/VoxelDrawCommands.js
delete mode 100644 Source/Shaders/VoxelFS.glsl
create mode 100644 Source/Shaders/Voxels/IntersectBox.glsl
create mode 100644 Source/Shaders/Voxels/IntersectClippingPlanes.glsl
create mode 100644 Source/Shaders/Voxels/IntersectCylinder.glsl
create mode 100644 Source/Shaders/Voxels/IntersectDepth.glsl
create mode 100644 Source/Shaders/Voxels/IntersectEllipsoid.glsl
create mode 100644 Source/Shaders/Voxels/Intersection.glsl
create mode 100644 Source/Shaders/Voxels/IntersectionUtils.glsl
create mode 100644 Source/Shaders/Voxels/Megatexture.glsl
create mode 100644 Source/Shaders/Voxels/Octree.glsl
create mode 100644 Source/Shaders/Voxels/VoxelFS.glsl
rename Source/Shaders/{ => Voxels}/VoxelVS.glsl (100%)
create mode 100644 Source/Shaders/Voxels/convertUvToBox.glsl
create mode 100644 Source/Shaders/Voxels/convertUvToCylinder.glsl
create mode 100644 Source/Shaders/Voxels/convertUvToEllipsoid.glsl
diff --git a/Apps/Sandcastle/gallery/Voxels.html b/Apps/Sandcastle/gallery/Voxels.html
index 048c6a58984..641bbd09ed6 100644
--- a/Apps/Sandcastle/gallery/Voxels.html
+++ b/Apps/Sandcastle/gallery/Voxels.html
@@ -32,7 +32,7 @@
Loading...
+
+
+ Loading...
+
+
+
diff --git a/Apps/Sandcastle/gallery/VoxelsBox.html b/Apps/Sandcastle/gallery/VoxelsBox.html
new file mode 100644
index 00000000000..73750c27470
--- /dev/null
+++ b/Apps/Sandcastle/gallery/VoxelsBox.html
@@ -0,0 +1,391 @@
+
+
+