智慧水务管理系统 - 精河县供水工程综合管理平台

GaussianSplat3DTileContent.js 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778
  1. import defined from "../Core/defined.js";
  2. import Resource from "../Core/Resource.js";
  3. import GltfLoader from "./GltfLoader.js";
  4. import RuntimeError from "../Core/RuntimeError.js";
  5. import Axis from "./Axis.js";
  6. import GaussianSplatPrimitive from "./GaussianSplatPrimitive.js";
  7. import destroyObject from "../Core/destroyObject.js";
  8. import ModelUtility from "./Model/ModelUtility.js";
  9. import VertexAttributeSemantic from "./VertexAttributeSemantic.js";
  10. import deprecationWarning from "../Core/deprecationWarning.js";
  11. /** @import Cesium3DTileContent from "./Cesium3DTileContent.js"; */
  12. /**
  13. * Represents the contents of a glTF or glb using the {@link https://github.com/CesiumGS/glTF/tree/draft-splat-spz/extensions/2.0/Khronos/KHR_gaussian_splatting | KHR_gaussian_splatting} and {@link https://github.com/CesiumGS/glTF/tree/draft-splat-spz/extensions/2.0/Khronos/KHR_gaussian_splatting_compression_spz_2 | KHR_gaussian_splatting_compression_spz_2} extensions.
  14. * <p>
  15. * Implements the {@link Cesium3DTileContent} interface.
  16. * </p>
  17. *
  18. * @implements Cesium3DTileContent
  19. */
  20. class GaussianSplat3DTileContent {
  21. constructor(loader, tileset, tile, resource) {
  22. this._tileset = tileset;
  23. this._tile = tile;
  24. this._resource = resource;
  25. this._loader = loader;
  26. if (!defined(this._tileset.gaussianSplatPrimitive)) {
  27. this._tileset.gaussianSplatPrimitive = new GaussianSplatPrimitive({
  28. tileset: this._tileset,
  29. });
  30. }
  31. /**
  32. * Local copy of the position attribute buffer transformed into root tile space.
  33. * The original glTF attribute data is kept untouched so rebuilds can re-apply
  34. * transforms from the source coordinates.
  35. * @type {undefined|Float32Array}
  36. * @private
  37. */
  38. this._positions = undefined;
  39. /**
  40. * Local copy of the rotation attribute buffer transformed into root tile space.
  41. * @type {undefined|Float32Array}
  42. * @private
  43. */
  44. this._rotations = undefined;
  45. /**
  46. * Local copy of the scale attribute buffer transformed into root tile space.
  47. * @type {undefined|Float32Array}
  48. * @private
  49. */
  50. this._scales = undefined;
  51. /**
  52. * glTF primitive data that contains the Gaussian splat data needed for rendering.
  53. * @type {undefined|Primitive}
  54. * @private
  55. */
  56. this.gltfPrimitive = undefined;
  57. /**
  58. * Transform matrix to turn model coordinates into world coordinates.
  59. * @type {undefined|Matrix4}
  60. * @private
  61. */
  62. this.worldTransform = undefined;
  63. /**
  64. * Gets or sets if any feature's property changed. Used to
  65. * optimized applying a style when a feature's property changed.
  66. * <p>
  67. * This is used to implement the <code>Cesium3DTileContent</code> interface, but is
  68. * not part of the public Cesium API.
  69. * </p>
  70. *
  71. * @type {boolean}
  72. *
  73. * @private
  74. */
  75. this.featurePropertiesDirty = false;
  76. this._metadata = undefined;
  77. this._group = undefined;
  78. this._ready = false;
  79. /**
  80. * Indicates whether or not the local coordinates of the tile have been transformed
  81. * @type {boolean}
  82. * @private
  83. */
  84. this._transformed = false;
  85. /**
  86. * The degree of the spherical harmonics used for the Gaussian splats.
  87. * @type {number}
  88. * @private
  89. */
  90. this._sphericalHarmonicsDegree = 0;
  91. /**
  92. * The number of spherical harmonic coefficients used for the Gaussian splats.
  93. * @type {number}
  94. * @private
  95. */
  96. this._sphericalHarmonicsCoefficientCount = 0;
  97. /**
  98. * Spherical Harmonic data that has been packed for use in a texture or shader.
  99. * @type {undefined|Uint32Array}
  100. * @private
  101. */
  102. this._packedSphericalHarmonicsData = undefined;
  103. /**
  104. * Cached local-space-to-root transform used for the last splat bake.
  105. * When unchanged, the transformed buffers can be reused directly.
  106. * @type {undefined|Matrix4}
  107. * @private
  108. */
  109. this._lastSplatTransform = undefined;
  110. }
  111. /**
  112. * Performs checks to ensure that the provided tileset has the Gaussian Splatting extensions.
  113. *
  114. * @param {Cesium3DTileset} tileset The tileset to check for the extensions.
  115. * @returns {boolean} Returns <code>true</code> if the necessary extensions are included in the tileset.
  116. * @static
  117. */
  118. static tilesetRequiresGaussianSplattingExt(tileset) {
  119. let hasGaussianSplatExtension = false;
  120. if (tileset.isGltfExtensionRequired instanceof Function) {
  121. hasGaussianSplatExtension =
  122. tileset.isGltfExtensionRequired("KHR_gaussian_splatting") &&
  123. tileset.isGltfExtensionRequired(
  124. "KHR_gaussian_splatting_compression_spz_2",
  125. );
  126. if (
  127. tileset.isGltfExtensionRequired("KHR_spz_gaussian_splats_compression")
  128. ) {
  129. deprecationWarning(
  130. "KHR_spz_gaussian_splats_compression",
  131. "Support for the original KHR_spz_gaussian_splats_compression extension has been removed in favor " +
  132. "of the up to date KHR_gaussian_splatting and KHR_gaussian_splatting_compression_spz_2 extensions" +
  133. "\n\nPlease retile your tileset with the KHR_gaussian_splatting and " +
  134. "KHR_gaussian_splatting_compression_spz_2 extensions.",
  135. );
  136. }
  137. }
  138. return hasGaussianSplatExtension;
  139. }
  140. /**
  141. * Gets the number of features in the tile. Currently this is always zero.
  142. *
  143. *
  144. * @type {number}
  145. * @readonly
  146. */
  147. get featuresLength() {
  148. return 0;
  149. }
  150. /**
  151. * Equal to the number of Gaussian splats in the tile. Each splat is represented by a median point and a set of attributes, so we can
  152. * treat this as the number of points in the tile.
  153. *
  154. *
  155. * @type {number}
  156. * @readonly
  157. */
  158. get pointsLength() {
  159. return this.gltfPrimitive.attributes[0].count;
  160. }
  161. /**
  162. * Gets the number of triangles in the tile. Currently this is always zero because Gaussian splats are not represented as triangles in the tile content.
  163. * <p>
  164. *
  165. * @type {number}
  166. * @readonly
  167. */
  168. get trianglesLength() {
  169. return 0;
  170. }
  171. /**
  172. * The number of bytes used by the geometry attributes of this content.
  173. * <p>
  174. * @type {number}
  175. * @readonly
  176. */
  177. get geometryByteLength() {
  178. return 0;
  179. }
  180. /**
  181. * The number of bytes used by the textures of this content.
  182. * <p>
  183. * @type {number}
  184. * @readonly
  185. */
  186. get texturesByteLength() {
  187. const primitive = this._tileset?.gaussianSplatPrimitive;
  188. if (!defined(primitive)) {
  189. return 0;
  190. }
  191. const texture = primitive.gaussianSplatTexture;
  192. const selectedTileLength = primitive.selectedTileLength;
  193. if (!defined(texture) || selectedTileLength === 0) {
  194. return 0;
  195. }
  196. return texture.sizeInBytes / selectedTileLength;
  197. }
  198. /**
  199. * Gets the amount of memory used by the batch table textures and any binary
  200. * metadata properties not accounted for in geometryByteLength or
  201. * texturesByteLength
  202. * <p>
  203. *
  204. * @type {number}
  205. * @readonly
  206. */
  207. get batchTableByteLength() {
  208. return 0;
  209. }
  210. /**
  211. * Gets the array of {@link Cesium3DTileContent} objects for contents that contain other contents, such as composite tiles. The inner contents may in turn have inner contents, such as a composite tile that contains a composite tile.
  212. *
  213. * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/Composite|Composite specification}
  214. *
  215. *
  216. * @type {Array}
  217. * @readonly
  218. */
  219. get innerContents() {
  220. return undefined;
  221. }
  222. /**
  223. * Returns true when the tile's content is ready to render; otherwise false
  224. *
  225. *
  226. * @type {boolean}
  227. * @readonly
  228. */
  229. get ready() {
  230. return this._ready;
  231. }
  232. /**
  233. * Returns true when the tile's content is transformed to world coordinates; otherwise false
  234. * <p>
  235. * @type {boolean}
  236. * @readonly
  237. */
  238. get transformed() {
  239. return this._transformed;
  240. }
  241. /**
  242. * The tileset that this content belongs to.
  243. * <p>
  244. * @type {Cesium3DTileset}
  245. * @readonly
  246. */
  247. get tileset() {
  248. return this._tileset;
  249. }
  250. /**
  251. * The tile that this content belongs to.
  252. * <p>
  253. * @type {Cesium3DTile}
  254. * @readonly
  255. */
  256. get tile() {
  257. return this._tile;
  258. }
  259. /**
  260. * The resource that this content was loaded from.
  261. * <p>
  262. * @type {string}
  263. * @readonly
  264. */
  265. get url() {
  266. return this._resource.getUrlComponent(true);
  267. }
  268. /**
  269. * Gets the batch table for this content.
  270. * <p>
  271. * This is used to implement the <code>Cesium3DTileContent</code> interface, but is
  272. * not part of the public Cesium API.
  273. * </p>
  274. *
  275. * @type {Cesium3DTileBatchTable}
  276. * @readonly
  277. *
  278. * @private
  279. */
  280. get batchTable() {
  281. return undefined;
  282. }
  283. /**
  284. * Gets the metadata for this content, whether it is available explicitly or via
  285. * implicit tiling. If there is no metadata, this property should be undefined.
  286. * <p>
  287. * This is used to implement the <code>Cesium3DTileContent</code> interface, but is
  288. * not part of the public Cesium API.
  289. * </p>
  290. *
  291. * @type {ImplicitMetadataView|undefined}
  292. *
  293. * @private
  294. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  295. */
  296. get metadata() {
  297. return this._metadata;
  298. }
  299. set metadata(value) {
  300. this._metadata = value;
  301. }
  302. /**
  303. * Gets the group for this content if the content has metadata (3D Tiles 1.1) or
  304. * if it uses the <code>3DTILES_metadata</code> extension. If neither are present,
  305. * this property should be undefined.
  306. * <p>
  307. * This is used to implement the <code>Cesium3DTileContent</code> interface, but is
  308. * not part of the public Cesium API.
  309. * </p>
  310. *
  311. * @type {Cesium3DContentGroup|undefined}
  312. *
  313. * @private
  314. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  315. */
  316. get group() {
  317. return this._group;
  318. }
  319. set group(value) {
  320. this._group = value;
  321. }
  322. /**
  323. * Get the transformed positions of this tile's Gaussian splats.
  324. * @type {undefined|Float32Array}
  325. * @private
  326. */
  327. get positions() {
  328. return this._positions;
  329. }
  330. /**
  331. * Get the transformed rotations of this tile's Gaussian splats.
  332. * @type {undefined|Float32Array}
  333. * @private
  334. */
  335. get rotations() {
  336. return this._rotations;
  337. }
  338. /**
  339. * Get the transformed scales of this tile's Gaussian splats.
  340. * @type {undefined|Float32Array}
  341. * @private
  342. */
  343. get scales() {
  344. return this._scales;
  345. }
  346. /**
  347. * The number of spherical harmonic coefficients used for the Gaussian splats.
  348. * @type {number}
  349. * @private
  350. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  351. */
  352. get sphericalHarmonicsCoefficientCount() {
  353. return this._sphericalHarmonicsCoefficientCount;
  354. }
  355. /**
  356. * The degree of the spherical harmonics used for the Gaussian splats.
  357. * @type {number}
  358. * @private
  359. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  360. */
  361. get sphericalHarmonicsDegree() {
  362. return this._sphericalHarmonicsDegree;
  363. }
  364. /**
  365. * The packed spherical harmonic data for the Gaussian splats for use a shader or texture.
  366. * @type {number}
  367. * @private
  368. * @experimental This feature is using part of the 3D Tiles spec that is not final and is subject to change without Cesium's standard deprecation policy.
  369. */
  370. get packedSphericalHarmonicsData() {
  371. return this._packedSphericalHarmonicsData;
  372. }
  373. /**
  374. * Creates a new instance of {@link GaussianSplat3DTileContent} from a glTF or glb resource.
  375. *
  376. * @param {Cesium3DTileset} tileset - The tileset that this content belongs to.
  377. * @param {Cesium3DTile} tile - The tile that this content belongs to.
  378. * @param {Resource|string} resource - The resource or URL of the glTF or glb file.
  379. * @param {object|Uint8Array} gltf - The glTF JSON object or a Uint8Array containing the glb binary data.
  380. * @returns {GaussianSplat3DTileContent} A new GaussianSplat3DTileContent instance.
  381. * @throws {RuntimeError} If the glTF or glb fails to load.
  382. * @private
  383. */
  384. static async fromGltf(tileset, tile, resource, gltf) {
  385. const basePath = resource;
  386. const baseResource = Resource.createIfNeeded(basePath);
  387. const loaderOptions = {
  388. releaseGltfJson: false,
  389. upAxis: Axis.Y,
  390. forwardAxis: Axis.Z,
  391. };
  392. if (defined(gltf.asset)) {
  393. loaderOptions.gltfJson = gltf;
  394. loaderOptions.baseResource = baseResource;
  395. loaderOptions.gltfResource = baseResource;
  396. } else if (gltf instanceof Uint8Array) {
  397. loaderOptions.typedArray = gltf;
  398. loaderOptions.baseResource = baseResource;
  399. loaderOptions.gltfResource = baseResource;
  400. } else {
  401. loaderOptions.gltfResource = Resource.createIfNeeded(gltf);
  402. }
  403. const loader = new GltfLoader(loaderOptions);
  404. try {
  405. await loader.load();
  406. } catch (error) {
  407. loader.destroy();
  408. throw new RuntimeError(`Failed to load glTF: ${error.message}`);
  409. }
  410. return new GaussianSplat3DTileContent(loader, tileset, tile, resource);
  411. }
  412. /**
  413. * Updates the content of the tile and prepares it for rendering.
  414. * @param {Cesium3DTileset}Data attribution
  415. * @param {FrameState} frameState - The current frame state.
  416. * @private
  417. */
  418. update(primitive, frameState) {
  419. const loader = this._loader;
  420. if (this._ready) {
  421. return;
  422. }
  423. frameState.afterRender.push(() => true);
  424. if (!defined(loader)) {
  425. this._ready = true;
  426. return;
  427. }
  428. if (this._resourcesLoaded) {
  429. this.gltfPrimitive = loader.components.scene.nodes[0].primitives[0];
  430. this.worldTransform = loader.components.scene.nodes[0].matrix;
  431. this._ready = true;
  432. // SPZ decode produces Float32Array attributes for these semantics, so
  433. // typedArray.slice() preserves the expected runtime type for current data.
  434. // If future splat encodings use quantized integer attributes here, revisit
  435. // this assumption before relying on the copied array type.
  436. this._positions = ModelUtility.getAttributeBySemantic(
  437. this.gltfPrimitive,
  438. VertexAttributeSemantic.POSITION,
  439. ).typedArray.slice();
  440. this._rotations = ModelUtility.getAttributeBySemantic(
  441. this.gltfPrimitive,
  442. VertexAttributeSemantic.ROTATION,
  443. ).typedArray.slice();
  444. this._scales = ModelUtility.getAttributeBySemantic(
  445. this.gltfPrimitive,
  446. VertexAttributeSemantic.SCALE,
  447. ).typedArray.slice();
  448. const { l, n } = degreeAndCoefFromAttributes(
  449. this.gltfPrimitive.attributes,
  450. );
  451. this._sphericalHarmonicsDegree = l;
  452. this._sphericalHarmonicsCoefficientCount = n;
  453. this._packedSphericalHarmonicsData = packSphericalHarmonicsData(this);
  454. return;
  455. }
  456. this._resourcesLoaded = loader.process(frameState);
  457. }
  458. /**
  459. * Returns whether the feature has this property.
  460. *
  461. * @param {number} batchId The batchId for the feature.
  462. * @param {string} name The case-sensitive name of the property.
  463. * @returns {boolean} <code>true</code> if the feature has this property; otherwise, <code>false</code>.
  464. */
  465. hasProperty(batchId, name) {
  466. return false;
  467. }
  468. /**
  469. * Returns the {@link Cesium3DTileFeature} object for the feature with the
  470. * given <code>batchId</code>. This object is used to get and modify the
  471. * feature's properties.
  472. * <p>
  473. * Features in a tile are ordered by <code>batchId</code>, an index used to retrieve their metadata from the batch table.
  474. * </p>
  475. *
  476. * @see {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/BatchTable}.
  477. *
  478. * @param {number} batchId The batchId for the feature.
  479. * @returns {Cesium3DTileFeature} The corresponding {@link Cesium3DTileFeature} object.
  480. *
  481. * @exception {DeveloperError} batchId must be between zero and {@link Cesium3DTileContent#featuresLength} - 1.
  482. */
  483. getFeature(batchId) {
  484. return undefined;
  485. }
  486. /**
  487. * Called when {@link Cesium3DTileset#debugColorizeTiles} changes.
  488. * <p>
  489. * This is used to implement the <code>Cesium3DTileContent</code> interface, but is
  490. * not part of the public Cesium API.
  491. * </p>
  492. *
  493. * @param {boolean} enabled Whether to enable or disable debug settings.
  494. * @returns {Cesium3DTileFeature} The corresponding {@link Cesium3DTileFeature} object.
  495. * @private
  496. */
  497. applyDebugSettings(enabled, color) {}
  498. /**
  499. * Apply a style to the content
  500. * <p>
  501. * This is used to implement the <code>Cesium3DTileContent</code> interface, but is
  502. * not part of the public Cesium API.
  503. * </p>
  504. *
  505. * @param {Cesium3DTileStyle} style The style.
  506. *
  507. * @private
  508. */
  509. applyStyle(style) {}
  510. /**
  511. * Find an intersection between a ray and the tile content surface that was rendered. The ray must be given in world coordinates.
  512. *
  513. * @param {Ray} ray The ray to test for intersection.
  514. * @param {FrameState} frameState The frame state.
  515. * @param {Cartesian3|undefined} [result] The intersection or <code>undefined</code> if none was found.
  516. * @returns {Cartesian3|undefined} The intersection or <code>undefined</code> if none was found.
  517. *
  518. * @private
  519. */
  520. pick(ray, frameState, result) {
  521. return undefined;
  522. }
  523. /**
  524. * Returns true if this object was destroyed; otherwise, false.
  525. * <br /><br />
  526. * If this object was destroyed, it should not be used; calling any function other than
  527. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  528. * <p>
  529. * This is used to implement the <code>Cesium3DTileContent</code> interface, but is
  530. * not part of the public Cesium API.
  531. * </p>
  532. *
  533. * @returns {boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  534. *
  535. * @see Cesium3DTileContent#destroy
  536. *
  537. * @private
  538. */
  539. isDestroyed() {
  540. return this.isDestroyed;
  541. }
  542. /**
  543. * Frees the resources used by this object.
  544. * @private
  545. */
  546. destroy() {
  547. this.splatPrimitive = undefined;
  548. this._tile = undefined;
  549. this._tileset = undefined;
  550. this._resource = undefined;
  551. this._ready = false;
  552. this._group = undefined;
  553. this._metadata = undefined;
  554. this._resourcesLoaded = false;
  555. this._lastSplatTransform = undefined;
  556. if (defined(this._loader)) {
  557. this._loader.destroy();
  558. this._loader = undefined;
  559. }
  560. return destroyObject(this);
  561. }
  562. }
  563. function getShAttributePrefix(attribute) {
  564. const prefix = attribute.startsWith("KHR_gaussian_splatting:")
  565. ? "KHR_gaussian_splatting:"
  566. : "_";
  567. return `${prefix}SH_DEGREE_`;
  568. }
  569. /**
  570. * Determine Spherical Harmonics degree and coefficient count from attributes
  571. * @param {Attribute[]} attributes - The list of glTF attributes.
  572. * @returns {object} An object containing the degree (l) and coefficient (n).
  573. * @private
  574. */
  575. function degreeAndCoefFromAttributes(attributes) {
  576. const shAttributes = attributes.filter((attr) =>
  577. attr.name.includes("SH_DEGREE_"),
  578. );
  579. switch (shAttributes.length) {
  580. default:
  581. case 0:
  582. return { l: 0, n: 0 };
  583. case 3:
  584. return { l: 1, n: 9 };
  585. case 8:
  586. return { l: 2, n: 24 };
  587. case 15:
  588. return { l: 3, n: 45 };
  589. }
  590. }
  591. /**
  592. * Converts a 32-bit floating point number to a 16-bit floating point number.
  593. * @param {number} float32 input
  594. * @returns {number} Half precision float
  595. * @private
  596. */
  597. const buffer = new ArrayBuffer(4);
  598. const floatView = new Float32Array(buffer);
  599. const intView = new Uint32Array(buffer);
  600. function float32ToFloat16(float32) {
  601. floatView[0] = float32;
  602. const bits = intView[0];
  603. const sign = (bits >> 31) & 0x1;
  604. const exponent = (bits >> 23) & 0xff;
  605. const mantissa = bits & 0x7fffff;
  606. let half;
  607. if (exponent === 0xff) {
  608. half = (sign << 15) | (0x1f << 10) | (mantissa ? 0x200 : 0);
  609. } else if (exponent === 0) {
  610. half = sign << 15;
  611. } else {
  612. const newExponent = exponent - 127 + 15;
  613. if (newExponent >= 31) {
  614. half = (sign << 15) | (0x1f << 10);
  615. } else if (newExponent <= 0) {
  616. half = sign << 15;
  617. } else {
  618. half = (sign << 15) | (newExponent << 10) | (mantissa >>> 13);
  619. }
  620. }
  621. return half;
  622. }
  623. /**
  624. * Extracts the spherical harmonic degree and coefficient from the attribute name.
  625. * @param {string} attribute - The attribute name.
  626. * @returns {object} An object containing the degree (l) and coefficient (n).
  627. * @private
  628. */
  629. function extractSHDegreeAndCoef(attribute) {
  630. const prefix = getShAttributePrefix(attribute);
  631. const separator = "_COEF_";
  632. const lStart = prefix.length;
  633. const coefIndex = attribute.indexOf(separator, lStart);
  634. const l = parseInt(attribute.slice(lStart, coefIndex), 10);
  635. const n = parseInt(attribute.slice(coefIndex + separator.length), 10);
  636. return { l, n };
  637. }
  638. /**
  639. * Packs spherical harmonic data into half-precision floats.
  640. * @param {GaussianSplat3DTileContent} tileContent - The tile content containing the spherical harmonic data.
  641. * @returns {Uint32Array} - The Float16 packed spherical harmonic data.
  642. * @private
  643. */
  644. function packSphericalHarmonicsData(tileContent) {
  645. const degree = tileContent.sphericalHarmonicsDegree;
  646. const coefs = tileContent.sphericalHarmonicsCoefficientCount;
  647. const totalLength = tileContent.pointsLength * (coefs * (2 / 3)); //3 packs into 2
  648. const packedData = new Uint32Array(totalLength);
  649. const shAttributes = tileContent.gltfPrimitive.attributes.filter((attr) =>
  650. attr.name.includes("SH_DEGREE_"),
  651. );
  652. let stride = 0;
  653. const base = [0, 9, 24];
  654. switch (degree) {
  655. case 1:
  656. stride = 9;
  657. break;
  658. case 2:
  659. stride = 24;
  660. break;
  661. case 3:
  662. stride = 45;
  663. break;
  664. }
  665. shAttributes.sort((a, b) => {
  666. if (a.name < b.name) {
  667. return -1;
  668. }
  669. if (a.name > b.name) {
  670. return 1;
  671. }
  672. return 0;
  673. });
  674. const packedStride = stride * (2 / 3);
  675. for (let i = 0; i < shAttributes.length; i++) {
  676. const { l, n } = extractSHDegreeAndCoef(shAttributes[i].name);
  677. for (let j = 0; j < tileContent.pointsLength; j++) {
  678. //interleave the data
  679. const packedBase = (base[l - 1] * 2) / 3;
  680. const idx = j * packedStride + packedBase + n * 2;
  681. const src = j * 3;
  682. packedData[idx] =
  683. float32ToFloat16(shAttributes[i].typedArray[src]) |
  684. (float32ToFloat16(shAttributes[i].typedArray[src + 1]) << 16);
  685. packedData[idx + 1] = float32ToFloat16(
  686. shAttributes[i].typedArray[src + 2],
  687. );
  688. }
  689. }
  690. return packedData;
  691. }
  692. export default GaussianSplat3DTileContent;