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

445 lines (343 loc) 21 kB
/* Parser for .XKT Format V7 */ import {utils} from "../../../viewer/scene/utils.js"; import * as p from "./lib/pako.js"; import {math} from "../../../viewer/scene/math/math.js"; import {geometryCompressionUtils} from "../../../viewer/scene/math/geometryCompressionUtils.js"; let pako = window.pako || p; if (!pako.inflate) { // See https://github.com/nodeca/pako/issues/97 pako = pako.default; } function extract(elements) { return { // Vertex attributes positions: elements[0], normals: elements[1], colors: elements[2], // Indices indices: elements[3], edgeIndices: elements[4], // Transform matrices matrices: elements[5], reusedGeometriesDecodeMatrix: elements[6], // Geometries eachGeometryPrimitiveType: elements[7], eachGeometryPositionsPortion: elements[8], eachGeometryNormalsPortion: elements[9], eachGeometryColorsPortion: elements[10], eachGeometryIndicesPortion: elements[11], eachGeometryEdgeIndicesPortion: elements[12], // Meshes are grouped in runs that are shared by the same entities eachMeshGeometriesPortion: elements[13], eachMeshMatricesPortion: elements[14], eachMeshMaterial: elements[15], // Entity elements in the following arrays are grouped in runs that are shared by the same tiles eachEntityId: elements[16], eachEntityMeshesPortion: elements[17], eachTileAABB: elements[18], eachTileEntitiesPortion: elements[19] }; } function inflate(deflatedData) { function inflate(array, options) { return (array.length === 0) ? [] : pako.inflate(array, options).buffer; } return { positions: new Uint16Array(inflate(deflatedData.positions)), normals: new Int8Array(inflate(deflatedData.normals)), colors: new Uint8Array(inflate(deflatedData.colors)), indices: new Uint32Array(inflate(deflatedData.indices)), edgeIndices: new Uint32Array(inflate(deflatedData.edgeIndices)), matrices: new Float32Array(inflate(deflatedData.matrices)), reusedGeometriesDecodeMatrix: new Float32Array(inflate(deflatedData.reusedGeometriesDecodeMatrix)), eachGeometryPrimitiveType: new Uint8Array(inflate(deflatedData.eachGeometryPrimitiveType)), eachGeometryPositionsPortion: new Uint32Array(inflate(deflatedData.eachGeometryPositionsPortion)), eachGeometryNormalsPortion: new Uint32Array(inflate(deflatedData.eachGeometryNormalsPortion)), eachGeometryColorsPortion: new Uint32Array(inflate(deflatedData.eachGeometryColorsPortion)), eachGeometryIndicesPortion: new Uint32Array(inflate(deflatedData.eachGeometryIndicesPortion)), eachGeometryEdgeIndicesPortion: new Uint32Array(inflate(deflatedData.eachGeometryEdgeIndicesPortion)), eachMeshGeometriesPortion: new Uint32Array(inflate(deflatedData.eachMeshGeometriesPortion)), eachMeshMatricesPortion: new Uint32Array(inflate(deflatedData.eachMeshMatricesPortion)), eachMeshMaterial: new Uint8Array(inflate(deflatedData.eachMeshMaterial)), eachEntityId: pako.inflate(deflatedData.eachEntityId, {to: 'string'}), eachEntityMeshesPortion: new Uint32Array(inflate(deflatedData.eachEntityMeshesPortion)), eachTileAABB: new Float64Array(inflate(deflatedData.eachTileAABB)), eachTileEntitiesPortion: new Uint32Array(inflate(deflatedData.eachTileEntitiesPortion)), }; } const decompressColor = (function () { const floatColor = new Float32Array(3); return function (intColor) { floatColor[0] = intColor[0] / 255.0; floatColor[1] = intColor[1] / 255.0; floatColor[2] = intColor[2] / 255.0; return floatColor; }; })(); function convertColorsRGBToRGBA(colorsRGB) { const colorsRGBA = []; for (let i = 0, len = colorsRGB.length; i < len; i+=3) { colorsRGBA.push(colorsRGB[i]); colorsRGBA.push(colorsRGB[i+1]); colorsRGBA.push(colorsRGB[i+2]); colorsRGBA.push(1.0); } return colorsRGBA; } function load(viewer, options, inflatedData, sceneModel, metaModel, manifestCtx) { const modelPartId = manifestCtx.getNextId(); const positions = inflatedData.positions; const normals = inflatedData.normals; const colors = inflatedData.colors; const indices = inflatedData.indices; const edgeIndices = inflatedData.edgeIndices; const matrices = inflatedData.matrices; const reusedGeometriesDecodeMatrix = inflatedData.reusedGeometriesDecodeMatrix; const eachGeometryPrimitiveType = inflatedData.eachGeometryPrimitiveType; const eachGeometryPositionsPortion = inflatedData.eachGeometryPositionsPortion; const eachGeometryNormalsPortion = inflatedData.eachGeometryNormalsPortion; const eachGeometryColorsPortion = inflatedData.eachGeometryColorsPortion; const eachGeometryIndicesPortion = inflatedData.eachGeometryIndicesPortion; const eachGeometryEdgeIndicesPortion = inflatedData.eachGeometryEdgeIndicesPortion; const eachMeshGeometriesPortion = inflatedData.eachMeshGeometriesPortion; const eachMeshMatricesPortion = inflatedData.eachMeshMatricesPortion; const eachMeshMaterial = inflatedData.eachMeshMaterial; const eachEntityId = JSON.parse(inflatedData.eachEntityId); const eachEntityMeshesPortion = inflatedData.eachEntityMeshesPortion; const eachTileAABB = inflatedData.eachTileAABB; const eachTileEntitiesPortion = inflatedData.eachTileEntitiesPortion; const numGeometries = eachGeometryPositionsPortion.length; const numMeshes = eachMeshGeometriesPortion.length; const numEntities = eachEntityId.length; const numTiles = eachTileEntitiesPortion.length; let nextMeshId = 0; // Count instances of each geometry const geometryReuseCounts = new Uint32Array(numGeometries); for (let meshIndex = 0; meshIndex < numMeshes; meshIndex++) { const geometryIndex = eachMeshGeometriesPortion[meshIndex]; if (geometryReuseCounts[geometryIndex] !== undefined) { geometryReuseCounts[geometryIndex]++; } else { geometryReuseCounts[geometryIndex] = 1; } } // Iterate over tiles const tileCenter = math.vec3(); const rtcAABB = math.AABB3(); for (let tileIndex = 0; tileIndex < numTiles; tileIndex++) { const lastTileIndex = (numTiles - 1); const atLastTile = (tileIndex === lastTileIndex); const firstTileEntityIndex = eachTileEntitiesPortion [tileIndex]; const lastTileEntityIndex = atLastTile ? numEntities : eachTileEntitiesPortion[tileIndex + 1]; const tileAABBIndex = tileIndex * 6; const tileAABB = eachTileAABB.subarray(tileAABBIndex, tileAABBIndex + 6); math.getAABB3Center(tileAABB, tileCenter); rtcAABB[0] = tileAABB[0] - tileCenter[0]; rtcAABB[1] = tileAABB[1] - tileCenter[1]; rtcAABB[2] = tileAABB[2] - tileCenter[2]; rtcAABB[3] = tileAABB[3] - tileCenter[0]; rtcAABB[4] = tileAABB[4] - tileCenter[1]; rtcAABB[5] = tileAABB[5] - tileCenter[2]; const tileDecodeMatrix = geometryCompressionUtils.createPositionsDecodeMatrix(rtcAABB); const geometryCreated = {}; // Iterate over each tile's entities for (let tileEntityIndex = firstTileEntityIndex; tileEntityIndex < lastTileEntityIndex; tileEntityIndex++) { const xktEntityId = eachEntityId[tileEntityIndex]; const entityId = options.globalizeObjectIds ? math.globalizeObjectId(sceneModel.id, xktEntityId) : xktEntityId; const lastTileEntityIndex = (numEntities - 1); const atLastTileEntity = (tileEntityIndex === lastTileEntityIndex); const firstMeshIndex = eachEntityMeshesPortion [tileEntityIndex]; const lastMeshIndex = atLastTileEntity ? eachMeshGeometriesPortion.length : eachEntityMeshesPortion[tileEntityIndex + 1]; const meshIds = []; const metaObject = viewer.metaScene.metaObjects[entityId]; const entityDefaults = {}; const meshDefaults = {}; if (metaObject) { // Mask loading of object types if (options.excludeTypesMap && metaObject.type && options.excludeTypesMap[metaObject.type]) { continue; } if (options.includeTypesMap && metaObject.type && (!options.includeTypesMap[metaObject.type])) { continue; } // Mask loading of object ids if (options.includeIdsMap && metaObject.id && (!options.includeIdsMap[metaObject.id])) { continue; } // Get initial property values for object types const props = options.objectDefaults ? options.objectDefaults[metaObject.type] || options.objectDefaults["DEFAULT"] : null; if (props) { if (props.visible === false) { entityDefaults.visible = false; } if (props.pickable === false) { entityDefaults.pickable = false; } if (props.colorize) { meshDefaults.color = props.colorize; } if (props.opacity !== undefined && props.opacity !== null) { meshDefaults.opacity = props.opacity; } if (props.metallic !== undefined && props.metallic !== null) { meshDefaults.metallic = props.metallic; } if (props.roughness !== undefined && props.roughness !== null) { meshDefaults.roughness = props.roughness; } } } else { if (options.excludeUnclassifiedObjects) { continue; } } // Iterate each entity's meshes for (let meshIndex = firstMeshIndex; meshIndex < lastMeshIndex; meshIndex++) { const geometryIndex = eachMeshGeometriesPortion[meshIndex]; const geometryReuseCount = geometryReuseCounts[geometryIndex]; const isReusedGeometry = (geometryReuseCount > 1); const atLastGeometry = (geometryIndex === (numGeometries - 1)); const meshColor = decompressColor(eachMeshMaterial.subarray((meshIndex * 6), (meshIndex * 6) + 3)); const meshOpacity = eachMeshMaterial[(meshIndex * 6) + 3] / 255.0; const meshMetallic = eachMeshMaterial[(meshIndex * 6) + 4] / 255.0; const meshRoughness = eachMeshMaterial[(meshIndex * 6) + 5] / 255.0; const meshId = manifestCtx.getNextId(); if (isReusedGeometry) { // Create mesh for multi-use geometry - create (or reuse) geometry, create mesh using that geometry const meshMatrixIndex = eachMeshMatricesPortion[meshIndex]; const meshMatrix = matrices.slice(meshMatrixIndex, meshMatrixIndex + 16); const geometryId = `${modelPartId}-geometry.${tileIndex}.${geometryIndex}`; // These IDs are local to the SceneModel if (!geometryCreated[geometryId]) { const primitiveType = eachGeometryPrimitiveType[geometryIndex]; let primitiveName; let geometryPositions; let geometryNormals; let geometryColors; let geometryIndices; let geometryEdgeIndices; switch (primitiveType) { case 0: primitiveName = "solid"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryNormals = normals.subarray(eachGeometryNormalsPortion [geometryIndex], atLastGeometry ? normals.length : eachGeometryNormalsPortion [geometryIndex + 1]); geometryIndices = indices.subarray(eachGeometryIndicesPortion [geometryIndex], atLastGeometry ? indices.length : eachGeometryIndicesPortion [geometryIndex + 1]); geometryEdgeIndices = edgeIndices.subarray(eachGeometryEdgeIndicesPortion [geometryIndex], atLastGeometry ? edgeIndices.length : eachGeometryEdgeIndicesPortion [geometryIndex + 1]); break; case 1: primitiveName = "surface"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryNormals = normals.subarray(eachGeometryNormalsPortion [geometryIndex], atLastGeometry ? normals.length : eachGeometryNormalsPortion [geometryIndex + 1]); geometryIndices = indices.subarray(eachGeometryIndicesPortion [geometryIndex], atLastGeometry ? indices.length : eachGeometryIndicesPortion [geometryIndex + 1]); geometryEdgeIndices = edgeIndices.subarray(eachGeometryEdgeIndicesPortion [geometryIndex], atLastGeometry ? edgeIndices.length : eachGeometryEdgeIndicesPortion [geometryIndex + 1]); break; case 2: primitiveName = "points"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryColors = convertColorsRGBToRGBA(colors.subarray(eachGeometryColorsPortion [geometryIndex], atLastGeometry ? colors.length : eachGeometryColorsPortion [geometryIndex + 1])); break; case 3: primitiveName = "lines"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryIndices = indices.subarray(eachGeometryIndicesPortion [geometryIndex], atLastGeometry ? indices.length : eachGeometryIndicesPortion [geometryIndex + 1]); break; default: continue; } sceneModel.createGeometry({ id: geometryId, primitive: primitiveName, positionsCompressed: geometryPositions, normalsCompressed: geometryNormals, colors: geometryColors, indices: geometryIndices, edgeIndices: geometryEdgeIndices, positionsDecodeMatrix: reusedGeometriesDecodeMatrix }); geometryCreated[geometryId] = true; } sceneModel.createMesh(utils.apply(meshDefaults, { id: meshId, geometryId: geometryId, origin: tileCenter, matrix: meshMatrix, color: meshColor, metallic: meshMetallic, roughness: meshRoughness, opacity: meshOpacity })); meshIds.push(meshId); } else { const primitiveType = eachGeometryPrimitiveType[geometryIndex]; let primitiveName; let geometryPositions; let geometryNormals; let geometryColors; let geometryIndices; let geometryEdgeIndices; switch (primitiveType) { case 0: primitiveName = "solid"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryNormals = normals.subarray(eachGeometryNormalsPortion [geometryIndex], atLastGeometry ? normals.length : eachGeometryNormalsPortion [geometryIndex + 1]); geometryIndices = indices.subarray(eachGeometryIndicesPortion [geometryIndex], atLastGeometry ? indices.length : eachGeometryIndicesPortion [geometryIndex + 1]); geometryEdgeIndices = edgeIndices.subarray(eachGeometryEdgeIndicesPortion [geometryIndex], atLastGeometry ? edgeIndices.length : eachGeometryEdgeIndicesPortion [geometryIndex + 1]); break; case 1: primitiveName = "surface"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryNormals = normals.subarray(eachGeometryNormalsPortion [geometryIndex], atLastGeometry ? normals.length : eachGeometryNormalsPortion [geometryIndex + 1]); geometryIndices = indices.subarray(eachGeometryIndicesPortion [geometryIndex], atLastGeometry ? indices.length : eachGeometryIndicesPortion [geometryIndex + 1]); geometryEdgeIndices = edgeIndices.subarray(eachGeometryEdgeIndicesPortion [geometryIndex], atLastGeometry ? edgeIndices.length : eachGeometryEdgeIndicesPortion [geometryIndex + 1]); break; case 2: primitiveName = "points"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryColors = convertColorsRGBToRGBA(colors.subarray(eachGeometryColorsPortion [geometryIndex], atLastGeometry ? colors.length : eachGeometryColorsPortion [geometryIndex + 1])); break; case 3: primitiveName = "lines"; geometryPositions = positions.subarray(eachGeometryPositionsPortion [geometryIndex], atLastGeometry ? positions.length : eachGeometryPositionsPortion [geometryIndex + 1]); geometryIndices = indices.subarray(eachGeometryIndicesPortion [geometryIndex], atLastGeometry ? indices.length : eachGeometryIndicesPortion [geometryIndex + 1]); break; default: continue; } sceneModel.createMesh(utils.apply(meshDefaults, { id: meshId, origin: tileCenter, primitive: primitiveName, positionsCompressed: geometryPositions, normalsCompressed: geometryNormals, colors: geometryColors, indices: geometryIndices, edgeIndices: geometryEdgeIndices, positionsDecodeMatrix: tileDecodeMatrix, color: meshColor, metallic: meshMetallic, roughness: meshRoughness, opacity: meshOpacity })); meshIds.push(meshId); } } if (meshIds.length > 0) { sceneModel.createEntity(utils.apply(entityDefaults, { id: entityId, isObject: true, meshIds: meshIds })); } } } } /** @private */ const ParserV7 = { version: 7, parse: function (viewer, options, elements, sceneModel, metaModel, manifestCtx) { const deflatedData = extract(elements); const inflatedData = inflate(deflatedData); load(viewer, options, inflatedData, sceneModel, metaModel, manifestCtx); } }; export {ParserV7};