UNPKG

@xeokit/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

283 lines (217 loc) 13.1 kB
/* Parser for .XKT Format V4 .XKT specifications: https://github.com/xeokit/xeokit-sdk/wiki/XKT-Format */ import {utils} from "../../../viewer/scene/utils.js"; import * as p from "./lib/pako.js"; let pako = window.pako || p; if (!pako.inflate) { // See https://github.com/nodeca/pako/issues/97 pako = pako.default; } function extract(elements) { return { positions: elements[0], normals: elements[1], indices: elements[2], edgeIndices: elements[3], decodeMatrices: elements[4], matrices: elements[5], eachPrimitivePositionsAndNormalsPortion: elements[6], eachPrimitiveIndicesPortion: elements[7], eachPrimitiveEdgeIndicesPortion: elements[8], eachPrimitiveDecodeMatricesPortion: elements[9], eachPrimitiveColor: elements[10], primitiveInstances: elements[11], eachEntityId: elements[12], eachEntityPrimitiveInstancesPortion: elements[13], eachEntityMatricesPortion: elements[14], eachEntityMatrix: elements[15] }; } function inflate(deflatedData) { return { positions: new Uint16Array(pako.inflate(deflatedData.positions).buffer), normals: new Int8Array(pako.inflate(deflatedData.normals).buffer), indices: new Uint32Array(pako.inflate(deflatedData.indices).buffer), edgeIndices: new Uint32Array(pako.inflate(deflatedData.edgeIndices).buffer), decodeMatrices: new Float32Array(pako.inflate(deflatedData.decodeMatrices).buffer), matrices: new Float32Array(pako.inflate(deflatedData.matrices).buffer), eachPrimitivePositionsAndNormalsPortion: new Uint32Array(pako.inflate(deflatedData.eachPrimitivePositionsAndNormalsPortion).buffer), eachPrimitiveIndicesPortion: new Uint32Array(pako.inflate(deflatedData.eachPrimitiveIndicesPortion).buffer), eachPrimitiveEdgeIndicesPortion: new Uint32Array(pako.inflate(deflatedData.eachPrimitiveEdgeIndicesPortion).buffer), eachPrimitiveDecodeMatricesPortion: new Uint32Array(pako.inflate(deflatedData.eachPrimitiveDecodeMatricesPortion).buffer), eachPrimitiveColor: new Uint8Array(pako.inflate(deflatedData.eachPrimitiveColor).buffer), primitiveInstances: new Uint32Array(pako.inflate(deflatedData.primitiveInstances).buffer), eachEntityId: pako.inflate(deflatedData.eachEntityId, {to: 'string'}), eachEntityPrimitiveInstancesPortion: new Uint32Array(pako.inflate(deflatedData.eachEntityPrimitiveInstancesPortion).buffer), eachEntityMatricesPortion: new Uint32Array(pako.inflate(deflatedData.eachEntityMatricesPortion).buffer) }; } const decompressColor = (function () { const color2 = new Float32Array(3); return function (color) { color2[0] = color[0] / 255.0; color2[1] = color[1] / 255.0; color2[2] = color[2] / 255.0; return color2; }; })(); function load(viewer, options, inflatedData, sceneModel, metaModel, manifestCtx) { const modelPartId = manifestCtx.getNextId(); sceneModel.positionsCompression = "precompressed"; sceneModel.normalsCompression = "precompressed"; const positions = inflatedData.positions; const normals = inflatedData.normals; const indices = inflatedData.indices; const edgeIndices = inflatedData.edgeIndices; const decodeMatrices = inflatedData.decodeMatrices; const matrices = inflatedData.matrices; const eachPrimitivePositionsAndNormalsPortion = inflatedData.eachPrimitivePositionsAndNormalsPortion; const eachPrimitiveIndicesPortion = inflatedData.eachPrimitiveIndicesPortion; const eachPrimitiveEdgeIndicesPortion = inflatedData.eachPrimitiveEdgeIndicesPortion; const eachPrimitiveDecodeMatricesPortion = inflatedData.eachPrimitiveDecodeMatricesPortion; const eachPrimitiveColor = inflatedData.eachPrimitiveColor; const primitiveInstances = inflatedData.primitiveInstances; const eachEntityId = JSON.parse(inflatedData.eachEntityId); const eachEntityPrimitiveInstancesPortion = inflatedData.eachEntityPrimitiveInstancesPortion; const eachEntityMatricesPortion = inflatedData.eachEntityMatricesPortion; const numPrimitives = eachPrimitivePositionsAndNormalsPortion.length; const numPrimitiveInstances = primitiveInstances.length; const primitiveInstanceCounts = new Uint8Array(numPrimitives); // For each mesh, how many times it is instanced const orderedPrimitiveIndexes = new Uint32Array(numPrimitives); // For each mesh, its index sorted into runs that share the same decode matrix const numEntities = eachEntityId.length; // Get lookup that orders primitives into runs that share the same decode matrices; // this is used to create meshes in batches that use the same decode matrix for (let primitiveIndex = 0; primitiveIndex < numPrimitives; primitiveIndex++) { orderedPrimitiveIndexes[primitiveIndex] = primitiveIndex; } orderedPrimitiveIndexes.sort((i1, i2) => { if (eachPrimitiveDecodeMatricesPortion[i1] < eachPrimitiveDecodeMatricesPortion[i2]) { return -1; } if (eachPrimitiveDecodeMatricesPortion[i1] > eachPrimitiveDecodeMatricesPortion[i2]) { return 1; } return 0; }); // Count instances of each primitive for (let primitiveInstanceIndex = 0; primitiveInstanceIndex < numPrimitiveInstances; primitiveInstanceIndex++) { const primitiveIndex = primitiveInstances[primitiveInstanceIndex]; primitiveInstanceCounts[primitiveIndex]++; } // Map batched primitives to the entities that will use them const batchedPrimitiveEntityIndexes = {}; for (let entityIndex = 0; entityIndex < numEntities; entityIndex++) { const lastEntityIndex = (numEntities - 1); const atLastEntity = (entityIndex === lastEntityIndex); const firstEntityPrimitiveInstanceIndex = eachEntityPrimitiveInstancesPortion [entityIndex]; const lastEntityPrimitiveInstanceIndex = atLastEntity ? eachEntityPrimitiveInstancesPortion[lastEntityIndex] : eachEntityPrimitiveInstancesPortion[entityIndex + 1]; for (let primitiveInstancesIndex = firstEntityPrimitiveInstanceIndex; primitiveInstancesIndex < lastEntityPrimitiveInstanceIndex; primitiveInstancesIndex++) { const primitiveIndex = primitiveInstances[primitiveInstancesIndex]; const primitiveInstanceCount = primitiveInstanceCounts[primitiveIndex]; const isInstancedPrimitive = (primitiveInstanceCount > 1); if (!isInstancedPrimitive) { batchedPrimitiveEntityIndexes[primitiveIndex] = entityIndex; } } } var countGeometries = 0; // Create 1) geometries for instanced primitives, and 2) meshes for batched primitives. We create all the // batched meshes now, before we create entities, because we're creating the batched meshes in runs that share // the same decode matrices. Each run of meshes with the same decode matrix will end up in the same // BatchingLayer; the SceneModel#createMesh() method starts a new BatchingLayer each time the decode // matrix has changed since the last invocation of that method, hence why we need to order batched meshes // in runs like this. for (let primitiveIndex = 0; primitiveIndex < numPrimitives; primitiveIndex++) { const orderedPrimitiveIndex = orderedPrimitiveIndexes[primitiveIndex]; const atLastPrimitive = (orderedPrimitiveIndex === (numPrimitives - 1)); const primitiveInstanceCount = primitiveInstanceCounts[orderedPrimitiveIndex]; const isInstancedPrimitive = (primitiveInstanceCount > 1); const color = decompressColor(eachPrimitiveColor.subarray((orderedPrimitiveIndex * 4), (orderedPrimitiveIndex * 4) + 3)); const opacity = eachPrimitiveColor[(orderedPrimitiveIndex * 4) + 3] / 255.0; const primitivePositions = positions.subarray(eachPrimitivePositionsAndNormalsPortion [orderedPrimitiveIndex], atLastPrimitive ? positions.length : eachPrimitivePositionsAndNormalsPortion [orderedPrimitiveIndex + 1]); const primitiveNormals = normals.subarray(eachPrimitivePositionsAndNormalsPortion [orderedPrimitiveIndex], atLastPrimitive ? normals.length : eachPrimitivePositionsAndNormalsPortion [orderedPrimitiveIndex + 1]); const primitiveIndices = indices.subarray(eachPrimitiveIndicesPortion [orderedPrimitiveIndex], atLastPrimitive ? indices.length : eachPrimitiveIndicesPortion [orderedPrimitiveIndex + 1]); const primitiveEdgeIndices = edgeIndices.subarray(eachPrimitiveEdgeIndicesPortion [orderedPrimitiveIndex], atLastPrimitive ? edgeIndices.length : eachPrimitiveEdgeIndicesPortion [orderedPrimitiveIndex + 1]); const primitiveDecodeMatrix = decodeMatrices.subarray(eachPrimitiveDecodeMatricesPortion [orderedPrimitiveIndex], eachPrimitiveDecodeMatricesPortion [orderedPrimitiveIndex] + 16); if (isInstancedPrimitive) { // Primitive instanced by more than one entity, and has positions in Model-space const geometryId = `${modelPartId}-geometry.${orderedPrimitiveIndex}`; // These IDs are local to the SceneModel sceneModel.createGeometry({ id: geometryId, primitive: "triangles", positionsCompressed: primitivePositions, normalsCompressed: primitiveNormals, indices: primitiveIndices, edgeIndices: primitiveEdgeIndices, positionsDecodeMatrix: primitiveDecodeMatrix }); countGeometries++; } else { // Primitive is used only by one entity, and has positions pre-transformed into World-space const meshId = `${modelPartId}-${orderedPrimitiveIndex}`; const entityIndex = batchedPrimitiveEntityIndexes[orderedPrimitiveIndex]; const entityId = eachEntityId[entityIndex]; const meshDefaults = {}; // TODO: get from lookup from entity IDs sceneModel.createMesh(utils.apply(meshDefaults, { id: meshId, primitive: "triangles", positionsCompressed: primitivePositions, normalsCompressed: primitiveNormals, indices: primitiveIndices, edgeIndices: primitiveEdgeIndices, positionsDecodeMatrix: primitiveDecodeMatrix, color: color, opacity: opacity })); } } let countInstances = 0; for (let entityIndex = 0; entityIndex < numEntities; entityIndex++) { const lastEntityIndex = (numEntities - 1); const atLastEntity = (entityIndex === lastEntityIndex); const entityId = eachEntityId[entityIndex]; const firstEntityPrimitiveInstanceIndex = eachEntityPrimitiveInstancesPortion [entityIndex]; const lastEntityPrimitiveInstanceIndex = atLastEntity ? eachEntityPrimitiveInstancesPortion[lastEntityIndex] : eachEntityPrimitiveInstancesPortion[entityIndex + 1]; const meshIds = []; for (let primitiveInstancesIndex = firstEntityPrimitiveInstanceIndex; primitiveInstancesIndex < lastEntityPrimitiveInstanceIndex; primitiveInstancesIndex++) { const primitiveIndex = primitiveInstances[primitiveInstancesIndex]; const primitiveInstanceCount = primitiveInstanceCounts[primitiveIndex]; const isInstancedPrimitive = (primitiveInstanceCount > 1); if (isInstancedPrimitive) { const meshDefaults = {}; // TODO: get from lookup from entity IDs const meshId = `${modelPartId}-instance.${countInstances++}`; const geometryId = `${modelPartId}-geometry.${primitiveIndex}`; // These IDs are local to the SceneModel const matricesIndex = (eachEntityMatricesPortion [entityIndex]) * 16; const matrix = matrices.subarray(matricesIndex, matricesIndex + 16); sceneModel.createMesh(utils.apply(meshDefaults, { id: meshId, geometryId: geometryId, matrix: matrix })); meshIds.push(meshId); } else { const meshId = `${modelPartId}-${primitiveIndex}`; meshIds.push(primitiveIndex); } } if (meshIds.length > 0) { const entityDefaults = {}; // TODO: get from lookup from entity IDs sceneModel.createEntity(utils.apply(entityDefaults, { id: entityId, isObject: true, ///////////////// TODO: If metaobject exists meshIds: meshIds })); } } } /** @private */ const ParserV4 = { version: 4, parse: function (viewer, options, elements, sceneModel, metaModel, manifestCtx) { const deflatedData = extract(elements); const inflatedData = inflate(deflatedData); load(viewer, options, inflatedData, sceneModel, metaModel, manifestCtx); } }; export {ParserV4};