Reference Source

src/plugins/XKTLoaderPlugin/parsers/ParserV5.js

  1. /*
  2.  
  3. Parser for .XKT Format V5
  4.  
  5. .XKT specifications: https://github.com/xeokit/xeokit-sdk/wiki/XKT-Format
  6.  
  7. */
  8.  
  9. import {utils} from "../../../viewer/scene/utils.js";
  10. import * as p from "./lib/pako.js";
  11.  
  12. let pako = window.pako || p;
  13. if (!pako.inflate) { // See https://github.com/nodeca/pako/issues/97
  14. pako = pako.default;
  15. }
  16.  
  17. function extract(elements) {
  18. return {
  19. positions: elements[0],
  20. normals: elements[1],
  21. indices: elements[2],
  22. edgeIndices: elements[3],
  23. matrices: elements[4],
  24. eachPrimitivePositionsAndNormalsPortion: elements[5],
  25. eachPrimitiveIndicesPortion: elements[6],
  26. eachPrimitiveEdgeIndicesPortion: elements[7],
  27. eachPrimitiveColor: elements[8],
  28. primitiveInstances: elements[9],
  29. eachEntityId: elements[10],
  30. eachEntityPrimitiveInstancesPortion: elements[11],
  31. eachEntityMatricesPortion: elements[12]
  32. };
  33. }
  34.  
  35. function inflate(deflatedData) {
  36. return {
  37. positions: new Float32Array(pako.inflate(deflatedData.positions).buffer),
  38. normals: new Int8Array(pako.inflate(deflatedData.normals).buffer),
  39. indices: new Uint32Array(pako.inflate(deflatedData.indices).buffer),
  40. edgeIndices: new Uint32Array(pako.inflate(deflatedData.edgeIndices).buffer),
  41. matrices: new Float32Array(pako.inflate(deflatedData.matrices).buffer),
  42. eachPrimitivePositionsAndNormalsPortion: new Uint32Array(pako.inflate(deflatedData.eachPrimitivePositionsAndNormalsPortion).buffer),
  43. eachPrimitiveIndicesPortion: new Uint32Array(pako.inflate(deflatedData.eachPrimitiveIndicesPortion).buffer),
  44. eachPrimitiveEdgeIndicesPortion: new Uint32Array(pako.inflate(deflatedData.eachPrimitiveEdgeIndicesPortion).buffer),
  45. eachPrimitiveColor: new Uint8Array(pako.inflate(deflatedData.eachPrimitiveColor).buffer),
  46. primitiveInstances: new Uint32Array(pako.inflate(deflatedData.primitiveInstances).buffer),
  47. eachEntityId: pako.inflate(deflatedData.eachEntityId, {to: 'string'}),
  48. eachEntityPrimitiveInstancesPortion: new Uint32Array(pako.inflate(deflatedData.eachEntityPrimitiveInstancesPortion).buffer),
  49. eachEntityMatricesPortion: new Uint32Array(pako.inflate(deflatedData.eachEntityMatricesPortion).buffer)
  50. };
  51. }
  52.  
  53. const decompressColor = (function () {
  54. const color2 = new Float32Array(3);
  55. return function (color) {
  56. color2[0] = color[0] / 255.0;
  57. color2[1] = color[1] / 255.0;
  58. color2[2] = color[2] / 255.0;
  59. return color2;
  60. };
  61. })();
  62.  
  63. function load(viewer, options, inflatedData, sceneModel, metaModel, manifestCtx) {
  64.  
  65. const modelPartId = manifestCtx.getNextId();
  66.  
  67. sceneModel.positionsCompression = "disabled"; // Positions in XKT V4 are floats, which we never quantize, for precision with big models
  68. sceneModel.normalsCompression = "precompressed"; // Normals are oct-encoded though
  69.  
  70. const positions = inflatedData.positions;
  71. const normals = inflatedData.normals;
  72. const indices = inflatedData.indices;
  73. const edgeIndices = inflatedData.edgeIndices;
  74. const matrices = inflatedData.matrices;
  75.  
  76. const eachPrimitivePositionsAndNormalsPortion = inflatedData.eachPrimitivePositionsAndNormalsPortion;
  77. const eachPrimitiveIndicesPortion = inflatedData.eachPrimitiveIndicesPortion;
  78. const eachPrimitiveEdgeIndicesPortion = inflatedData.eachPrimitiveEdgeIndicesPortion;
  79. const eachPrimitiveColor = inflatedData.eachPrimitiveColor;
  80.  
  81. const primitiveInstances = inflatedData.primitiveInstances;
  82.  
  83. const eachEntityId = JSON.parse(inflatedData.eachEntityId);
  84. const eachEntityPrimitiveInstancesPortion = inflatedData.eachEntityPrimitiveInstancesPortion;
  85. const eachEntityMatricesPortion = inflatedData.eachEntityMatricesPortion;
  86.  
  87. const numPrimitives = eachPrimitivePositionsAndNormalsPortion.length;
  88. const numPrimitiveInstances = primitiveInstances.length;
  89. const primitiveInstanceCounts = new Uint8Array(numPrimitives); // For each mesh, how many times it is instanced
  90.  
  91. const numEntities = eachEntityId.length;
  92.  
  93. // Count instances of each primitive
  94.  
  95. for (let primitiveInstanceIndex = 0; primitiveInstanceIndex < numPrimitiveInstances; primitiveInstanceIndex++) {
  96. const primitiveIndex = primitiveInstances[primitiveInstanceIndex];
  97. primitiveInstanceCounts[primitiveIndex]++;
  98. }
  99.  
  100. // Map batched primitives to the entities that will use them
  101.  
  102. const batchedPrimitiveEntityIndexes = {};
  103.  
  104. for (let entityIndex = 0; entityIndex < numEntities; entityIndex++) {
  105.  
  106. const lastEntityIndex = (numEntities - 1);
  107. const atLastEntity = (entityIndex === lastEntityIndex);
  108. const firstEntityPrimitiveInstanceIndex = eachEntityPrimitiveInstancesPortion [entityIndex];
  109. const lastEntityPrimitiveInstanceIndex = atLastEntity ? eachEntityPrimitiveInstancesPortion[lastEntityIndex] : eachEntityPrimitiveInstancesPortion[entityIndex + 1];
  110.  
  111. for (let primitiveInstancesIndex = firstEntityPrimitiveInstanceIndex; primitiveInstancesIndex < lastEntityPrimitiveInstanceIndex; primitiveInstancesIndex++) {
  112.  
  113. const primitiveIndex = primitiveInstances[primitiveInstancesIndex];
  114. const primitiveInstanceCount = primitiveInstanceCounts[primitiveIndex];
  115. const isInstancedPrimitive = (primitiveInstanceCount > 1);
  116.  
  117. if (!isInstancedPrimitive) {
  118. batchedPrimitiveEntityIndexes[primitiveIndex] = entityIndex;
  119. }
  120. }
  121. }
  122.  
  123. var countGeometries = 0;
  124.  
  125. // Create geometries for instanced primitives and meshes for batched primitives.
  126.  
  127. for (let primitiveIndex = 0; primitiveIndex < numPrimitives; primitiveIndex++) {
  128.  
  129. const atLastPrimitive = (primitiveIndex === (numPrimitives - 1));
  130.  
  131. const primitiveInstanceCount = primitiveInstanceCounts[primitiveIndex];
  132. const isInstancedPrimitive = (primitiveInstanceCount > 1);
  133.  
  134. const color = decompressColor(eachPrimitiveColor.subarray((primitiveIndex * 4), (primitiveIndex * 4) + 3));
  135. const opacity = eachPrimitiveColor[(primitiveIndex * 4) + 3] / 255.0;
  136.  
  137. const primitivePositions = positions.subarray(eachPrimitivePositionsAndNormalsPortion [primitiveIndex], atLastPrimitive ? positions.length : eachPrimitivePositionsAndNormalsPortion [primitiveIndex + 1]);
  138. const primitiveNormals = normals.subarray(eachPrimitivePositionsAndNormalsPortion [primitiveIndex], atLastPrimitive ? normals.length : eachPrimitivePositionsAndNormalsPortion [primitiveIndex + 1]);
  139. const primitiveIndices = indices.subarray(eachPrimitiveIndicesPortion [primitiveIndex], atLastPrimitive ? indices.length : eachPrimitiveIndicesPortion [primitiveIndex + 1]);
  140. const primitiveEdgeIndices = edgeIndices.subarray(eachPrimitiveEdgeIndicesPortion [primitiveIndex], atLastPrimitive ? edgeIndices.length : eachPrimitiveEdgeIndicesPortion [primitiveIndex + 1]);
  141.  
  142. if (isInstancedPrimitive) {
  143.  
  144. // Primitive instanced by more than one entity, and has positions in Model-space
  145.  
  146. const geometryId = `${modelPartId}-geometry.${primitiveIndex}`; // These IDs are local to the SceneModel
  147.  
  148. sceneModel.createGeometry({
  149. id: geometryId,
  150. primitive: "triangles",
  151. positionsCompressed: primitivePositions,
  152. normalsCompressed: primitiveNormals,
  153. indices: primitiveIndices,
  154. edgeIndices: primitiveEdgeIndices
  155. });
  156.  
  157. countGeometries++;
  158.  
  159. } else {
  160.  
  161. // Primitive is used only by one entity, and has positions pre-transformed into World-space
  162.  
  163. const meshId = primitiveIndex; // These IDs are local to the SceneModel
  164.  
  165. const entityIndex = batchedPrimitiveEntityIndexes[primitiveIndex];
  166. const entityId = eachEntityId[entityIndex];
  167.  
  168. const meshDefaults = {}; // TODO: get from lookup from entity IDs
  169.  
  170. sceneModel.createMesh(utils.apply(meshDefaults, {
  171. id: meshId,
  172. primitive: "triangles",
  173. positionsCompressed: primitivePositions,
  174. normalsCompressed: primitiveNormals,
  175. indices: primitiveIndices,
  176. edgeIndices: primitiveEdgeIndices,
  177. color: color,
  178. opacity: opacity
  179. }));
  180. }
  181. }
  182.  
  183. let countInstances = 0;
  184.  
  185. for (let entityIndex = 0; entityIndex < numEntities; entityIndex++) {
  186.  
  187. const lastEntityIndex = (numEntities - 1);
  188. const atLastEntity = (entityIndex === lastEntityIndex);
  189. const entityId = eachEntityId[entityIndex];
  190. const firstEntityPrimitiveInstanceIndex = eachEntityPrimitiveInstancesPortion [entityIndex];
  191. const lastEntityPrimitiveInstanceIndex = atLastEntity ? eachEntityPrimitiveInstancesPortion[lastEntityIndex] : eachEntityPrimitiveInstancesPortion[entityIndex + 1];
  192.  
  193. const meshIds = [];
  194.  
  195. for (let primitiveInstancesIndex = firstEntityPrimitiveInstanceIndex; primitiveInstancesIndex < lastEntityPrimitiveInstanceIndex; primitiveInstancesIndex++) {
  196.  
  197. const primitiveIndex = primitiveInstances[primitiveInstancesIndex];
  198. const primitiveInstanceCount = primitiveInstanceCounts[primitiveIndex];
  199. const isInstancedPrimitive = (primitiveInstanceCount > 1);
  200.  
  201. if (isInstancedPrimitive) {
  202.  
  203. const meshDefaults = {}; // TODO: get from lookup from entity IDs
  204.  
  205. const meshId = "instance." + countInstances++;
  206. const geometryId = "geometry" + primitiveIndex;
  207. const matricesIndex = (eachEntityMatricesPortion [entityIndex]) * 16;
  208. const matrix = matrices.subarray(matricesIndex, matricesIndex + 16);
  209.  
  210. sceneModel.createMesh(utils.apply(meshDefaults, {
  211. id: meshId,
  212. geometryId: geometryId,
  213. matrix: matrix
  214. }));
  215.  
  216. meshIds.push(meshId);
  217.  
  218. } else {
  219. meshIds.push(primitiveIndex);
  220. }
  221. }
  222.  
  223. if (meshIds.length > 0) {
  224.  
  225. const entityDefaults = {}; // TODO: get from lookup from entity IDs
  226.  
  227. sceneModel.createEntity(utils.apply(entityDefaults, {
  228. id: entityId,
  229. isObject: true, ///////////////// TODO: If metaobject exists
  230. meshIds: meshIds
  231. }));
  232. }
  233. }
  234. }
  235.  
  236. /** @private */
  237. const ParserV5 = {
  238. version: 5,
  239. parse: function (viewer, options, elements, sceneModel, metaModel, manifestCtx) {
  240. const deflatedData = extract(elements);
  241. const inflatedData = inflate(deflatedData);
  242. load(viewer, options, inflatedData, sceneModel, metaModel, manifestCtx);
  243. }
  244. };
  245.  
  246. export {ParserV5};