@itwin/core-frontend
Version:
iTwin.js frontend components
1,051 lines • 53 kB
JavaScript
"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