UNPKG

@itwin/core-frontend

Version:
1,051 lines • 53 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Tiles */ Object.defineProperty(exports, "__esModule", { value: true }); exports.toVertexTable = toVertexTable; exports.edgeParamsFromImdl = edgeParamsFromImdl; exports.edgeParamsToImdl = edgeParamsToImdl; exports.toMaterialParams = toMaterialParams; exports.convertFeatureTable = convertFeatureTable; exports.parseImdlDocument = parseImdlDocument; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const MeshPrimitive_1 = require("../internal/render/MeshPrimitive"); const SurfaceParams_1 = require("../internal/render/SurfaceParams"); const DisplayParams_1 = require("../internal/render/DisplayParams"); const AuxChannelTable_1 = require("../internal/render/AuxChannelTable"); const VertexTableSplitter_1 = require("../internal/render/VertexTableSplitter"); const AnimationNodeId_1 = require("../internal/render/AnimationNodeId"); const VertexIndices_1 = require("../internal/render/VertexIndices"); const CompactEdges_1 = require("./CompactEdges"); const internal_1 = require("../../tile/internal"); const nodeIdRegex = /Node_(.*)/; function extractNodeId(nodeName) { const match = nodeName.match(nodeIdRegex); (0, core_bentley_1.assert)(!!match && match.length === 2); if (!match || match.length !== 2) return 0; const nodeId = Number.parseInt(match[1], 10); (0, core_bentley_1.assert)(!Number.isNaN(nodeId)); return Number.isNaN(nodeId) ? 0 : nodeId; } class Texture extends core_common_1.RenderTexture { constructor(type) { super(type); } dispose() { } get bytesUsed() { return 0; } } class NamedTexture extends Texture { _name; constructor(_name, type) { super(type); this._name = _name; } toImdl() { return this._name; } } class GradientTexture extends Texture { _gradient; constructor(_gradient) { super(core_common_1.RenderTexture.Type.Normal); this._gradient = _gradient; } toImdl() { return this._gradient; } } class Material extends core_common_1.RenderMaterial { materialParams; toImdl() { const material = this.key ?? this.materialParams; return { isAtlas: false, material }; } constructor(params, imdl) { super(params); this.materialParams = imdl ?? { alpha: params.alpha, diffuse: { color: params.diffuseColor?.toJSON(), weight: params.diffuse, }, specular: { color: params.specularColor?.toJSON(), weight: params.specular, exponent: params.specularExponent, }, }; } static create(args) { const params = new core_common_1.RenderMaterialParams(); params.alpha = args.alpha; if (args.diffuse) { if (undefined !== args.diffuse.weight) params.diffuse = args.diffuse?.weight; if (args.diffuse?.color) params.diffuseColor = args.diffuse.color instanceof core_common_1.ColorDef ? args.diffuse.color : core_common_1.RgbColor.fromJSON(args.diffuse.color).toColorDef(); } if (args.specular) { if (undefined !== args.specular.weight) params.specular = args.specular.weight; if (undefined !== args.specular.exponent) params.specularExponent = args.specular.exponent; if (args.specular.color) params.specularColor = args.specular.color instanceof core_common_1.ColorDef ? args.specular.color : core_common_1.RgbColor.fromJSON(args.specular.color).toColorDef(); } return new Material(params); } } /** @internal */ function toVertexTable(imdl) { return { ...imdl, uniformColor: undefined !== imdl.uniformColor ? core_common_1.ColorDef.fromJSON(imdl.uniformColor) : undefined, qparams: core_common_1.QParams3d.fromJSON(imdl.qparams), uvParams: imdl.uvParams ? core_common_1.QParams2d.fromJSON(imdl.uvParams) : undefined, }; } function fromVertexTable(table) { return { ...table, uniformColor: table.uniformColor?.toJSON(), qparams: table.qparams.toJSON(), uvParams: table.uvParams?.toJSON(), }; } /** @internal */ function edgeParamsFromImdl(imdl) { return { ...imdl, segments: imdl.segments ? { ...imdl.segments, indices: new VertexIndices_1.VertexIndices(imdl.segments.indices), } : undefined, silhouettes: imdl.silhouettes ? { ...imdl.silhouettes, indices: new VertexIndices_1.VertexIndices(imdl.silhouettes.indices), } : undefined, polylineGroups: imdl.polylines ? [{ polyline: { ...imdl.polylines, indices: new VertexIndices_1.VertexIndices(imdl.polylines.indices), prevIndices: new VertexIndices_1.VertexIndices(imdl.polylines.prevIndices), }, }] : undefined, indexed: imdl.indexed ? { indices: new VertexIndices_1.VertexIndices(imdl.indexed.indices), edges: imdl.indexed.edges, } : undefined, }; } /** @internal */ function edgeParamsToImdl(params) { return { ...params, segments: params.segments ? { ...params.segments, indices: params.segments.indices.data, } : undefined, silhouettes: params.silhouettes ? { ...params.silhouettes, indices: params.silhouettes.indices.data, } : undefined, polylines: params.polylineGroups?.length === 1 ? { ...params.polylineGroups[0].polyline, indices: params.polylineGroups[0].polyline.indices.data, prevIndices: params.polylineGroups[0].polyline.prevIndices.data, } : undefined, indexed: params.indexed ? { indices: params.indexed.indices.data, edges: params.indexed.edges, } : undefined, }; } class Parser { _document; _binaryData; _options; _featureTableInfo; _patterns = new Map(); _stream; _timeline; _meshoptDecoder; constructor(doc, binaryData, options, featureTableInfo, stream) { this._document = doc; this._binaryData = binaryData; this._options = options; this._featureTableInfo = featureTableInfo; this._stream = stream; this._timeline = options.timeline; } async parse() { const featureTable = this.parseFeatureTable(); if (!featureTable) return core_common_1.TileReadStatus.InvalidFeatureTable; if (this.hasMeshoptCompression()) { this._meshoptDecoder = await (0, internal_1.getMeshoptDecoder)(); if (!this._meshoptDecoder) return core_common_1.TileReadStatus.InvalidTileData; } const rtcCenter = this._document.rtcCenter ? { x: this._document.rtcCenter[0] ?? 0, y: this._document.rtcCenter[1] ?? 0, z: this._document.rtcCenter[2] ?? 0, } : undefined; const primitiveNodes = this.parseNodes(featureTable); const nodes = this.groupPrimitiveNodes(primitiveNodes, featureTable); return { featureTable, nodes, rtcCenter, binaryData: this._binaryData, json: this._document, patterns: this._patterns, }; } hasMeshoptCompression() { let hasMeshoptCompression = false; for (const meshKey of Object.keys(this._document.meshes)) { const mesh = this._document.meshes[meshKey]; mesh?.primitives?.forEach((primitive) => { if (primitive.type !== "areaPattern") { const imdlPrimitive = primitive; const vertexTable = imdlPrimitive.vertices; if (vertexTable.compressedSize && vertexTable.compressedSize > 0) { hasMeshoptCompression = true; } const surf = imdlPrimitive.surface; if (surf && surf.compressedIndexCount && surf.compressedIndexCount > 0) { hasMeshoptCompression = true; } } }); } return hasMeshoptCompression; } parseFeatureTable() { this._stream.curPos = this._featureTableInfo.startPos; const header = core_common_1.FeatureTableHeader.readFrom(this._stream); if (!header || 0 !== header.length % 4) return undefined; // NB: We make a copy of the sub-array because we don't want to pin the entire data array in memory. const numUint32s = (header.length - core_common_1.FeatureTableHeader.sizeInBytes) / 4; const packedFeatureArray = new Uint32Array(this._stream.nextUint32s(numUint32s)); if (this._stream.isPastTheEnd) return undefined; let featureTable; if (this._featureTableInfo.multiModel) { featureTable = { multiModel: true, data: packedFeatureArray, numFeatures: header.count, numSubCategories: header.numSubCategories, }; } else { let animNodesArray; const animationNodes = this._document.animationNodes; if (undefined !== animationNodes) { const bytesPerId = core_bentley_1.JsonUtils.asInt(animationNodes.bytesPerId); const bufferViewId = core_bentley_1.JsonUtils.asString(animationNodes.bufferView); const bufferViewJson = this._document.bufferViews[bufferViewId]; if (undefined !== bufferViewJson) { const byteOffset = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteOffset); const byteLength = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteLength); const bytes = this._binaryData.subarray(byteOffset, byteOffset + byteLength); switch (bytesPerId) { case 1: animNodesArray = new Uint8Array(bytes); break; case 2: // NB: A *copy* of the subarray. animNodesArray = Uint16Array.from(new Uint16Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 2)); break; case 4: // NB: A *copy* of the subarray. animNodesArray = Uint32Array.from(new Uint32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4)); break; } } } featureTable = { multiModel: false, data: packedFeatureArray, numFeatures: header.count, animationNodeIds: animNodesArray, }; } this._stream.curPos = this._featureTableInfo.startPos + header.length; return featureTable; } parseNodes(featureTable) { const nodes = []; const docNodes = this._document.nodes; const docMeshes = this._document.meshes; if (undefined === docNodes.Node_Root) { // A veeeery early version of the tile format (prior to introduction of schedule animation support) just supplied a flat list of meshes. // We shall never encounter such tiles again. return nodes; } for (const nodeKey of Object.keys(docNodes)) { const docNode = this._document.nodes[nodeKey]; (0, core_bentley_1.assert)(undefined !== docNode); // we're iterating the keys... const docMesh = docMeshes[docNode]; const docPrimitives = docMesh?.primitives; if (!docPrimitives) continue; const layerId = docMesh.layer; if ("Node_Root" === nodeKey) { if (this._timeline) { // Split up the root node into transform nodes. this.parseAnimationBranches(nodes, docMesh, featureTable, this._timeline); } else if (this._options.createUntransformedRootNode) { // If transform nodes exist in the tile tree, then we need to create a branch for the root node so that elements not associated with // any node in the schedule script can be grouped together. nodes.push({ animationNodeId: AnimationNodeId_1.AnimationNodeId.Untransformed, primitives: this.parseNodePrimitives(docPrimitives), }); } else { nodes.push({ primitives: this.parseNodePrimitives(docPrimitives) }); } } else if (undefined === layerId) { nodes.push({ animationNodeId: extractNodeId(nodeKey), animationId: `${this._options.batchModelId}_${nodeKey}`, primitives: this.parseNodePrimitives(docPrimitives), }); } else { nodes.push({ layerId, primitives: this.parseNodePrimitives(docPrimitives), }); } } return nodes; } parseAnimationBranches(output, docMesh, imdlFeatureTable, timeline) { const docPrimitives = docMesh.primitives; if (!docPrimitives) return; const primitives = docPrimitives.map((x) => this.parseNodePrimitive(x)).filter((x) => x !== undefined); if (primitives.length === 0) return; const nodesById = new Map(); const getNode = (nodeId) => { nodeId = nodeId ?? AnimationNodeId_1.AnimationNodeId.Untransformed; let node = nodesById.get(nodeId); if (!node) { node = { animationNodeId: nodeId, animationId: `${this._options.batchModelId}_Node_${nodeId}`, primitives: [], }; nodesById.set(nodeId, node); output.push(node); } return node; }; // NB: The BatchType is irrelevant - just use Primary. (0, core_bentley_1.assert)(undefined === imdlFeatureTable.animationNodeIds); const featureTable = convertFeatureTable(imdlFeatureTable, this._options.batchModelId); featureTable.populateAnimationNodeIds((feature) => timeline.getBatchIdForFeature(feature), timeline.maxBatchId); imdlFeatureTable.animationNodeIds = featureTable.animationNodeIds; const discreteNodeIds = timeline.discreteBatchIds; const computeNodeId = (featureIndex) => { const nodeId = featureTable.getAnimationNodeId(featureIndex); return 0 !== nodeId && discreteNodeIds.has(nodeId) ? nodeId : 0; }; this.splitPrimitives(primitives, featureTable, computeNodeId, getNode); } splitPrimitives(primitives, featureTable, computeNodeId, getPrimitivesNode) { const splitArgs = { maxDimension: this._options.maxVertexTableSize, computeNodeId, featureTable, }; const convertMaterial = (imdl) => { if (!imdl) return undefined; else if (imdl.isAtlas) return imdl; const material = (typeof imdl.material === "string") ? this.materialFromJson(imdl.material) : Material.create(toMaterialParams(imdl.material)); return material ? { isAtlas: false, material } : undefined; }; for (const primitive of primitives) { switch (primitive.type) { case "pattern": { // ###TODO splitting area patterns getPrimitivesNode(undefined).primitives.push(primitive); break; } case "mesh": { const mesh = primitive.params; const texMap = mesh.surface.textureMapping; const params = { vertices: toVertexTable(primitive.params.vertices), surface: { ...primitive.params.surface, indices: new VertexIndices_1.VertexIndices(primitive.params.surface.indices), material: convertMaterial(mesh.surface.material), textureMapping: texMap ? { alwaysDisplayed: texMap.alwaysDisplayed, // The texture type doesn't actually matter here. texture: typeof texMap.texture === "string" ? new NamedTexture(texMap.texture, core_common_1.RenderTexture.Type.Normal) : new GradientTexture(texMap.texture), } : undefined, }, edges: primitive.params.edges ? edgeParamsFromImdl(primitive.params.edges) : undefined, isPlanar: primitive.params.isPlanar, auxChannels: primitive.params.auxChannels ? AuxChannelTable_1.AuxChannelTable.fromJSON(primitive.params.auxChannels) : undefined, }; const split = (0, VertexTableSplitter_1.splitMeshParams)({ ...splitArgs, params, createMaterial: (args) => Material.create(args), }); for (const [nodeId, p] of split) { let material; if (p.surface.material) { if (p.surface.material.isAtlas) { material = p.surface.material; } else { (0, core_bentley_1.assert)(p.surface.material.material instanceof Material); material = p.surface.material.material.toImdl(); } } (0, core_bentley_1.assert)(p.surface.textureMapping === undefined || p.surface.textureMapping.texture instanceof Texture); getPrimitivesNode(nodeId).primitives.push({ type: "mesh", modifier: primitive.modifier, params: { vertices: fromVertexTable(p.vertices), surface: { ...p.surface, indices: p.surface.indices.data, material, textureMapping: p.surface.textureMapping?.texture instanceof Texture ? { texture: p.surface.textureMapping.texture.toImdl(), alwaysDisplayed: p.surface.textureMapping.alwaysDisplayed, } : undefined, }, edges: p.edges ? edgeParamsToImdl(p.edges) : undefined, isPlanar: p.isPlanar, auxChannels: p.auxChannels?.toJSON(), }, }); } break; } case "point": { const params = { vertices: toVertexTable(primitive.params.vertices), indices: new VertexIndices_1.VertexIndices(primitive.params.indices), weight: primitive.params.weight, }; const split = (0, VertexTableSplitter_1.splitPointStringParams)({ ...splitArgs, params }); for (const [nodeId, p] of split) { getPrimitivesNode(nodeId).primitives.push({ type: "point", modifier: primitive.modifier, params: { vertices: fromVertexTable(p.vertices), indices: p.indices.data, weight: p.weight, }, }); } break; } case "polyline": { const params = { ...primitive.params, vertices: toVertexTable(primitive.params.vertices), polyline: { indices: new VertexIndices_1.VertexIndices(primitive.params.polyline.indices), prevIndices: new VertexIndices_1.VertexIndices(primitive.params.polyline.prevIndices), nextIndicesAndParams: primitive.params.polyline.nextIndicesAndParams, }, }; const split = (0, VertexTableSplitter_1.splitPolylineParams)({ ...splitArgs, params }); for (const [nodeId, p] of split) { getPrimitivesNode(nodeId).primitives.push({ type: "polyline", modifier: primitive.modifier, params: { ...p, vertices: fromVertexTable(p.vertices), polyline: { indices: p.polyline.indices.data, prevIndices: p.polyline.prevIndices.data, nextIndicesAndParams: p.polyline.nextIndicesAndParams, }, }, }); } break; } } } } groupPrimitiveNodes(inputNodes, imdlFeatureTable) { const modelGroups = this._options.modelGroups; if (!modelGroups?.length) return inputNodes; const groupNodes = []; let orphanNode; const getGroupNode = (groupId) => { (0, core_bentley_1.assert)(groupId <= modelGroups.length); if (groupId === modelGroups.length) { // This would happen if: // - The tile contains geometry from a model not present in modelGroups (should never occur); or // - The tile contains an area pattern (we haven't yet implemented splitting for them). // In either case, orphaned geometry will end up getting discarded. return orphanNode ?? (orphanNode = { groupId, nodes: [] }); } let groupNode = groupNodes[groupId]; if (!groupNode) groupNodes[groupId] = groupNode = { groupId, nodes: [] }; return groupNode; }; const featureTable = convertFeatureTable(imdlFeatureTable, this._options.batchModelId); const modelIdPair = { lower: 0, upper: 0 }; const computeNodeId = (featureIndex) => { featureTable.getModelIdPair(featureIndex, modelIdPair); const modelId = core_bentley_1.Id64.fromUint32PairObject(modelIdPair); for (let i = 0; i < modelGroups.length; i++) { if (modelGroups[i].has(modelId)) return i; } return modelGroups.length; }; for (const inputNode of inputNodes) { // Indexed by model group index. const splitNodes = []; const getSplitNode = (groupIndex) => { groupIndex = groupIndex ?? modelGroups.length; if (!splitNodes[groupIndex]) { const splitNode = splitNodes[groupIndex] = { ...inputNode, primitives: [] }; getGroupNode(groupIndex).nodes.push(splitNode); } return splitNodes[groupIndex]; }; this.splitPrimitives(inputNode.primitives, featureTable, computeNodeId, getSplitNode); } return groupNodes.filter((x) => undefined !== x); } parseTesselatedPolyline(json) { const indices = this.findBuffer(json.indices); const prevIndices = this.findBuffer(json.prevIndices); const nextIndicesAndParams = this.findBuffer(json.nextIndicesAndParams); return indices && prevIndices && nextIndicesAndParams ? { indices, prevIndices, nextIndicesAndParams } : undefined; } parseSegmentEdges(imdl) { const indices = this.findBuffer(imdl.indices); const endPointAndQuadIndices = this.findBuffer(imdl.endPointAndQuadIndices); return indices && endPointAndQuadIndices ? { indices, endPointAndQuadIndices } : undefined; } parseSilhouetteEdges(imdl) { const segments = this.parseSegmentEdges(imdl); const normalPairs = this.findBuffer(imdl.normalPairs); return segments && normalPairs ? { ...segments, normalPairs } : undefined; } parseIndexedEdges(imdl) { const indices = this.findBuffer(imdl.indices); const edgeTable = this.findBuffer(imdl.edges); if (!indices || !edgeTable) return undefined; return { indices, edges: { data: edgeTable, width: imdl.width, height: imdl.height, silhouettePadding: imdl.silhouettePadding, numSegments: imdl.numSegments, }, }; } parseCompactEdges(imdl, vertexIndices) { const visibility = this.findBuffer(imdl.visibility); if (!visibility) return undefined; const normals = undefined !== imdl.normalPairs ? this.findBuffer(imdl.normalPairs) : undefined; return (0, CompactEdges_1.indexedEdgeParamsFromCompactEdges)({ numVisibleEdges: imdl.numVisible, visibility, vertexIndices, normalPairs: normals ? new Uint32Array(normals.buffer, normals.byteOffset, normals.byteLength / 4) : undefined, maxEdgeTableDimension: this._options.maxVertexTableSize, }); } parseEdges(imdl, displayParams, indices) { if (!imdl) return undefined; const segments = imdl.segments ? this.parseSegmentEdges(imdl.segments) : undefined; const silhouettes = imdl.silhouettes ? this.parseSilhouetteEdges(imdl.silhouettes) : undefined; const polylines = imdl.polylines ? this.parseTesselatedPolyline(imdl.polylines) : undefined; let indexed = imdl.indexed ? this.parseIndexedEdges(imdl.indexed) : undefined; if (!indexed && imdl.compact) indexed = this.parseCompactEdges(imdl.compact, new VertexIndices_1.VertexIndices(indices)); if (!segments && !silhouettes && !indexed && !polylines) return undefined; return { segments, silhouettes, polylines, indexed, weight: displayParams.width, linePixels: displayParams.linePixels, }; } getPattern(name) { let primitives = this._patterns.get(name); if (!primitives) { const symbol = this._document.patternSymbols[name]; primitives = symbol ? this.parsePrimitives(symbol.primitives) : []; this._patterns.set(name, primitives); } return primitives.length > 0 ? primitives : undefined; } parseAreaPattern(json) { const primitives = this.getPattern(json.symbolName); if (!primitives || primitives.length === 0) return undefined; const xyOffsets = this.findBuffer(json.xyOffsets); if (!xyOffsets) return undefined; return { type: "pattern", params: { ...json, xyOffsets: new Float32Array(xyOffsets.buffer, xyOffsets.byteOffset, xyOffsets.byteLength / 4), }, }; } parseNodePrimitives(docPrimitives) { const primitives = []; for (const docPrimitive of docPrimitives) { const primitive = this.parseNodePrimitive(docPrimitive); if (primitive) primitives.push(primitive); } return primitives; } parseNodePrimitive(docPrimitive) { return docPrimitive.type === "areaPattern" ? this.parseAreaPattern(docPrimitive) : this.parsePrimitive(docPrimitive); } parsePrimitives(docPrimitives) { const primitives = []; for (const docPrimitive of docPrimitives) { const primitive = this.parsePrimitive(docPrimitive); if (primitive) primitives.push(primitive); } return primitives; } parsePrimitive(docPrimitive) { let modifier = this.parseInstances(docPrimitive); if (!modifier && docPrimitive.viewIndependentOrigin) { const origin = core_geometry_1.Point3d.fromJSON(docPrimitive.viewIndependentOrigin); modifier = { type: "viewIndependentOrigin", origin: { x: origin.x, y: origin.y, z: origin.z }, }; } const materialName = docPrimitive.material ?? ""; const dpMaterial = materialName.length ? core_bentley_1.JsonUtils.asObject(this._document.materials[materialName]) : undefined; const displayParams = dpMaterial ? this.parseDisplayParams(dpMaterial) : undefined; if (!displayParams) return undefined; const vertices = this.parseVertexTable(docPrimitive); if (!vertices) return undefined; let primitive; const isPlanar = !this._options.is3d || core_bentley_1.JsonUtils.asBool(docPrimitive.isPlanar); switch (docPrimitive.type) { case MeshPrimitive_1.MeshPrimitiveType.Mesh: { const surface = this.parseSurface(docPrimitive, displayParams); if (surface) { primitive = { type: "mesh", params: { vertices, surface, isPlanar, auxChannels: this.parseAuxChannelTable(docPrimitive), edges: this.parseEdges(docPrimitive.edges, displayParams, surface.indices), }, }; } break; } case MeshPrimitive_1.MeshPrimitiveType.Polyline: { const polyline = this.parseTesselatedPolyline(docPrimitive); if (polyline) { let type = core_common_1.PolylineTypeFlags.Normal; if (DisplayParams_1.DisplayParams.RegionEdgeType.Outline === displayParams.regionEdgeType) type = (!displayParams.gradient || displayParams.gradient.isOutlined) ? core_common_1.PolylineTypeFlags.Edge : core_common_1.PolylineTypeFlags.Outline; primitive = { type: "polyline", params: { vertices, polyline, isPlanar, type, weight: displayParams.width, linePixels: displayParams.linePixels, }, }; } break; } case MeshPrimitive_1.MeshPrimitiveType.Point: { const indices = this.findBuffer(docPrimitive.indices); const weight = displayParams.width; if (indices) { primitive = { type: "point", params: { vertices, indices, weight }, }; } break; } } if (primitive) primitive.modifier = modifier; return primitive; } parseSurface(mesh, displayParams) { const surf = mesh.surface; if (!surf) return undefined; let indices = this.findBuffer(surf.indices); if (!indices) return undefined; if (surf.compressedIndexCount && surf.compressedIndexCount > 0) { if (!this._meshoptDecoder) { return undefined; } const decompressedIndices = new Uint8Array(surf.compressedIndexCount * 4); this._meshoptDecoder.decodeIndexSequence(decompressedIndices, surf.compressedIndexCount, 4, indices); // reduce from 32 to 24 bits indices = new Uint8Array(surf.compressedIndexCount * 3); for (let i = 0; i < surf.compressedIndexCount; i++) { const srcIndex = i * 4; const dstIndex = i * 3; indices[dstIndex + 0] = decompressedIndices[srcIndex + 0]; indices[dstIndex + 1] = decompressedIndices[srcIndex + 1]; indices[dstIndex + 2] = decompressedIndices[srcIndex + 2]; } } const type = surf.type; if (!(0, SurfaceParams_1.isValidSurfaceType)(type)) return undefined; const texture = displayParams.textureMapping?.texture; let material; const atlas = mesh.vertices.materialAtlas; const numColors = mesh.vertices.numColors; if (atlas && undefined !== numColors) { material = { isAtlas: true, hasTranslucency: core_bentley_1.JsonUtils.asBool(atlas.hasTranslucency), overridesAlpha: core_bentley_1.JsonUtils.asBool(atlas.overridesAlpha, false), vertexTableOffset: core_bentley_1.JsonUtils.asInt(numColors), numMaterials: core_bentley_1.JsonUtils.asInt(atlas.numMaterials), }; } else if (displayParams.material) { (0, core_bentley_1.assert)(displayParams.material instanceof Material); material = displayParams.material.toImdl(); } let textureMapping; if (texture) { (0, core_bentley_1.assert)(texture instanceof Texture); textureMapping = { texture: texture.toImdl(), alwaysDisplayed: core_bentley_1.JsonUtils.asBool(surf.alwaysDisplayTexture), }; } return { type, indices, fillFlags: displayParams.fillFlags, hasBakedLighting: false, material, textureMapping, }; } parseAuxChannelTable(primitive) { const json = primitive.auxChannels; if (undefined === json) return undefined; const bytes = this.findBuffer(core_bentley_1.JsonUtils.asString(json.bufferView)); if (undefined === bytes) return undefined; return { data: bytes, width: json.width, height: json.height, count: json.count, numBytesPerVertex: json.numBytesPerVertex, displacements: json.displacements, normals: json.normals, params: json.params, }; } parseVertexTable(primitive) { const json = primitive.vertices; if (!json) return undefined; let bytes; if (json.compressedSize && json.compressedSize > 0) { if (!this._meshoptDecoder) { return undefined; } const bufferViewJson = this._document.bufferViews[core_bentley_1.JsonUtils.asString(json.bufferView)]; if (undefined === bufferViewJson) return undefined; const byteOffset = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteOffset); const byteLength = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteLength); if (0 === byteLength) return undefined; const compressedBytes = this._binaryData.subarray(byteOffset, byteOffset + json.compressedSize); if (!compressedBytes) return undefined; bytes = new Uint8Array(json.width * json.height * 4); this._meshoptDecoder.decodeVertexBuffer(bytes, json.count, json.numRgbaPerVertex * 4, compressedBytes); const remainingBytesSize = byteLength - json.compressedSize; // if there are remaining bytes, copy the data that did not go through the compression if (remainingBytesSize > 0) { const remainingBytes = this._binaryData.subarray(byteOffset + json.compressedSize, byteOffset + byteLength); if (!remainingBytes) return undefined; const decompressedSize = json.count * json.numRgbaPerVertex * 4; for (let i = 0; i < remainingBytesSize; i++) { bytes[decompressedSize + i] = remainingBytes[i]; } } } else { bytes = this.findBuffer(core_bentley_1.JsonUtils.asString(json.bufferView)); if (!bytes) return undefined; } const uniformFeatureID = undefined !== json.featureID ? core_bentley_1.JsonUtils.asInt(json.featureID) : undefined; const rangeMin = core_bentley_1.JsonUtils.asArray(json.params.decodedMin); const rangeMax = core_bentley_1.JsonUtils.asArray(json.params.decodedMax); if (undefined === rangeMin || undefined === rangeMax) return undefined; const qparams = core_common_1.QParams3d.fromRange(core_geometry_1.Range3d.create(core_geometry_1.Point3d.create(rangeMin[0], rangeMin[1], rangeMin[2]), core_geometry_1.Point3d.create(rangeMax[0], rangeMax[1], rangeMax[2]))); const uniformColor = undefined !== json.uniformColor ? core_common_1.ColorDef.fromJSON(json.uniformColor) : undefined; let uvParams; if (MeshPrimitive_1.MeshPrimitiveType.Mesh === primitive.type && primitive.surface && primitive.surface.uvParams) { const uvMin = primitive.surface.uvParams.decodedMin; const uvMax = primitive.surface.uvParams.decodedMax; const uvRange = new core_geometry_1.Range2d(uvMin[0], uvMin[1], uvMax[0], uvMax[1]); uvParams = core_common_1.QParams2d.fromRange(uvRange); } return { data: bytes, usesUnquantizedPositions: true === json.usesUnquantizedPositions, qparams: qparams.toJSON(), width: json.width, height: json.height, hasTranslucency: json.hasTranslucency, uniformColor: uniformColor?.toJSON(), featureIndexType: json.featureIndexType, uniformFeatureID, numVertices: json.count, numRgbaPerVertex: json.numRgbaPerVertex, uvParams: uvParams?.toJSON(), }; } parseInstances(primitive) { const json = primitive.instances; if (!json) return undefined; const count = core_bentley_1.JsonUtils.asInt(json.count, 0); if (count <= 0) return undefined; const centerComponents = core_bentley_1.JsonUtils.asArray(json.transformCenter); if (undefined === centerComponents || 3 !== centerComponents.length) return undefined; const transformCenter = core_geometry_1.Point3d.create(centerComponents[0], centerComponents[1], centerComponents[2]); const featureIds = this.findBuffer(core_bentley_1.JsonUtils.asString(json.featureIds)); if (undefined === featureIds) return undefined; const transformBytes = this.findBuffer(core_bentley_1.JsonUtils.asString(json.transforms)); if (undefined === transformBytes) return undefined; // 1 transform = 3 rows of 4 floats = 12 floats per instance const numFloats = transformBytes.byteLength / 4; (0, core_bentley_1.assert)(Math.floor(numFloats) === numFloats); (0, core_bentley_1.assert)(0 === numFloats % 12); const transforms = new Float32Array(transformBytes.buffer, transformBytes.byteOffset, numFloats); let symbologyOverrides; if (undefined !== json.symbologyOverrides) symbologyOverrides = this.findBuffer(core_bentley_1.JsonUtils.asString(json.symbologyOverrides)); return { type: "instances", count, transforms, transformCenter, featureIds, symbologyOverrides, }; } findBuffer(bufferViewId) { if (typeof bufferViewId !== "string" || 0 === bufferViewId.length) return undefined; const bufferViewJson = this._document.bufferViews[bufferViewId]; if (undefined === bufferViewJson) return undefined; const byteOffset = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteOffset); const byteLength = core_bentley_1.JsonUtils.asInt(bufferViewJson.byteLength); if (0 === byteLength) return undefined; return this._binaryData.subarray(byteOffset, byteOffset + byteLength); } colorDefFromMaterialJson(json) { return undefined !== json ? core_common_1.ColorDef.from(json[0] * 255 + 0.5, json[1] * 255 + 0.5, json[2] * 255 + 0.5) : undefined; } materialFromJson(key) { const materialJson = this._document.renderMaterials[key]; if (!materialJson) return undefined; const materialParams = new core_common_1.RenderMaterialParams(key); materialParams.diffuseColor = this.colorDefFromMaterialJson(materialJson.diffuseColor); if (materialJson.diffuse !== undefined) materialParams.diffuse = core_bentley_1.JsonUtils.asDouble(materialJson.diffuse); materialParams.specularColor = this.colorDefFromMaterialJson(materialJson.specularColor); if (materialJson.specular !== undefined) materialParams.specular = core_bentley_1.JsonUtils.asDouble(materialJson.specular); materialParams.reflectColor = this.colorDefFromMaterialJson(materialJson.reflectColor); if (materialJson.reflect !== undefined) materialParams.reflect = core_bentley_1.JsonUtils.asDouble(materialJson.reflect); if (materialJson.specularExponent !== undefined) materialParams.specularExponent = materialJson.specularExponent; if (undefined !== materialJson.transparency) materialParams.alpha = 1.0 - materialJson.transparency; materialParams.refract = core_bentley_1.JsonUtils.asDouble(materialJson.refract); materialParams.shadows = core_bentley_1.JsonUtils.asBool(materialJson.shadows); materialParams.ambient = core_bentley_1.JsonUtils.asDouble(materialJson.ambient); if (undefined !== materialJson.textureMapping) materialParams.textureMapping = this.textureMappingFromJson(materialJson.textureMapping.texture); return new Material(materialParams); } parseNamedTexture(namedTex, name) { const textureType = core_bentley_1.JsonUtils.asBool(namedTex.isGlyph) ? core_common_1.RenderTexture.Type.Glyph : (core_bentley_1.JsonUtils.asBool(namedTex.isTileSection) ? core_common_1.RenderTexture.Type.TileSection : core_common_1.RenderTexture.Type.Normal); return new NamedTexture(name, textureType); } parseConstantLodProps(propsJson) { if (undefined === propsJson) return undefined; return { repetitions: core_bentley_1.JsonUtils.asDouble(propsJson.repetitions, 1.0), offset: { x: propsJson.offset ? core_bentley_1.JsonUtils.asDouble(propsJson.offset[0]) : 0.0, y: propsJson.offset ? core_bentley_1.JsonUtils.asDouble(propsJson.offset[1]) : 0.0 }, minDistClamp: core_bentley_1.JsonUtils.asDouble(propsJson.minDistClamp, 1.0), maxDistClamp: core_bentley_1.JsonUtils.asDouble(propsJson.maxDistClamp, 4096.0 * 1024.0 * 1024.0), }; } textureMappingFromJson(json) { if (!json) return undefined; const name = core_bentley_1.JsonUtils.asString(json.name); const namedTex = 0 !== name.length ? this._document.namedTextures[name] : undefined; const texture = namedTex ? this.parseNamedTexture(namedTex, name) : undefined; if (!texture) return undefined; const paramsJson = json.params; const tf = paramsJson.transform; const paramProps = { textureMat2x3: new core_common_1.TextureMapping.Trans2x3(tf[0][0], tf[0][1], tf[0][2], tf[1][0], tf[1][1], tf[1][2]), textureWeight: core_bentley_1.JsonUtils.asDouble(paramsJson.weight, 1.0), mapMode: core_bentley_1.JsonUtils.asInt(paramsJson.mode), worldMapping: core_bentley_1.JsonUtils.asBool(paramsJson.worldMapping), useConstantLod: core_bentley_1.JsonUtils.asBool(paramsJson.useConstantLod), constantLodProps: this.parseConstantLodProps(paramsJson.constantLodParams), }; const textureMapping = new core_common_1.TextureMapping(texture, new core_common_1.TextureMapping.Params(paramProps)); const normalMapJson = json.normalMapParams; if (normalMapJson) { const normalTexName = core_bentley_1.JsonUtils.asString(normalMapJson.textureName); const namedNormalTex = normalTexName.length > 0 ? this._document.namedTextures[normalTexName] : undefined; const normalMap = namedNormalTex ? this.parseNamedTexture(namedNormalTex, normalTexName) : undefined; if (normalMap) { textureMapping.normalMapParams = { normalMap, greenUp: core_bentley_1.JsonUtils.asBool(normalMapJson.greenUp), scale: core_bentley_1.JsonUtils.asDouble(normalMapJson.scale, 1), useConstantLod: core_bentley_1.JsonUtils.asBool(normalMapJson.useConstantLod), }; } } return textureMapping; } parseDisplayParams(json) { const type = core_bentley_1.JsonUtils.asInt(json.type, DisplayParams_1.DisplayParams.Type.Mesh); const lineColor = core_common_1.ColorDef.create(core_bentley_1.JsonUtils.asInt(json.lineColor)); const fillColor = core_common_1.ColorDef.create(core_bentley_1.JsonUtils.asInt(json.fillColor)); const width = core_bentley_1.JsonUtils.asInt(json.lineWidth); const linePixels = core_bentley_1.JsonUtils.asInt(json.linePixels, core_common_1.LinePixels.Solid); const fillFlags = core_bentley_1.JsonUtils.asInt(json.fillFlags, core_common_1.FillFlags.None); const ignoreLighting = core_bentley_1.JsonUtils.asBool(json.ignoreLighting); // Material will always contain its own texture if it has one const materialKey = json.materialId; const material = undefined !== materialKey ? this.materialFromJson(materialKey) : undefined; // We will only attempt to include the texture if material is undefined let textureMapping; let gradient; if (!material) { const textureJson = json.texture; textureMapping = undefined !== textureJson ? this.textureMappingFromJson(textureJson) : undefined; if (undefined === textureMapping) { const gradientProps = json.gradient; gradient = undefined !== gradientProps ? core_common_1.Gradient.Symb.fromJSON(gradientProps) : undefined; if (gradient) { (0, core_bentley_1.assert)(undefined !== gradientProps); const texture = new GradientTexture(gradientProps); textureMapping = new core_common_1.TextureMapping(texture, new core_common_1.TextureMapping.Params({ textureMat2x3: new core_common_1.TextureMapping.Trans2x3(0, 1, 0, 1, 0, 0) })); } } } return new DisplayParams_1.DisplayParams(type, lineColor, fillColor, width, linePixels, fillFlags, material, gradient, ignoreLighting, textureMapping); } } /** @internal */ function toMaterialParams(mat) { const args = { alpha: mat.alpha }; if (mat.diffuse) { args.diffuse = { weight: mat.diffuse.weight, color: undefined !== mat.diffuse.color ? core_common_1.ColorDef.fromJSON(mat.diffuse.color) : undefined, }; } if (mat.specular) { args.specular = { weight: mat.specular.weight, exponent: mat.specular.exponent, color: undefined !== mat.specular.color ? core_common_1.ColorDef.fromJSON(mat.specular.color) : undefined, }; } return args; } /** @internal */ function convertFeatureTable(imdlFeatureTable, batchModelId) { const table = imdlFeatureTable.multiModel ? core_common_1.MultiModelPackedFeatureTable.create(imdlFeatureTable.data, batchModelId, imdlFeatureTable.numFeatures, core_common_1.BatchType.Primary, imdlFeatureTable.numSubCategories) : new core_common_1.PackedF