@cesium/engine 23.0.0 → 24.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/Build/Workers/{chunk-QTOHIXGF.js → chunk-2N4KDHS3.js} +10 -10
  2. package/Build/Workers/{chunk-SQTRWKO2.js → chunk-44EMEGKW.js} +5 -5
  3. package/Build/Workers/{chunk-OHBM4YG4.js → chunk-4TIG36AW.js} +12 -12
  4. package/Build/Workers/{chunk-BNBD2YZK.js → chunk-5A2SW66E.js} +16 -16
  5. package/Build/Workers/{chunk-52K3GJKG.js → chunk-5GXYJQ2K.js} +3 -3
  6. package/Build/Workers/{chunk-NQCAZU7S.js → chunk-63SIWPLS.js} +13 -13
  7. package/Build/Workers/{chunk-UEEGWJJP.js → chunk-6MPGQEHA.js} +2 -2
  8. package/Build/Workers/{chunk-PQVNT5CJ.js → chunk-6RMS4S72.js} +6 -6
  9. package/Build/Workers/{chunk-HYTFXWMY.js → chunk-6WLHJFD3.js} +7 -7
  10. package/Build/Workers/{chunk-DT5SJBKA.js → chunk-72VYSVEW.js} +9 -9
  11. package/Build/Workers/{chunk-JUGY5AZN.js → chunk-7MDYBRYJ.js} +4 -4
  12. package/Build/Workers/{chunk-6SLGTBB2.js → chunk-7O5XJTVE.js} +5 -5
  13. package/Build/Workers/{chunk-4UNQX2MP.js → chunk-7QRFIKWQ.js} +2 -2
  14. package/Build/Workers/{chunk-GN5NEUDT.js → chunk-7TYLCBGK.js} +8 -8
  15. package/Build/Workers/{chunk-SGKTBHFQ.js → chunk-7X322WP2.js} +13 -13
  16. package/Build/Workers/{chunk-T43FXHNC.js → chunk-A47HVBC5.js} +7 -7
  17. package/Build/Workers/{chunk-FSDVXW7M.js → chunk-AF3MU2RL.js} +5 -5
  18. package/Build/Workers/{chunk-3B65YFXC.js → chunk-AJLTU6PF.js} +1 -1
  19. package/Build/Workers/{chunk-OQ3EAJNW.js → chunk-AMIHNSST.js} +2 -2
  20. package/Build/Workers/{chunk-N4AWE7TZ.js → chunk-B6SCUUFP.js} +10 -10
  21. package/Build/Workers/{chunk-OMXN7YF4.js → chunk-BJD6QGXZ.js} +12 -12
  22. package/Build/Workers/{chunk-APDKMX7W.js → chunk-C6P4IG5P.js} +6 -6
  23. package/Build/Workers/{chunk-QA24Q62E.js → chunk-D4EJ2NXG.js} +17 -17
  24. package/Build/Workers/{chunk-HYLEKKO5.js → chunk-DHKPFLSJ.js} +1 -1
  25. package/Build/Workers/{chunk-MQJG522T.js → chunk-DVB56LG7.js} +6 -6
  26. package/Build/Workers/{chunk-Y5AJSEEN.js → chunk-EDXL4W76.js} +2 -2
  27. package/Build/Workers/{chunk-QQX7S5QT.js → chunk-EV3VGO6Y.js} +17 -17
  28. package/Build/Workers/{chunk-IFA7VHHP.js → chunk-GOQBUCXH.js} +10 -10
  29. package/Build/Workers/{chunk-5A7YSAXT.js → chunk-GQUNK2XC.js} +14 -14
  30. package/Build/Workers/{chunk-36LBFITA.js → chunk-HKUES6UG.js} +12 -12
  31. package/Build/Workers/{chunk-OC6X22PG.js → chunk-HRHRYFHS.js} +9 -9
  32. package/Build/Workers/{chunk-L7RDJ4V7.js → chunk-HXJMS2ZI.js} +1 -1
  33. package/Build/Workers/{chunk-NAYRNPEP.js → chunk-ILMFADZA.js} +4 -4
  34. package/Build/Workers/{chunk-AA33B6PU.js → chunk-ISY4EZCA.js} +4 -4
  35. package/Build/Workers/{chunk-7KRN6JPQ.js → chunk-J6AW5YHS.js} +14 -14
  36. package/Build/Workers/{chunk-OQBPOKPZ.js → chunk-JCI63T3K.js} +6 -6
  37. package/Build/Workers/{chunk-2WI4HGJU.js → chunk-KLPGTXS3.js} +6 -6
  38. package/Build/Workers/{chunk-5R7SBUTW.js → chunk-LIB2H534.js} +10 -10
  39. package/Build/Workers/{chunk-IYVDOB5V.js → chunk-LXMCPFFS.js} +4 -4
  40. package/Build/Workers/{chunk-NUYLNQKV.js → chunk-MKM27KGV.js} +2 -2
  41. package/Build/Workers/{chunk-R7M4XNPP.js → chunk-N65BAKSH.js} +8 -8
  42. package/Build/Workers/{chunk-4LGFX2P5.js → chunk-NKT5K3K3.js} +11 -11
  43. package/Build/Workers/{chunk-NNHU33JK.js → chunk-NPF7SSHZ.js} +7 -7
  44. package/Build/Workers/{chunk-LDQWNMEC.js → chunk-PVV64VFW.js} +1 -1
  45. package/Build/Workers/{chunk-WKXDCVBO.js → chunk-Q3C6VQH6.js} +4 -4
  46. package/Build/Workers/{chunk-ARCFIOKA.js → chunk-RQNJD3O4.js} +12 -12
  47. package/Build/Workers/{chunk-G3YT7M7A.js → chunk-UIGVOKKI.js} +1 -1
  48. package/Build/Workers/{chunk-ZIW5FCIW.js → chunk-UQU4RXLT.js} +27 -19
  49. package/Build/Workers/{chunk-MT5M7RLZ.js → chunk-VLDEUTOP.js} +2 -2
  50. package/Build/Workers/{chunk-5JP5FG7U.js → chunk-VMVOUQ5J.js} +7 -7
  51. package/Build/Workers/{chunk-2GL2EUEE.js → chunk-XJRBB253.js} +11 -11
  52. package/Build/Workers/{chunk-ESD4IEOU.js → chunk-XY6HMDGI.js} +5 -5
  53. package/Build/Workers/{chunk-56OHFSLH.js → chunk-Y7HNNPNK.js} +10 -10
  54. package/Build/Workers/{chunk-6FVFM2KB.js → chunk-YAOZMOFL.js} +5 -5
  55. package/Build/Workers/{chunk-AP6XWET5.js → chunk-YJZGW74L.js} +8 -8
  56. package/Build/Workers/{chunk-KPYU6YZG.js → chunk-YQFTYQMZ.js} +1 -1
  57. package/Build/Workers/{chunk-5VKZVPO5.js → chunk-ZBZRTRJB.js} +10 -10
  58. package/Build/Workers/combineGeometry.js +24 -24
  59. package/Build/Workers/createBoxGeometry.js +18 -18
  60. package/Build/Workers/createBoxOutlineGeometry.js +16 -16
  61. package/Build/Workers/createCircleGeometry.js +26 -26
  62. package/Build/Workers/createCircleOutlineGeometry.js +19 -19
  63. package/Build/Workers/createCoplanarPolygonGeometry.js +33 -33
  64. package/Build/Workers/createCoplanarPolygonOutlineGeometry.js +31 -31
  65. package/Build/Workers/createCorridorGeometry.js +29 -29
  66. package/Build/Workers/createCorridorOutlineGeometry.js +28 -28
  67. package/Build/Workers/createCylinderGeometry.js +20 -20
  68. package/Build/Workers/createCylinderOutlineGeometry.js +18 -18
  69. package/Build/Workers/createEllipseGeometry.js +26 -26
  70. package/Build/Workers/createEllipseOutlineGeometry.js +19 -19
  71. package/Build/Workers/createEllipsoidGeometry.js +19 -19
  72. package/Build/Workers/createEllipsoidOutlineGeometry.js +18 -18
  73. package/Build/Workers/createFrustumGeometry.js +18 -18
  74. package/Build/Workers/createFrustumOutlineGeometry.js +18 -18
  75. package/Build/Workers/createGeometry.js +24 -24
  76. package/Build/Workers/createGroundPolylineGeometry.js +22 -22
  77. package/Build/Workers/createPlaneGeometry.js +16 -16
  78. package/Build/Workers/createPlaneOutlineGeometry.js +15 -15
  79. package/Build/Workers/createPolygonGeometry.js +32 -32
  80. package/Build/Workers/createPolygonOutlineGeometry.js +30 -30
  81. package/Build/Workers/createPolylineGeometry.js +25 -25
  82. package/Build/Workers/createPolylineVolumeGeometry.js +31 -31
  83. package/Build/Workers/createPolylineVolumeOutlineGeometry.js +27 -27
  84. package/Build/Workers/createRectangleGeometry.js +27 -27
  85. package/Build/Workers/createRectangleOutlineGeometry.js +20 -20
  86. package/Build/Workers/createSimplePolylineGeometry.js +23 -23
  87. package/Build/Workers/createSphereGeometry.js +19 -19
  88. package/Build/Workers/createSphereOutlineGeometry.js +18 -18
  89. package/Build/Workers/createTaskProcessorWorker.js +3 -3
  90. package/Build/Workers/createVectorTileClampedPolylines.js +14 -14
  91. package/Build/Workers/createVectorTileGeometries.js +24 -24
  92. package/Build/Workers/createVectorTilePoints.js +13 -13
  93. package/Build/Workers/createVectorTilePolygons.js +22 -22
  94. package/Build/Workers/createVectorTilePolylines.js +14 -14
  95. package/Build/Workers/createVerticesFromCesium3DTilesTerrain.js +25 -25
  96. package/Build/Workers/createVerticesFromGoogleEarthEnterpriseBuffer.js +22 -22
  97. package/Build/Workers/createVerticesFromHeightmap.js +22 -22
  98. package/Build/Workers/createVerticesFromQuantizedTerrainMesh.js +20 -20
  99. package/Build/Workers/createWallGeometry.js +25 -25
  100. package/Build/Workers/createWallOutlineGeometry.js +24 -24
  101. package/Build/Workers/decodeDraco.js +10 -10
  102. package/Build/Workers/decodeGoogleEarthEnterprisePacket.js +5 -5
  103. package/Build/Workers/decodeI3S.js +12 -12
  104. package/Build/Workers/gaussianSplatSorter.js +4 -4
  105. package/Build/Workers/gaussianSplatTextureGenerator.js +4 -4
  106. package/Build/Workers/incrementallyBuildTerrainPicker.js +10 -10
  107. package/Build/Workers/transcodeKTX2.js +6 -6
  108. package/Build/Workers/transferTypedArrayTest.js +1 -1
  109. package/Build/Workers/upsampleQuantizedTerrainMesh.js +22 -22
  110. package/Build/Workers/upsampleVerticesFromCesium3DTilesTerrain.js +25 -25
  111. package/Source/Core/BoundingSphere.js +1 -1
  112. package/Source/Core/Cartesian2.js +7 -5
  113. package/Source/Core/Cartesian3.js +8 -6
  114. package/Source/Core/Cartesian4.js +8 -6
  115. package/Source/Core/Color.js +5 -3
  116. package/Source/Core/DeveloperError.js +2 -1
  117. package/Source/Core/ITwinPlatform.js +1 -0
  118. package/Source/Core/Ion.js +1 -1
  119. package/Source/Core/S2Cell.js +2 -2
  120. package/Source/Core/TerrainMesh.js +10 -1
  121. package/Source/Core/TerrainPicker.js +1 -0
  122. package/Source/Core/WallGeometry.js +1 -1
  123. package/Source/Core/WallOutlineGeometry.js +1 -1
  124. package/Source/Core/assert.js +27 -0
  125. package/Source/Core/globalTypes.js +4 -0
  126. package/Source/DataSources/CzmlDataSource.js +9 -0
  127. package/Source/DataSources/PathGraphics.js +13 -0
  128. package/Source/DataSources/PathVisualizer.js +330 -18
  129. package/Source/DataSources/StaticGeometryColorBatch.js +7 -2
  130. package/Source/DataSources/StaticGeometryPerMaterialBatch.js +7 -2
  131. package/Source/DataSources/StaticGroundGeometryPerMaterialBatch.js +3 -1
  132. package/Source/DataSources/StaticGroundPolylinePerMaterialBatch.js +3 -1
  133. package/Source/Renderer/Context.js +1 -1
  134. package/Source/Renderer/RenderState.js +2 -2
  135. package/Source/Renderer/VertexArray.js +80 -0
  136. package/Source/Scene/ArcGisMapService.js +1 -1
  137. package/Source/Scene/BillboardCollection.js +65 -364
  138. package/Source/Scene/BufferPoint.js +148 -0
  139. package/Source/Scene/BufferPointCollection.js +135 -0
  140. package/Source/Scene/BufferPointMaterial.js +95 -0
  141. package/Source/Scene/BufferPolygon.js +516 -0
  142. package/Source/Scene/BufferPolygonCollection.js +368 -0
  143. package/Source/Scene/BufferPolygonMaterial.js +41 -0
  144. package/Source/Scene/BufferPolyline.js +185 -0
  145. package/Source/Scene/BufferPolylineCollection.js +136 -0
  146. package/Source/Scene/BufferPolylineMaterial.js +93 -0
  147. package/Source/Scene/BufferPrimitive.js +347 -0
  148. package/Source/Scene/BufferPrimitiveCollection.js +702 -0
  149. package/Source/Scene/BufferPrimitiveMaterial.js +140 -0
  150. package/Source/Scene/Cesium3DTileContent.js +5 -1
  151. package/Source/Scene/Cesium3DTileContentFactory.js +9 -0
  152. package/Source/Scene/Cesium3DTileStyle.js +22 -0
  153. package/Source/Scene/Cesium3DTileset.js +1 -0
  154. package/Source/Scene/ClippingPolygonCollection.js +270 -101
  155. package/Source/Scene/CubeMapPanorama.js +4 -4
  156. package/Source/Scene/GaussianSplat3DTileContent.js +40 -46
  157. package/Source/Scene/GaussianSplatPrimitive.js +413 -70
  158. package/Source/Scene/GetFeatureInfoFormat.js +3 -3
  159. package/Source/Scene/GltfLoader.js +227 -34
  160. package/Source/Scene/GltfSpzLoader.js +109 -0
  161. package/Source/Scene/ITwinData.js +1 -0
  162. package/Source/Scene/Label.js +32 -0
  163. package/Source/Scene/LabelCollection.js +14 -13
  164. package/Source/Scene/Material.js +2 -0
  165. package/Source/Scene/Megatexture.js +1 -0
  166. package/Source/Scene/MetadataClassProperty.js +24 -10
  167. package/Source/Scene/MetadataComponentType.js +115 -23
  168. package/Source/Scene/Model/CustomShaderPipelineStage.js +3 -1
  169. package/Source/Scene/Model/EdgeVisibilityPipelineStage.js +761 -263
  170. package/Source/Scene/Model/MaterialPipelineStage.js +147 -0
  171. package/Source/Scene/Model/MetadataPipelineStage.js +3 -3
  172. package/Source/Scene/Model/ModelDrawCommand.js +6 -0
  173. package/Source/Scene/Model/ModelReader.js +1 -1
  174. package/Source/Scene/Model/ModelUtility.js +3 -1
  175. package/Source/Scene/Model/createVectorTileBuffersFromModelComponents.js +330 -0
  176. package/Source/Scene/ModelComponents.js +1632 -1542
  177. package/Source/Scene/PickedMetadataInfo.js +1 -0
  178. package/Source/Scene/ScreenSpaceCameraController.js +13 -0
  179. package/Source/Scene/SkyBox.js +17 -8
  180. package/Source/Scene/TileBoundingRegion.js +1 -1
  181. package/Source/Scene/TileBoundingS2Cell.js +1 -1
  182. package/Source/Scene/TileBoundingSphere.js +1 -1
  183. package/Source/Scene/TweenCollection.js +7 -0
  184. package/Source/Scene/VectorGltf3DTileContent.js +382 -0
  185. package/Source/Scene/VertexAttributeSemantic.js +14 -0
  186. package/Source/Scene/WebMapServiceImageryProvider.js +2 -2
  187. package/Source/Scene/WebMapTileServiceImageryProvider.js +366 -131
  188. package/Source/Scene/parseStructuralMetadata.js +97 -37
  189. package/Source/Scene/renderBufferPointCollection.js +334 -0
  190. package/Source/Scene/renderBufferPolygonCollection.js +386 -0
  191. package/Source/Scene/renderBufferPolylineCollection.js +529 -0
  192. package/Source/Shaders/BillboardCollectionFS.glsl +2 -77
  193. package/Source/Shaders/BillboardCollectionFS.js +2 -77
  194. package/Source/Shaders/BillboardCollectionVS.glsl +2 -60
  195. package/Source/Shaders/BillboardCollectionVS.js +2 -60
  196. package/Source/Shaders/BufferPointMaterialFS.glsl +27 -0
  197. package/Source/Shaders/BufferPointMaterialFS.js +29 -0
  198. package/Source/Shaders/BufferPointMaterialVS.glsl +48 -0
  199. package/Source/Shaders/BufferPointMaterialVS.js +50 -0
  200. package/Source/Shaders/BufferPolygonMaterialFS.glsl +13 -0
  201. package/Source/Shaders/BufferPolygonMaterialFS.js +15 -0
  202. package/Source/Shaders/BufferPolygonMaterialVS.glsl +30 -0
  203. package/Source/Shaders/BufferPolygonMaterialVS.js +32 -0
  204. package/Source/Shaders/BufferPolylineMaterialFS.glsl +13 -0
  205. package/Source/Shaders/BufferPolylineMaterialFS.js +15 -0
  206. package/Source/Shaders/BufferPolylineMaterialVS.glsl +49 -0
  207. package/Source/Shaders/BufferPolylineMaterialVS.js +51 -0
  208. package/Source/Shaders/Model/EdgeVisibilityStageFS.glsl +34 -48
  209. package/Source/Shaders/Model/EdgeVisibilityStageFS.js +34 -49
  210. package/Source/Shaders/Model/EdgeVisibilityStageVS.glsl +98 -0
  211. package/Source/Shaders/Model/EdgeVisibilityStageVS.js +100 -0
  212. package/Source/Shaders/Model/ModelFS.glsl +15 -0
  213. package/Source/Shaders/Model/ModelFS.js +15 -0
  214. package/Source/Shaders/Model/ModelVS.glsl +8 -0
  215. package/Source/Shaders/Model/ModelVS.js +8 -0
  216. package/Source/Shaders/PolygonSignedDistanceFS.glsl +64 -33
  217. package/Source/Shaders/PolygonSignedDistanceFS.js +64 -33
  218. package/Source/Shaders/PrimitiveGaussianSplatVS.glsl +9 -2
  219. package/Source/Shaders/PrimitiveGaussianSplatVS.js +9 -2
  220. package/Source/Shaders/Voxels/VoxelFS.glsl +1 -1
  221. package/Source/Shaders/Voxels/VoxelFS.js +1 -1
  222. package/Source/Workers/decodeDraco.js +3 -0
  223. package/Source/Workers/decodeI3S.js +5 -2
  224. package/index.d.ts +1007 -111
  225. package/index.js +368 -343
  226. package/package.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import Frozen from "../Core/Frozen.js";
2
2
  import Matrix4 from "../Core/Matrix4.js";
3
+ import Matrix3 from "../Core/Matrix3.js";
3
4
  import ModelUtility from "./Model/ModelUtility.js";
4
5
  import GaussianSplatSorter from "./GaussianSplatSorter.js";
5
6
  import GaussianSplatTextureGenerator from "./GaussianSplatTextureGenerator.js";
@@ -36,9 +37,16 @@ import ContextLimits from "../Renderer/ContextLimits.js";
36
37
  import Transforms from "../Core/Transforms.js";
37
38
 
38
39
  const scratchMatrix4A = new Matrix4();
39
- const scratchMatrix4B = new Matrix4();
40
40
  const scratchMatrix4C = new Matrix4();
41
41
  const scratchMatrix4D = new Matrix4();
42
+ const scratchMatrix3 = new Matrix3();
43
+ const scratchTransformQuat = new Quaternion();
44
+ const scratchTransformPosition = new Cartesian3();
45
+ const scratchTransformRotation = new Quaternion();
46
+ const scratchTransformScale = new Cartesian3();
47
+ const TRANSFORM_CACHE_EPSILON = 1e-12;
48
+ const RIGID_TRANSFORM_EPSILON = 1e-5;
49
+ const UNIT_SCALE_FAST_PATH_EPSILON = 1e-7;
42
50
 
43
51
  /**
44
52
  * Runtime state machine for steady-state re-sorting of an already committed snapshot.
@@ -337,6 +345,49 @@ function releaseRetiredTextures(primitive, frameNumber) {
337
345
  primitive._retiredTextures = next;
338
346
  }
339
347
 
348
+ function getSnapshotArrayBuffer(snapshot, key) {
349
+ const value = snapshot?.[key];
350
+ return defined(value) ? value.buffer : undefined;
351
+ }
352
+
353
+ function acquireAggregateScratchBuffer(
354
+ primitive,
355
+ key,
356
+ componentDatatype,
357
+ requiredLength,
358
+ ) {
359
+ let pool = primitive._aggregateScratchBuffers[key];
360
+ if (!defined(pool)) {
361
+ pool = [];
362
+ primitive._aggregateScratchBuffers[key] = pool;
363
+ }
364
+
365
+ const activeBuffer = getSnapshotArrayBuffer(primitive._snapshot, key);
366
+ for (let i = 0; i < pool.length; i++) {
367
+ const candidate = pool[i];
368
+ if (
369
+ candidate.length >= requiredLength &&
370
+ candidate.buffer !== activeBuffer
371
+ ) {
372
+ return candidate;
373
+ }
374
+ }
375
+
376
+ const created = ComponentDatatype.createTypedArray(
377
+ componentDatatype,
378
+ requiredLength,
379
+ );
380
+ pool.push(created);
381
+ return created;
382
+ }
383
+
384
+ function trimAggregateScratchBuffer(buffer, length) {
385
+ if (buffer.length === length) {
386
+ return buffer;
387
+ }
388
+ return buffer.subarray(0, length);
389
+ }
390
+
340
391
  /**
341
392
  * Atomically promotes a fully-built snapshot to be the active splat data for
342
393
  * the primitive. This includes swapping attribute arrays, GPU textures, and
@@ -389,6 +440,13 @@ function commitSnapshot(primitive, snapshot, frameState) {
389
440
  primitive.sphericalHarmonicsTexture = snapshot.sphericalHarmonicsTexture;
390
441
  primitive._lastTextureWidth = snapshot.lastTextureWidth;
391
442
  primitive._lastTextureHeight = snapshot.lastTextureHeight;
443
+ // Commit row-addressing params alongside the texture; the shader must
444
+ // always see the mask/shift that matches the active texture layout.
445
+ primitive._splatRowMask = snapshot.splatRowMask;
446
+ primitive._splatRowShift = snapshot.splatRowShift;
447
+ // Above 1.0 when the previous snapshot hit the hard cap; used in
448
+ // _wrappedUpdate to inflate traversal SSE and reduce tile load.
449
+ primitive._splatBudgetSSEScale = snapshot.splatBudgetSSEScale ?? 1.0;
392
450
  primitive._hasGaussianSplatTexture = defined(snapshot.gaussianSplatTexture);
393
451
  primitive._needsGaussianSplatTexture = false;
394
452
  primitive._gaussianSplatTexturePending = false;
@@ -422,6 +480,72 @@ async function processGeneratedSplatTextureData(
422
480
  ) {
423
481
  try {
424
482
  const splatTextureData = await promise;
483
+ const maxTex = ContextLimits.maximumTextureSize;
484
+
485
+ // Use maximumTextureSize as the texture width; splatsPerRow = maxTex / 2
486
+ // (each splat occupies 2 side-by-side texels). The WASM buffer layout is
487
+ // width-independent, so the raw data is reused as-is.
488
+ const optimalWidth = maxTex;
489
+ let optimalHeight = Math.ceil(snapshot.numSplats / (maxTex / 2));
490
+ const splatRowShift = Math.log2(maxTex / 2);
491
+ const splatRowMask = maxTex / 2 - 1;
492
+
493
+ // Hard cap: >maxTex*(maxTex/2) splats cannot fit in any valid texture.
494
+ if (optimalHeight > maxTex) {
495
+ const originalCount = snapshot.numSplats;
496
+ optimalHeight = maxTex;
497
+ const splatsPerRow = optimalWidth / 2;
498
+ snapshot.numSplats = maxTex * splatsPerRow;
499
+ // Truncate CPU attribute arrays to match numSplats.
500
+ snapshot.positions = snapshot.positions.subarray(
501
+ 0,
502
+ snapshot.numSplats * 3,
503
+ );
504
+ snapshot.rotations = snapshot.rotations.subarray(
505
+ 0,
506
+ snapshot.numSplats * 4,
507
+ );
508
+ snapshot.scales = snapshot.scales.subarray(0, snapshot.numSplats * 3);
509
+ snapshot.colors = snapshot.colors.subarray(0, snapshot.numSplats * 4);
510
+ // shData is allocated independently and must be truncated separately.
511
+ if (defined(snapshot.shData)) {
512
+ const shPerSplat = snapshot.shData.length / originalCount;
513
+ snapshot.shData = snapshot.shData.subarray(
514
+ 0,
515
+ Math.floor(snapshot.numSplats * shPerSplat),
516
+ );
517
+ }
518
+ // Scale up SSE next frame so traversal selects fewer tiles.
519
+ snapshot.splatBudgetSSEScale = originalCount / snapshot.numSplats;
520
+ console.warn(
521
+ `[GaussianSplat][HARD CAP] ${originalCount} splats exceed the maximum texture capacity ` +
522
+ `(${maxTex}\u00d7${splatsPerRow} = ${snapshot.numSplats} splats at width=${optimalWidth}). ` +
523
+ `Rendering only the first ${snapshot.numSplats} splats to avoid a WebGL crash. ` +
524
+ `Increasing maximumScreenSpaceError by ${snapshot.splatBudgetSSEScale.toFixed(2)}x next frame.`,
525
+ );
526
+ } else {
527
+ // Within budget; clear any SSE inflation carried over from a previous cap.
528
+ snapshot.splatBudgetSSEScale = 1.0;
529
+ }
530
+
531
+ // Trim or zero-pad the raw WASM buffer to match the chosen dimensions.
532
+ const requiredLen = optimalWidth * optimalHeight * 4;
533
+ let effectiveData;
534
+ if (requiredLen <= splatTextureData.data.length) {
535
+ effectiveData = splatTextureData.data.subarray(0, requiredLen);
536
+ } else {
537
+ effectiveData = new Uint32Array(requiredLen);
538
+ effectiveData.set(splatTextureData.data);
539
+ }
540
+ const effectiveTextureData = {
541
+ width: optimalWidth,
542
+ height: optimalHeight,
543
+ data: effectiveData,
544
+ };
545
+
546
+ snapshot.splatRowMask = splatRowMask;
547
+ snapshot.splatRowShift = splatRowShift;
548
+
425
549
  if (primitive._pendingSnapshot !== snapshot) {
426
550
  snapshot.state = SnapshotState.BUILDING;
427
551
  return;
@@ -429,29 +553,29 @@ async function processGeneratedSplatTextureData(
429
553
  if (!defined(snapshot.gaussianSplatTexture)) {
430
554
  snapshot.gaussianSplatTexture = createGaussianSplatTexture(
431
555
  frameState.context,
432
- splatTextureData,
556
+ effectiveTextureData,
433
557
  );
434
558
  } else if (
435
- snapshot.lastTextureHeight !== splatTextureData.height ||
436
- snapshot.lastTextureWidth !== splatTextureData.width
559
+ snapshot.lastTextureHeight !== effectiveTextureData.height ||
560
+ snapshot.lastTextureWidth !== effectiveTextureData.width
437
561
  ) {
438
562
  const oldTex = snapshot.gaussianSplatTexture;
439
563
  snapshot.gaussianSplatTexture = createGaussianSplatTexture(
440
564
  frameState.context,
441
- splatTextureData,
565
+ effectiveTextureData,
442
566
  );
443
567
  oldTex.destroy();
444
568
  } else {
445
569
  snapshot.gaussianSplatTexture.copyFrom({
446
570
  source: {
447
- width: splatTextureData.width,
448
- height: splatTextureData.height,
449
- arrayBufferView: splatTextureData.data,
571
+ width: effectiveTextureData.width,
572
+ height: effectiveTextureData.height,
573
+ arrayBufferView: effectiveTextureData.data,
450
574
  },
451
575
  });
452
576
  }
453
- snapshot.lastTextureHeight = splatTextureData.height;
454
- snapshot.lastTextureWidth = splatTextureData.width;
577
+ snapshot.lastTextureHeight = effectiveTextureData.height;
578
+ snapshot.lastTextureWidth = effectiveTextureData.width;
455
579
 
456
580
  if (defined(snapshot.shData) && snapshot.sphericalHarmonicsDegree > 0) {
457
581
  const oldTex = snapshot.sphericalHarmonicsTexture;
@@ -459,28 +583,44 @@ async function processGeneratedSplatTextureData(
459
583
  const dims = snapshot.shCoefficientCount / 3;
460
584
  const splatsPerRow = Math.floor(width / dims);
461
585
  const floatsPerRow = splatsPerRow * (dims * 2);
462
- const texBuf = new Uint32Array(
463
- width * Math.ceil(snapshot.numSplats / splatsPerRow) * 2,
464
- );
465
586
 
466
- let dataIndex = 0;
467
- for (let i = 0; dataIndex < snapshot.shData.length; i += width * 2) {
468
- texBuf.set(
469
- snapshot.shData.subarray(dataIndex, dataIndex + floatsPerRow),
470
- i,
587
+ const shHeight = Math.ceil(snapshot.numSplats / splatsPerRow);
588
+
589
+ // SH texture width is already maxTex and cannot be widened further.
590
+ // When height would exceed the GPU limit, gracefully disable SH for this
591
+ // snapshot and fall back to base color rendering rather than crashing.
592
+ if (shHeight > width) {
593
+ console.warn(
594
+ `[GaussianSplat][SHTexture] ${snapshot.numSplats} splats require SH height ${shHeight} > maxTex ${width}. ` +
595
+ `Disabling spherical harmonics for this snapshot (color-only fallback).`,
471
596
  );
472
- dataIndex += floatsPerRow;
473
- }
474
- snapshot.sphericalHarmonicsTexture = createSphericalHarmonicsTexture(
475
- frameState.context,
476
- {
477
- data: texBuf,
478
- width: width,
479
- height: Math.ceil(snapshot.numSplats / splatsPerRow),
480
- },
481
- );
482
- if (defined(oldTex)) {
483
- oldTex.destroy();
597
+ snapshot.sphericalHarmonicsDegree = 0;
598
+ if (defined(oldTex)) {
599
+ oldTex.destroy();
600
+ }
601
+ snapshot.sphericalHarmonicsTexture = undefined;
602
+ } else {
603
+ const texBuf = new Uint32Array(width * shHeight * 2);
604
+
605
+ let dataIndex = 0;
606
+ for (let i = 0; dataIndex < snapshot.shData.length; i += width * 2) {
607
+ texBuf.set(
608
+ snapshot.shData.subarray(dataIndex, dataIndex + floatsPerRow),
609
+ i,
610
+ );
611
+ dataIndex += floatsPerRow;
612
+ }
613
+ snapshot.sphericalHarmonicsTexture = createSphericalHarmonicsTexture(
614
+ frameState.context,
615
+ {
616
+ data: texBuf,
617
+ width: width,
618
+ height: shHeight,
619
+ },
620
+ );
621
+ if (defined(oldTex)) {
622
+ oldTex.destroy();
623
+ }
484
624
  }
485
625
  }
486
626
 
@@ -713,6 +853,12 @@ function GaussianSplatPrimitive(options) {
713
853
  this._snapshot = undefined;
714
854
  this._pendingSnapshot = undefined;
715
855
  this._retiredTextures = [];
856
+ this._aggregateScratchBuffers = {
857
+ positions: [],
858
+ scales: [],
859
+ rotations: [],
860
+ colors: [],
861
+ };
716
862
 
717
863
  /**
718
864
  * Scratch buffer re-used across frames for aggregating packed spherical
@@ -860,6 +1006,15 @@ function GaussianSplatPrimitive(options) {
860
1006
  new Matrix4(),
861
1007
  );
862
1008
 
1009
+ /**
1010
+ * Cached inverse rotation for SH evaluation, updated each snapshot rebuild.
1011
+ * Converts a world-space view direction to the original GLB Y-up model space
1012
+ * so that spherical harmonic coefficients are evaluated in the correct frame.
1013
+ * @type {Matrix3}
1014
+ * @private
1015
+ */
1016
+ this._shInverseRotation = new Matrix3();
1017
+
863
1018
  /**
864
1019
  * Indicates whether or not the primitive has been destroyed.
865
1020
  * @type {boolean}
@@ -899,6 +1054,22 @@ function GaussianSplatPrimitive(options) {
899
1054
  * @private
900
1055
  */
901
1056
  this._sorterError = undefined;
1057
+
1058
+ // Splat texture row-addressing params; forwarded to the shader as uniforms.
1059
+ // The texture width is always maximumTextureSize (varies by GPU), so these
1060
+ // are computed per-snapshot and initialized here as safe placeholders.
1061
+ this._splatRowMask = 0; // overwritten on first snapshot commit
1062
+ this._splatRowShift = 0; // overwritten on first snapshot commit
1063
+
1064
+ /**
1065
+ * Multiplier applied to maximumScreenSpaceError during tile traversal when
1066
+ * the previous snapshot exceeded the splat texture budget. A value above 1.0
1067
+ * biases traversal toward coarser LODs, lowering the total splat count.
1068
+ * Resets to 1.0 once the splat count is within budget.
1069
+ * @type {number}
1070
+ * @private
1071
+ */
1072
+ this._splatBudgetSSEScale = 1.0;
902
1073
  }
903
1074
 
904
1075
  Object.defineProperties(GaussianSplatPrimitive.prototype, {
@@ -951,14 +1122,25 @@ Object.defineProperties(GaussianSplatPrimitive.prototype, {
951
1122
  });
952
1123
 
953
1124
  /**
954
- * Since we aren't visible at the scene level, we need to wrap the tileset update
955
- * so we not only get called but ensure we update immediately after the tileset.
1125
+ * Replaces the tileset's own update function so this primitive is updated
1126
+ * immediately after the tileset traversal, within the same frame. When
1127
+ * _splatBudgetSSEScale is above 1.0, maximumScreenSpaceError is inflated
1128
+ * for the duration of the traversal to reduce the number of tiles selected.
956
1129
  * @param {FrameState} frameState
957
1130
  * @private
958
- *
959
1131
  */
960
1132
  GaussianSplatPrimitive.prototype._wrappedUpdate = function (frameState) {
961
- this._baseTilesetUpdate.call(this._tileset, frameState);
1133
+ const tileset = this._tileset;
1134
+ if (this._splatBudgetSSEScale !== 1.0) {
1135
+ // Inflate SSE for this traversal only; the original value is restored
1136
+ // immediately so the user-visible tileset property is never permanently changed.
1137
+ const originalSSE = tileset.maximumScreenSpaceError;
1138
+ tileset.maximumScreenSpaceError *= this._splatBudgetSSEScale;
1139
+ this._baseTilesetUpdate.call(tileset, frameState);
1140
+ tileset.maximumScreenSpaceError = originalSSE;
1141
+ } else {
1142
+ this._baseTilesetUpdate.call(tileset, frameState);
1143
+ }
962
1144
  this.update(frameState);
963
1145
  };
964
1146
 
@@ -982,6 +1164,7 @@ GaussianSplatPrimitive.prototype.destroy = function () {
982
1164
  this._retiredTextures = [];
983
1165
  this._pendingSnapshot = undefined;
984
1166
  this._snapshot = undefined;
1167
+ this._aggregateScratchBuffers = undefined;
985
1168
  this.gaussianSplatTexture = undefined;
986
1169
  this.sphericalHarmonicsTexture = undefined;
987
1170
 
@@ -1065,20 +1248,95 @@ GaussianSplatPrimitive.transformTile = function (tile) {
1065
1248
  computedModelMatrix,
1066
1249
  );
1067
1250
 
1068
- const toGlobal = Matrix4.multiply(
1069
- tile.tileset.modelMatrix,
1070
- rootTransform,
1071
- scratchMatrix4B,
1072
- );
1073
- const toLocal = Matrix4.inverse(toGlobal, scratchMatrix4C);
1251
+ // toLocal is inverse(rootTransform) only. tileset.modelMatrix is already
1252
+ // factored into computedModelMatrix via tile.computedTransform, so its effect
1253
+ // is baked directly into the splat values rather than split into the draw
1254
+ // command's modelMatrix. This keeps czm_view * modelMatrix numerically small,
1255
+ // avoiding float32 precision loss at ECEF-scale translations.
1256
+ const toLocal = Matrix4.inverse(rootTransform, scratchMatrix4C);
1074
1257
  const transform = Matrix4.multiplyTransformation(
1075
1258
  toLocal,
1076
1259
  computedModelMatrix,
1077
1260
  scratchMatrix4A,
1078
1261
  );
1262
+ const cachedTransform = tile.content._lastSplatTransform;
1263
+ if (
1264
+ tile.content._transformed &&
1265
+ defined(cachedTransform) &&
1266
+ Matrix4.equalsEpsilon(transform, cachedTransform, TRANSFORM_CACHE_EPSILON)
1267
+ ) {
1268
+ return;
1269
+ }
1079
1270
  const positions = tile.content.positions;
1080
1271
  const rotations = tile.content.rotations;
1081
1272
  const scales = tile.content.scales;
1273
+
1274
+ // Extract the rotation quaternion from transform once, before the per-splat
1275
+ // loop. The columns of transform's upper-left 3x3 have magnitude ≈ 1 (rigid
1276
+ // body placement), so normalizing is numerically stable. We cannot decompose
1277
+ // the per-splat combined matrix (transform × TRS_i) instead, because each
1278
+ // splat's scale can be very small, causing catastrophic cancellation when
1279
+ // dividing to recover a pure rotation matrix.
1280
+ const col0Len = Math.sqrt(
1281
+ transform[0] * transform[0] +
1282
+ transform[1] * transform[1] +
1283
+ transform[2] * transform[2],
1284
+ );
1285
+ const col1Len = Math.sqrt(
1286
+ transform[4] * transform[4] +
1287
+ transform[5] * transform[5] +
1288
+ transform[6] * transform[6],
1289
+ );
1290
+ const col2Len = Math.sqrt(
1291
+ transform[8] * transform[8] +
1292
+ transform[9] * transform[9] +
1293
+ transform[10] * transform[10],
1294
+ );
1295
+ scratchMatrix3[0] = transform[0] / col0Len;
1296
+ scratchMatrix3[1] = transform[1] / col0Len;
1297
+ scratchMatrix3[2] = transform[2] / col0Len;
1298
+ scratchMatrix3[3] = transform[4] / col1Len;
1299
+ scratchMatrix3[4] = transform[5] / col1Len;
1300
+ scratchMatrix3[5] = transform[6] / col1Len;
1301
+ scratchMatrix3[6] = transform[8] / col2Len;
1302
+ scratchMatrix3[7] = transform[9] / col2Len;
1303
+ scratchMatrix3[8] = transform[10] / col2Len;
1304
+ const dot01 =
1305
+ scratchMatrix3[0] * scratchMatrix3[3] +
1306
+ scratchMatrix3[1] * scratchMatrix3[4] +
1307
+ scratchMatrix3[2] * scratchMatrix3[5];
1308
+ const dot02 =
1309
+ scratchMatrix3[0] * scratchMatrix3[6] +
1310
+ scratchMatrix3[1] * scratchMatrix3[7] +
1311
+ scratchMatrix3[2] * scratchMatrix3[8];
1312
+ const dot12 =
1313
+ scratchMatrix3[3] * scratchMatrix3[6] +
1314
+ scratchMatrix3[4] * scratchMatrix3[7] +
1315
+ scratchMatrix3[5] * scratchMatrix3[8];
1316
+ const hasUnitScale =
1317
+ Math.abs(col0Len - 1.0) <= UNIT_SCALE_FAST_PATH_EPSILON &&
1318
+ Math.abs(col1Len - 1.0) <= UNIT_SCALE_FAST_PATH_EPSILON &&
1319
+ Math.abs(col2Len - 1.0) <= UNIT_SCALE_FAST_PATH_EPSILON;
1320
+ const isOrthogonal =
1321
+ Math.abs(dot01) <= RIGID_TRANSFORM_EPSILON &&
1322
+ Math.abs(dot02) <= RIGID_TRANSFORM_EPSILON &&
1323
+ Math.abs(dot12) <= RIGID_TRANSFORM_EPSILON;
1324
+ const determinant =
1325
+ scratchMatrix3[0] *
1326
+ (scratchMatrix3[4] * scratchMatrix3[8] -
1327
+ scratchMatrix3[5] * scratchMatrix3[7]) -
1328
+ scratchMatrix3[3] *
1329
+ (scratchMatrix3[1] * scratchMatrix3[8] -
1330
+ scratchMatrix3[2] * scratchMatrix3[7]) +
1331
+ scratchMatrix3[6] *
1332
+ (scratchMatrix3[1] * scratchMatrix3[5] -
1333
+ scratchMatrix3[2] * scratchMatrix3[4]);
1334
+ const useFastPath =
1335
+ hasUnitScale &&
1336
+ isOrthogonal &&
1337
+ Math.abs(determinant - 1.0) <= RIGID_TRANSFORM_EPSILON;
1338
+ Quaternion.fromRotationMatrix(scratchMatrix3, scratchTransformQuat);
1339
+ Quaternion.normalize(scratchTransformQuat, scratchTransformQuat);
1082
1340
  const attributePositions = ModelUtility.getAttributeBySemantic(
1083
1341
  gltfPrimitive,
1084
1342
  VertexAttributeSemantic.POSITION,
@@ -1094,9 +1352,9 @@ GaussianSplatPrimitive.transformTile = function (tile) {
1094
1352
  VertexAttributeSemantic.SCALE,
1095
1353
  ).typedArray;
1096
1354
 
1097
- const position = new Cartesian3();
1098
- const rotation = new Quaternion();
1099
- const scale = new Cartesian3();
1355
+ const position = scratchTransformPosition;
1356
+ const rotation = scratchTransformRotation;
1357
+ const scale = scratchTransformScale;
1100
1358
  for (let i = 0; i < attributePositions.length / 3; ++i) {
1101
1359
  position.x = attributePositions[i * 3];
1102
1360
  position.y = attributePositions[i * 3 + 1];
@@ -1111,18 +1369,31 @@ GaussianSplatPrimitive.transformTile = function (tile) {
1111
1369
  scale.y = attributeScales[i * 3 + 1];
1112
1370
  scale.z = attributeScales[i * 3 + 2];
1113
1371
 
1114
- Matrix4.fromTranslationQuaternionRotationScale(
1115
- position,
1116
- rotation,
1117
- scale,
1118
- scratchMatrix4C,
1119
- );
1372
+ if (useFastPath) {
1373
+ Matrix4.multiplyByPoint(transform, position, position);
1374
+ Quaternion.multiply(scratchTransformQuat, rotation, rotation);
1375
+ Quaternion.normalize(rotation, rotation);
1376
+ } else {
1377
+ Matrix4.fromTranslationQuaternionRotationScale(
1378
+ position,
1379
+ rotation,
1380
+ scale,
1381
+ scratchMatrix4D,
1382
+ );
1120
1383
 
1121
- Matrix4.multiplyTransformation(transform, scratchMatrix4C, scratchMatrix4C);
1384
+ Matrix4.multiplyTransformation(
1385
+ transform,
1386
+ scratchMatrix4D,
1387
+ scratchMatrix4D,
1388
+ );
1122
1389
 
1123
- Matrix4.getTranslation(scratchMatrix4C, position);
1124
- Matrix4.getRotation(scratchMatrix4C, rotation);
1125
- Matrix4.getScale(scratchMatrix4C, scale);
1390
+ Matrix4.getTranslation(scratchMatrix4D, position);
1391
+ Matrix4.getScale(scratchMatrix4D, scale);
1392
+ // rotation still holds the original splat quaternion from attributeRotations.
1393
+ // Apply the transform's rotation by left-multiplying the transform quaternion.
1394
+ Quaternion.multiply(scratchTransformQuat, rotation, rotation);
1395
+ Quaternion.normalize(rotation, rotation);
1396
+ }
1126
1397
 
1127
1398
  positions[i * 3] = position.x;
1128
1399
  positions[i * 3 + 1] = position.y;
@@ -1137,6 +1408,11 @@ GaussianSplatPrimitive.transformTile = function (tile) {
1137
1408
  scales[i * 3 + 1] = scale.y;
1138
1409
  scales[i * 3 + 2] = scale.z;
1139
1410
  }
1411
+ tile.content._lastSplatTransform = Matrix4.clone(
1412
+ transform,
1413
+ tile.content._lastSplatTransform,
1414
+ );
1415
+ tile.content._transformed = true;
1140
1416
  };
1141
1417
 
1142
1418
  /**
@@ -1243,10 +1519,21 @@ GaussianSplatPrimitive.buildGSplatDrawCommand = function (
1243
1519
 
1244
1520
  const uniformMap = renderResources.uniformMap;
1245
1521
 
1522
+ // Row-addressing uniforms: read from primitive each draw so they stay in sync
1523
+ // with the texture width chosen for the current snapshot.
1524
+ shaderBuilder.addUniform("int", "u_splatRowMask", ShaderDestination.VERTEX);
1525
+ shaderBuilder.addUniform("int", "u_splatRowShift", ShaderDestination.VERTEX);
1526
+
1246
1527
  const textureCache = primitive.gaussianSplatTexture;
1247
1528
  uniformMap.u_splatAttributeTexture = function () {
1248
1529
  return textureCache;
1249
1530
  };
1531
+ uniformMap.u_splatRowMask = function () {
1532
+ return primitive._splatRowMask;
1533
+ };
1534
+ uniformMap.u_splatRowShift = function () {
1535
+ return primitive._splatRowShift;
1536
+ };
1250
1537
 
1251
1538
  if (primitive._sphericalHarmonicsDegree > 0) {
1252
1539
  shaderBuilder.addDefine(
@@ -1272,17 +1559,12 @@ GaussianSplatPrimitive.buildGSplatDrawCommand = function (
1272
1559
  };
1273
1560
 
1274
1561
  uniformMap.u_inverseModelRotation = function () {
1275
- const tileset = primitive._tileset;
1276
- const modelMatrix = Matrix4.multiply(
1277
- tileset.modelMatrix,
1278
- primitive._rootTransform,
1279
- scratchMatrix4A,
1280
- );
1281
- const inverseModelRotation = Matrix4.getRotation(
1282
- Matrix4.inverse(modelMatrix, scratchMatrix4C),
1283
- scratchMatrix4D,
1284
- );
1285
- return inverseModelRotation;
1562
+ // SH coefficients are encoded in the GLB Y-up training space. To evaluate
1563
+ // them the world-space view direction must be rotated by
1564
+ // inverse(computedTransform × axisCorrectionMatrix × worldTransform).
1565
+ // This matrix is pre-computed each snapshot rebuild and stored on the
1566
+ // primitive so the uniform closure just returns the cached value.
1567
+ return primitive._shInverseRotation;
1286
1568
  };
1287
1569
 
1288
1570
  uniformMap.u_splitDirection = function () {
@@ -1357,8 +1639,11 @@ GaussianSplatPrimitive.buildGSplatDrawCommand = function (
1357
1639
 
1358
1640
  primitive._vertexArrayLen = primitive._indexes.length;
1359
1641
 
1360
- const modelMatrix = Matrix4.multiply(
1361
- tileset.modelMatrix,
1642
+ // The draw command uses rootTransform as its modelMatrix. tileset.modelMatrix
1643
+ // is baked into the splat positions by transformTile and must not appear here
1644
+ // as well. This keeps czm_view * modelMatrix numerically small (ENU frame),
1645
+ // avoiding float32 precision loss from ECEF-scale translations.
1646
+ const modelMatrix = Matrix4.clone(
1362
1647
  primitive._rootTransform,
1363
1648
  primitive._drawCommandModelMatrix,
1364
1649
  );
@@ -1489,36 +1774,88 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
1489
1774
  }
1490
1775
 
1491
1776
  const tiles = tileset._selectedTiles;
1777
+
1778
+ // Rebuild the ENU origin from the current tileset world center so that
1779
+ // baked splat positions remain in a numerically small (meter-scale) local
1780
+ // frame, regardless of the current tileset.modelMatrix value.
1781
+ this._rootTransform = Transforms.eastNorthUpToFixedFrame(
1782
+ tileset.boundingSphere.center,
1783
+ );
1784
+
1785
+ // Compute the SH inverse rotation from the first tile's coordinate frame.
1786
+ // SH coefficients are encoded in the GLB Y-up training space. To evaluate
1787
+ // them correctly the view direction must be transformed by
1788
+ // inverse(computedTransform × axisCorrectionMatrix × worldTransform).
1789
+ // All tiles in a typical GS tileset share the same root coordinate frame,
1790
+ // so using the first tile is sufficient.
1791
+ {
1792
+ const ft = tiles[0];
1793
+ const shFwd = Matrix4.multiplyTransformation(
1794
+ ft.computedTransform,
1795
+ this._axisCorrectionMatrix,
1796
+ scratchMatrix4C,
1797
+ );
1798
+ Matrix4.multiplyTransformation(
1799
+ shFwd,
1800
+ ft.content.worldTransform ?? Matrix4.IDENTITY,
1801
+ shFwd,
1802
+ );
1803
+ Matrix4.getRotation(
1804
+ Matrix4.inverse(shFwd, shFwd),
1805
+ this._shInverseRotation,
1806
+ );
1807
+ }
1808
+
1809
+ for (const tile of tiles) {
1810
+ GaussianSplatPrimitive.transformTile(tile);
1811
+ }
1812
+
1492
1813
  const totalElements = tiles.reduce(
1493
1814
  (total, tile) => total + tile.content.pointsLength,
1494
1815
  0,
1495
1816
  );
1496
1817
  const aggregateAttributeValues = (
1818
+ key,
1497
1819
  componentDatatype,
1498
1820
  getAttributeCallback,
1499
1821
  numberOfComponents,
1500
1822
  ) => {
1501
1823
  let aggregate;
1502
1824
  let offset = 0;
1825
+ let requiredLength = 0;
1503
1826
  for (const tile of tiles) {
1504
- const content = tile.content;
1505
- const attribute = getAttributeCallback(content);
1827
+ const attribute = getAttributeCallback(tile.content);
1506
1828
  const componentsPerAttribute = defined(numberOfComponents)
1507
1829
  ? numberOfComponents
1508
1830
  : AttributeType.getNumberOfComponents(attribute.type);
1509
1831
  const buffer = defined(attribute.typedArray)
1510
1832
  ? attribute.typedArray
1511
1833
  : attribute;
1834
+ requiredLength += buffer.length;
1512
1835
  if (!defined(aggregate)) {
1513
- aggregate = ComponentDatatype.createTypedArray(
1836
+ aggregate = acquireAggregateScratchBuffer(
1837
+ this,
1838
+ key,
1514
1839
  componentDatatype,
1515
1840
  totalElements * componentsPerAttribute,
1516
1841
  );
1517
1842
  }
1843
+ }
1844
+
1845
+ if (!defined(aggregate)) {
1846
+ return ComponentDatatype.createTypedArray(componentDatatype, 0);
1847
+ }
1848
+
1849
+ for (const tile of tiles) {
1850
+ const content = tile.content;
1851
+ const attribute = getAttributeCallback(content);
1852
+ const buffer = defined(attribute.typedArray)
1853
+ ? attribute.typedArray
1854
+ : attribute;
1518
1855
  aggregate.set(buffer, offset);
1519
1856
  offset += buffer.length;
1520
1857
  }
1521
- return aggregate;
1858
+ return trimAggregateScratchBuffer(aggregate, requiredLength);
1522
1859
  };
1523
1860
 
1524
1861
  const aggregateShData = () => {
@@ -1576,24 +1913,28 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
1576
1913
  };
1577
1914
 
1578
1915
  const positions = aggregateAttributeValues(
1916
+ "positions",
1579
1917
  ComponentDatatype.FLOAT,
1580
1918
  (content) => content.positions,
1581
1919
  3,
1582
1920
  );
1583
1921
 
1584
1922
  const scales = aggregateAttributeValues(
1923
+ "scales",
1585
1924
  ComponentDatatype.FLOAT,
1586
1925
  (content) => content.scales,
1587
1926
  3,
1588
1927
  );
1589
1928
 
1590
1929
  const rotations = aggregateAttributeValues(
1930
+ "rotations",
1591
1931
  ComponentDatatype.FLOAT,
1592
1932
  (content) => content.rotations,
1593
1933
  4,
1594
1934
  );
1595
1935
 
1596
1936
  const colors = aggregateAttributeValues(
1937
+ "colors",
1597
1938
  ComponentDatatype.UNSIGNED_BYTE,
1598
1939
  (content) =>
1599
1940
  ModelUtility.getAttributeBySemantic(
@@ -1625,6 +1966,8 @@ GaussianSplatPrimitive.prototype.update = function (frameState) {
1625
1966
  sphericalHarmonicsTexture: undefined,
1626
1967
  lastTextureWidth: 0,
1627
1968
  lastTextureHeight: 0,
1969
+ splatRowMask: 0, // set by processGeneratedSplatTextureData
1970
+ splatRowShift: 0, // set by processGeneratedSplatTextureData
1628
1971
  state: SnapshotState.BUILDING,
1629
1972
  };
1630
1973