PntsLoader.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. import AttributeCompression from "../../Core/AttributeCompression.js";
  2. import Cartesian3 from "../../Core/Cartesian3.js";
  3. import Color from "../../Core/Color.js";
  4. import Check from "../../Core/Check.js";
  5. import ComponentDatatype from "../../Core/ComponentDatatype.js";
  6. import defaultValue from "../../Core/defaultValue.js";
  7. import defined from "../../Core/defined.js";
  8. import DeveloperError from "../../Core/DeveloperError.js";
  9. import Matrix4 from "../../Core/Matrix4.js";
  10. import PrimitiveType from "../../Core/PrimitiveType.js";
  11. import WebGLConstants from "../../Core/WebGLConstants.js";
  12. import MersenneTwister from "mersenne-twister";
  13. import Buffer from "../../Renderer/Buffer.js";
  14. import BufferUsage from "../../Renderer/BufferUsage.js";
  15. import AlphaMode from "../AlphaMode.js";
  16. import AttributeType from "../AttributeType.js";
  17. import Axis from "../Axis.js";
  18. import parseBatchTable from "../parseBatchTable.js";
  19. import DracoLoader from "../DracoLoader.js";
  20. import StructuralMetadata from "../StructuralMetadata.js";
  21. import ResourceLoader from "../ResourceLoader.js";
  22. import ModelComponents from "../ModelComponents.js";
  23. import PntsParser from "../PntsParser.js";
  24. import ResourceLoaderState from "../ResourceLoaderState.js";
  25. import VertexAttributeSemantic from "../VertexAttributeSemantic.js";
  26. const Components = ModelComponents.Components;
  27. const Scene = ModelComponents.Scene;
  28. const Node = ModelComponents.Node;
  29. const Primitive = ModelComponents.Primitive;
  30. const Attribute = ModelComponents.Attribute;
  31. const Quantization = ModelComponents.Quantization;
  32. const FeatureIdAttribute = ModelComponents.FeatureIdAttribute;
  33. const Material = ModelComponents.Material;
  34. const MetallicRoughness = ModelComponents.MetallicRoughness;
  35. /**
  36. * Loads a .pnts point cloud and transcodes it into a {@link ModelComponents}
  37. *
  38. * @alias PntsLoader
  39. * @constructor
  40. * @augments ResourceLoader
  41. * @private
  42. *
  43. * @param {object} options An object containing the following properties
  44. * @param {ArrayBuffer} options.arrayBuffer The array buffer of the pnts contents
  45. * @param {number} [options.byteOffset] The byte offset to the beginning of the pnts contents in the array buffer
  46. * @param {boolean} [options.loadAttributesFor2D=false] If true, load the positions buffer as a typed array for accurately projecting models to 2D.
  47. */
  48. function PntsLoader(options) {
  49. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  50. const arrayBuffer = options.arrayBuffer;
  51. const byteOffset = defaultValue(options.byteOffset, 0);
  52. //>>includeStart('debug', pragmas.debug);
  53. Check.typeOf.object("options.arrayBuffer", arrayBuffer);
  54. //>>includeEnd('debug');
  55. this._arrayBuffer = arrayBuffer;
  56. this._byteOffset = byteOffset;
  57. this._loadAttributesFor2D = defaultValue(options.loadAttributesFor2D, false);
  58. this._parsedContent = undefined;
  59. this._decodePromise = undefined;
  60. this._decodedAttributes = undefined;
  61. this._promise = undefined;
  62. this._error = undefined;
  63. this._state = ResourceLoaderState.UNLOADED;
  64. this._buffers = [];
  65. // The batch table object contains a json and a binary component access using keys of the same name.
  66. this._components = undefined;
  67. this._transform = Matrix4.IDENTITY;
  68. }
  69. if (defined(Object.create)) {
  70. PntsLoader.prototype = Object.create(ResourceLoader.prototype);
  71. PntsLoader.prototype.constructor = PntsLoader;
  72. }
  73. Object.defineProperties(PntsLoader.prototype, {
  74. /**
  75. * The cache key of the resource
  76. *
  77. * @memberof PntsLoader.prototype
  78. *
  79. * @type {string}
  80. * @readonly
  81. * @private
  82. */
  83. cacheKey: {
  84. get: function () {
  85. return undefined;
  86. },
  87. },
  88. /**
  89. * The loaded components.
  90. *
  91. * @memberof PntsLoader.prototype
  92. *
  93. * @type {ModelComponents.Components}
  94. * @readonly
  95. * @private
  96. */
  97. components: {
  98. get: function () {
  99. return this._components;
  100. },
  101. },
  102. /**
  103. * A world-space transform to apply to the primitives.
  104. * See {@link https://github.com/CesiumGS/3d-tiles/tree/main/specification/TileFormats/PointCloud#global-semantics}
  105. *
  106. * @memberof PntsLoader.prototype
  107. *
  108. * @type {Matrix4}
  109. * @readonly
  110. * @private
  111. */
  112. transform: {
  113. get: function () {
  114. return this._transform;
  115. },
  116. },
  117. });
  118. /**
  119. * Loads the resource.
  120. * @returns {Promise<PntsLoader>} A promise which resolves to the loader when the resource loading is completed.
  121. * @private
  122. */
  123. PntsLoader.prototype.load = function () {
  124. if (defined(this._promise)) {
  125. return this._promise;
  126. }
  127. this._parsedContent = PntsParser.parse(this._arrayBuffer, this._byteOffset);
  128. this._state = ResourceLoaderState.PROCESSING;
  129. this._promise = Promise.resolve(this);
  130. };
  131. PntsLoader.prototype.process = function (frameState) {
  132. if (defined(this._error)) {
  133. const error = this._error;
  134. this._error = undefined;
  135. throw error;
  136. }
  137. if (this._state === ResourceLoaderState.READY) {
  138. return true;
  139. }
  140. if (this._state === ResourceLoaderState.PROCESSING) {
  141. if (defined(this._decodePromise)) {
  142. return false;
  143. }
  144. this._decodePromise = decodeDraco(this, frameState.context);
  145. }
  146. return false;
  147. };
  148. function decodeDraco(loader, context) {
  149. const parsedContent = loader._parsedContent;
  150. const draco = parsedContent.draco;
  151. let decodePromise;
  152. if (!defined(draco)) {
  153. // The draco extension wasn't present,
  154. decodePromise = Promise.resolve();
  155. } else {
  156. decodePromise = DracoLoader.decodePointCloud(draco, context);
  157. }
  158. if (!defined(decodePromise)) {
  159. // Could not schedule Draco decoding this frame.
  160. return;
  161. }
  162. loader._decodePromise = decodePromise;
  163. return decodePromise
  164. .then(function (decodeDracoResult) {
  165. if (loader.isDestroyed()) {
  166. return;
  167. }
  168. if (defined(decodeDracoResult)) {
  169. processDracoAttributes(loader, draco, decodeDracoResult);
  170. }
  171. makeComponents(loader, context);
  172. loader._state = ResourceLoaderState.READY;
  173. return loader;
  174. })
  175. .catch(function (error) {
  176. loader.unload();
  177. loader._state = ResourceLoaderState.FAILED;
  178. const errorMessage = "Failed to load Draco pnts";
  179. // This error will be thrown next time process is called;
  180. loader._error = loader.getError(errorMessage, error);
  181. });
  182. }
  183. function processDracoAttributes(loader, draco, result) {
  184. loader._state = ResourceLoaderState.READY;
  185. const parsedContent = loader._parsedContent;
  186. let attribute;
  187. if (defined(result.POSITION)) {
  188. attribute = {
  189. name: "POSITION",
  190. semantic: VertexAttributeSemantic.POSITION,
  191. typedArray: result.POSITION.array,
  192. componentDatatype: ComponentDatatype.FLOAT,
  193. type: AttributeType.VEC3,
  194. isQuantized: false,
  195. };
  196. if (defined(result.POSITION.data.quantization)) {
  197. // Draco quantization range == quantized volume scale - size in meters of the quantized volume
  198. // Internal quantized range is the range of values of the quantized data, e.g. 255 for 8-bit, 1023 for 10-bit, etc
  199. const quantization = result.POSITION.data.quantization;
  200. const range = quantization.range;
  201. const quantizedVolumeScale = Cartesian3.fromElements(range, range, range);
  202. const quantizedVolumeOffset = Cartesian3.unpack(quantization.minValues);
  203. const quantizedRange = (1 << quantization.quantizationBits) - 1.0;
  204. attribute.isQuantized = true;
  205. attribute.quantizedRange = quantizedRange;
  206. attribute.quantizedVolumeOffset = quantizedVolumeOffset;
  207. attribute.quantizedVolumeScale = quantizedVolumeScale;
  208. attribute.quantizedComponentDatatype =
  209. quantizedRange <= 255
  210. ? ComponentDatatype.UNSIGNED_BYTE
  211. : ComponentDatatype.UNSIGNED_SHORT;
  212. attribute.quantizedType = AttributeType.VEC3;
  213. }
  214. parsedContent.positions = attribute;
  215. }
  216. if (defined(result.NORMAL)) {
  217. attribute = {
  218. name: "NORMAL",
  219. semantic: VertexAttributeSemantic.NORMAL,
  220. typedArray: result.NORMAL.array,
  221. componentDatatype: ComponentDatatype.FLOAT,
  222. type: AttributeType.VEC3,
  223. isQuantized: false,
  224. octEncoded: false,
  225. octEncodedZXY: false,
  226. };
  227. if (defined(result.NORMAL.data.quantization)) {
  228. const octEncodedRange =
  229. (1 << result.NORMAL.data.quantization.quantizationBits) - 1.0;
  230. attribute.quantizedRange = octEncodedRange;
  231. attribute.octEncoded = true;
  232. attribute.octEncodedZXY = true;
  233. attribute.quantizedComponentDatatype = ComponentDatatype.UNSIGNED_BYTE;
  234. attribute.quantizedType = AttributeType.VEC2;
  235. }
  236. parsedContent.normals = attribute;
  237. }
  238. if (defined(result.RGBA)) {
  239. parsedContent.colors = {
  240. name: "COLOR",
  241. semantic: VertexAttributeSemantic.COLOR,
  242. setIndex: 0,
  243. typedArray: result.RGBA.array,
  244. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  245. type: AttributeType.VEC4,
  246. normalized: true,
  247. isTranslucent: true,
  248. };
  249. } else if (defined(result.RGB)) {
  250. parsedContent.colors = {
  251. name: "COLOR",
  252. semantic: VertexAttributeSemantic.COLOR,
  253. setIndex: 0,
  254. typedArray: result.RGB.array,
  255. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  256. type: AttributeType.VEC3,
  257. normalized: true,
  258. isTranslucent: false,
  259. };
  260. }
  261. // Transcode Batch ID (3D Tiles 1.0) -> Feature ID (3D Tiles Next)
  262. if (defined(result.BATCH_ID)) {
  263. const batchIds = result.BATCH_ID.array;
  264. parsedContent.batchIds = {
  265. name: "_FEATURE_ID",
  266. semantic: VertexAttributeSemantic.FEATURE_ID,
  267. setIndex: 0,
  268. typedArray: batchIds,
  269. componentDatatype: ComponentDatatype.fromTypedArray(batchIds),
  270. type: AttributeType.SCALAR,
  271. };
  272. }
  273. let batchTableJson = parsedContent.batchTableJson;
  274. const batchTableProperties = draco.batchTableProperties;
  275. for (const name in batchTableProperties) {
  276. if (batchTableProperties.hasOwnProperty(name)) {
  277. const property = result[name];
  278. if (!defined(batchTableJson)) {
  279. batchTableJson = {};
  280. }
  281. parsedContent.hasDracoBatchTable = true;
  282. const data = property.data;
  283. batchTableJson[name] = {
  284. byteOffset: data.byteOffset,
  285. // Draco returns the results like glTF values, but here
  286. // we want to transcode to a batch table. It's redundant
  287. // but necessary to use parseBatchTable()
  288. type: transcodeAttributeType(data.componentsPerAttribute),
  289. componentType: transcodeComponentType(data.componentDatatype),
  290. // Each property is stored as a separate typed array, so
  291. // store it here. parseBatchTable() will check for this
  292. // instead of the entire binary body.
  293. typedArray: property.array,
  294. };
  295. }
  296. }
  297. parsedContent.batchTableJson = batchTableJson;
  298. }
  299. function transcodeAttributeType(componentsPerAttribute) {
  300. switch (componentsPerAttribute) {
  301. case 1:
  302. return "SCALAR";
  303. case 2:
  304. return "VEC2";
  305. case 3:
  306. return "VEC3";
  307. case 4:
  308. return "VEC4";
  309. //>>includeStart('debug', pragmas.debug);
  310. default:
  311. throw new DeveloperError(
  312. "componentsPerAttribute must be a number from 1-4"
  313. );
  314. //>>includeEnd('debug');
  315. }
  316. }
  317. function transcodeComponentType(value) {
  318. switch (value) {
  319. case WebGLConstants.BYTE:
  320. return "BYTE";
  321. case WebGLConstants.UNSIGNED_BYTE:
  322. return "UNSIGNED_BYTE";
  323. case WebGLConstants.SHORT:
  324. return "SHORT";
  325. case WebGLConstants.UNSIGNED_SHORT:
  326. return "UNSIGNED_SHORT";
  327. case WebGLConstants.INT:
  328. return "INT";
  329. case WebGLConstants.UNSIGNED_INT:
  330. return "UNSIGNED_INT";
  331. case WebGLConstants.DOUBLE:
  332. return "DOUBLE";
  333. case WebGLConstants.FLOAT:
  334. return "FLOAT";
  335. //>>includeStart('debug', pragmas.debug);
  336. default:
  337. throw new DeveloperError("value is not a valid WebGL constant");
  338. //>>includeEnd('debug');
  339. }
  340. }
  341. function makeAttribute(loader, attributeInfo, context) {
  342. let typedArray = attributeInfo.typedArray;
  343. let quantization;
  344. if (attributeInfo.octEncoded) {
  345. quantization = new Quantization();
  346. quantization.octEncoded = attributeInfo.octEncoded;
  347. quantization.octEncodedZXY = attributeInfo.octEncodedZXY;
  348. quantization.normalizationRange = attributeInfo.quantizedRange;
  349. quantization.type = attributeInfo.quantizedType;
  350. quantization.componentDatatype = attributeInfo.quantizedComponentDatatype;
  351. }
  352. if (attributeInfo.isQuantized) {
  353. quantization = new Quantization();
  354. const normalizationRange = attributeInfo.quantizedRange;
  355. quantization.normalizationRange = normalizationRange;
  356. // volume offset sometimes requires 64-bit precision so this is handled
  357. // in the components.transform matrix.
  358. quantization.quantizedVolumeOffset = Cartesian3.ZERO;
  359. const quantizedVolumeDimensions = attributeInfo.quantizedVolumeScale;
  360. quantization.quantizedVolumeDimensions = quantizedVolumeDimensions;
  361. quantization.quantizedVolumeStepSize = Cartesian3.divideByScalar(
  362. quantizedVolumeDimensions,
  363. normalizationRange,
  364. new Cartesian3()
  365. );
  366. quantization.componentDatatype = attributeInfo.quantizedComponentDatatype;
  367. quantization.type = attributeInfo.quantizedType;
  368. }
  369. const attribute = new Attribute();
  370. attribute.name = attributeInfo.name;
  371. attribute.semantic = attributeInfo.semantic;
  372. attribute.setIndex = attributeInfo.setIndex;
  373. attribute.componentDatatype = attributeInfo.componentDatatype;
  374. attribute.type = attributeInfo.type;
  375. attribute.normalized = defaultValue(attributeInfo.normalized, false);
  376. attribute.min = attributeInfo.min;
  377. attribute.max = attributeInfo.max;
  378. attribute.quantization = quantization;
  379. if (attributeInfo.isRGB565) {
  380. typedArray = AttributeCompression.decodeRGB565(typedArray);
  381. }
  382. if (defined(attributeInfo.constantColor)) {
  383. const packedColor = new Array(4);
  384. attribute.constant = Color.pack(attributeInfo.constantColor, packedColor);
  385. } else {
  386. const buffer = Buffer.createVertexBuffer({
  387. typedArray: typedArray,
  388. context: context,
  389. usage: BufferUsage.STATIC_DRAW,
  390. });
  391. buffer.vertexArrayDestroyable = false;
  392. loader._buffers.push(buffer);
  393. attribute.buffer = buffer;
  394. }
  395. const loadAttributesFor2D = loader._loadAttributesFor2D;
  396. if (
  397. attribute.semantic === VertexAttributeSemantic.POSITION &&
  398. loadAttributesFor2D
  399. ) {
  400. attribute.typedArray = typedArray;
  401. }
  402. return attribute;
  403. }
  404. let randomNumberGenerator;
  405. let randomValues;
  406. function getRandomValues(samplesLength) {
  407. // Use same random values across all runs
  408. if (!defined(randomValues)) {
  409. // Use MersenneTwister directly to avoid interfering with CesiumMath.nextRandomNumber()
  410. // See https://github.com/CesiumGS/cesium/issues/9730
  411. randomNumberGenerator = new MersenneTwister(0);
  412. randomValues = new Array(samplesLength);
  413. for (let i = 0; i < samplesLength; ++i) {
  414. randomValues[i] = randomNumberGenerator.random();
  415. }
  416. }
  417. return randomValues;
  418. }
  419. const scratchMin = new Cartesian3();
  420. const scratchMax = new Cartesian3();
  421. const scratchPosition = new Cartesian3();
  422. function computeApproximateExtrema(positions) {
  423. const positionsArray = positions.typedArray;
  424. const maximumSamplesLength = 20;
  425. const pointsLength = positionsArray.length / 3;
  426. const samplesLength = Math.min(pointsLength, maximumSamplesLength);
  427. const randomValues = getRandomValues(maximumSamplesLength);
  428. const maxValue = Number.MAX_VALUE;
  429. const minValue = -Number.MAX_VALUE;
  430. let min = Cartesian3.fromElements(maxValue, maxValue, maxValue, scratchMin);
  431. let max = Cartesian3.fromElements(minValue, minValue, minValue, scratchMax);
  432. let i;
  433. let index;
  434. let position;
  435. if (positions.isQuantized) {
  436. // The quantized volume offset is not used here since it will become part of
  437. // the model matrix.
  438. min = Cartesian3.ZERO;
  439. max = positions.quantizedVolumeScale;
  440. } else {
  441. for (i = 0; i < samplesLength; ++i) {
  442. index = Math.floor(randomValues[i] * pointsLength);
  443. position = Cartesian3.unpack(positionsArray, index * 3, scratchPosition);
  444. Cartesian3.minimumByComponent(min, position, min);
  445. Cartesian3.maximumByComponent(max, position, max);
  446. }
  447. }
  448. positions.min = Cartesian3.clone(min);
  449. positions.max = Cartesian3.clone(max);
  450. }
  451. // By default, point clouds are rendered as dark gray.
  452. const defaultColorAttribute = {
  453. name: VertexAttributeSemantic.COLOR,
  454. semantic: VertexAttributeSemantic.COLOR,
  455. setIndex: 0,
  456. constantColor: Color.DARKGRAY,
  457. componentDatatype: ComponentDatatype.FLOAT,
  458. type: AttributeType.VEC4,
  459. isQuantized: false,
  460. isTranslucent: false,
  461. };
  462. function makeAttributes(loader, parsedContent, context) {
  463. const attributes = [];
  464. let attribute;
  465. const positions = parsedContent.positions;
  466. if (defined(positions)) {
  467. computeApproximateExtrema(positions);
  468. attribute = makeAttribute(loader, positions, context);
  469. attribute.count = parsedContent.pointsLength;
  470. attributes.push(attribute);
  471. }
  472. if (defined(parsedContent.normals)) {
  473. attribute = makeAttribute(loader, parsedContent.normals, context);
  474. attributes.push(attribute);
  475. }
  476. if (defined(parsedContent.colors)) {
  477. attribute = makeAttribute(loader, parsedContent.colors, context);
  478. attributes.push(attribute);
  479. } else {
  480. attribute = makeAttribute(loader, defaultColorAttribute, context);
  481. attributes.push(attribute);
  482. }
  483. if (defined(parsedContent.batchIds)) {
  484. attribute = makeAttribute(loader, parsedContent.batchIds, context);
  485. attributes.push(attribute);
  486. }
  487. return attributes;
  488. }
  489. function makeStructuralMetadata(parsedContent, customAttributeOutput) {
  490. const batchLength = parsedContent.batchLength;
  491. const pointsLength = parsedContent.pointsLength;
  492. const batchTableBinary = parsedContent.batchTableBinary;
  493. // If there are batch IDs, parse as a property table. Otherwise, parse
  494. // as property attributes.
  495. const parseAsPropertyAttributes = !defined(parsedContent.batchIds);
  496. if (defined(batchTableBinary) || parsedContent.hasDracoBatchTable) {
  497. const count = defaultValue(batchLength, pointsLength);
  498. return parseBatchTable({
  499. count: count,
  500. batchTable: parsedContent.batchTableJson,
  501. binaryBody: batchTableBinary,
  502. parseAsPropertyAttributes: parseAsPropertyAttributes,
  503. customAttributeOutput: customAttributeOutput,
  504. });
  505. }
  506. return new StructuralMetadata({
  507. schema: {},
  508. propertyTables: [],
  509. });
  510. }
  511. function makeComponents(loader, context) {
  512. const parsedContent = loader._parsedContent;
  513. const metallicRoughness = new MetallicRoughness();
  514. metallicRoughness.metallicFactor = 0;
  515. metallicRoughness.roughnessFactor = 0.9;
  516. const material = new Material();
  517. material.metallicRoughness = metallicRoughness;
  518. const colors = parsedContent.colors;
  519. if (defined(colors) && colors.isTranslucent) {
  520. material.alphaMode = AlphaMode.BLEND;
  521. }
  522. // Render point clouds as unlit, unless normals are present, in which case
  523. // render as a PBR material.
  524. const isUnlit = !defined(parsedContent.normals);
  525. material.unlit = isUnlit;
  526. const primitive = new Primitive();
  527. primitive.attributes = makeAttributes(loader, parsedContent, context);
  528. primitive.primitiveType = PrimitiveType.POINTS;
  529. primitive.material = material;
  530. if (defined(parsedContent.batchIds)) {
  531. const featureIdAttribute = new FeatureIdAttribute();
  532. featureIdAttribute.propertyTableId = 0;
  533. featureIdAttribute.setIndex = 0;
  534. featureIdAttribute.positionalLabel = "featureId_0";
  535. primitive.featureIds.push(featureIdAttribute);
  536. }
  537. const node = new Node();
  538. node.index = 0;
  539. node.primitives = [primitive];
  540. const scene = new Scene();
  541. scene.nodes = [node];
  542. scene.upAxis = Axis.Z;
  543. scene.forwardAxis = Axis.X;
  544. const components = new Components();
  545. components.scene = scene;
  546. components.nodes = [node];
  547. // Per-point features will be parsed as property attributes and handled on
  548. // the GPU since CPU styling would be too expensive. However, if batch IDs
  549. // exist, features will be parsed as a property table.
  550. //
  551. // Property attributes refer to a custom attribute that will
  552. // store the values; such attributes will be populated in this array
  553. // as needed.
  554. const customAttributeOutput = [];
  555. components.structuralMetadata = makeStructuralMetadata(
  556. parsedContent,
  557. customAttributeOutput
  558. );
  559. if (customAttributeOutput.length > 0) {
  560. addPropertyAttributesToPrimitive(
  561. loader,
  562. primitive,
  563. customAttributeOutput,
  564. context
  565. );
  566. }
  567. if (defined(parsedContent.rtcCenter)) {
  568. components.transform = Matrix4.multiplyByTranslation(
  569. components.transform,
  570. parsedContent.rtcCenter,
  571. components.transform
  572. );
  573. }
  574. const positions = parsedContent.positions;
  575. if (defined(positions) && positions.isQuantized) {
  576. // The volume offset is sometimes in ECEF, so this is applied here rather
  577. // than the dequantization shader to avoid jitter
  578. components.transform = Matrix4.multiplyByTranslation(
  579. components.transform,
  580. positions.quantizedVolumeOffset,
  581. components.transform
  582. );
  583. }
  584. loader._components = components;
  585. // Free the parsed content and array buffer so we don't hold onto the large arrays.
  586. loader._parsedContent = undefined;
  587. loader._arrayBuffer = undefined;
  588. }
  589. function addPropertyAttributesToPrimitive(
  590. loader,
  591. primitive,
  592. customAttributes,
  593. context
  594. ) {
  595. const attributes = primitive.attributes;
  596. const length = customAttributes.length;
  597. for (let i = 0; i < length; i++) {
  598. const customAttribute = customAttributes[i];
  599. // Upload the typed array to the GPU and free the CPU copy.
  600. const buffer = Buffer.createVertexBuffer({
  601. typedArray: customAttribute.typedArray,
  602. context: context,
  603. usage: BufferUsage.STATIC_DRAW,
  604. });
  605. buffer.vertexArrayDestroyable = false;
  606. loader._buffers.push(buffer);
  607. customAttribute.buffer = buffer;
  608. customAttribute.typedArray = undefined;
  609. attributes.push(customAttribute);
  610. }
  611. // The batch table is always transcoded as a single property attribute, so
  612. // it will always be index 0
  613. primitive.propertyAttributeIds = [0];
  614. }
  615. PntsLoader.prototype.unload = function () {
  616. const buffers = this._buffers;
  617. for (let i = 0; i < buffers.length; i++) {
  618. buffers[i].destroy();
  619. }
  620. buffers.length = 0;
  621. this._components = undefined;
  622. this._parsedContent = undefined;
  623. this._arrayBuffer = undefined;
  624. };
  625. export default PntsLoader;