UNPKG

@itwin/core-common

Version:

iTwin.js components common to frontend and backend

712 lines • 30.3 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module Tile */ import { assert, compareBooleans, compareBooleansOrUndefined, compareNumbers, compareStrings, compareStringsOrUndefined, Id64, } from "@itwin/core-bentley"; import { Range3d, Vector3d } from "@itwin/core-geometry"; import { BatchType } from "../FeatureTable"; import { CurrentImdlVersion, FeatureTableHeader, ImdlFlags, ImdlHeader } from "./IModelTileIO"; import { TileReadError, TileReadStatus } from "./TileIO"; // cspell:ignore imdl mult bitfield // NB: These constants correspond to those defined in Tile.cpp. var Constants; (function (Constants) { Constants.minToleranceRatioMultiplier = 2; })(Constants || (Constants = {})); function compareEdgeOptions(a, b) { if (typeof a !== typeof b) return a ? 1 : -1; if (typeof a === "boolean") { assert(typeof b === "boolean"); return compareBooleans(a, b); } assert(typeof b === "object"); let cmp = compareStrings(a.type, b.type); if (0 === cmp) cmp = compareBooleans(a.smooth, b.smooth); return cmp; } /** @internal */ export var TileOptions; (function (TileOptions) { /** Given the string representation of an [[IModelTileTreeId]] and the contentId of a [Tile]($frontend) belonging to that [TileTree]($frontend), * compute the [[TileOptions]] that were used to generate the Ids. * @throws Error if `treeId` or `contentId` are not valid Ids. * @note `treeId` and `contentId` are assumed to be valid Ids. They are not fully parsed and validated - only the information required by this function is extracted. * @note `treeId` and `contentId` are assumed to have been produced for version 4 or later of the iMdl tile format. */ function fromTreeIdAndContentId(treeId, contentId) { const tree = treeFlagsAndFormatVersionFromId(treeId); const contentFlags = contentFlagsFromId(contentId); const edgeOptions = edgeOptionsFromTreeId(treeId); return { maximumMajorTileFormatVersion: tree.version, enableInstancing: 0 !== (contentFlags & ContentFlags.AllowInstancing), enableImprovedElision: 0 !== (contentFlags & ContentFlags.ImprovedElision), ignoreAreaPatterns: 0 !== (contentFlags & ContentFlags.IgnoreAreaPatterns), enableExternalTextures: 0 !== (contentFlags & ContentFlags.ExternalTextures), useProjectExtents: 0 !== (tree.flags & TreeFlags.UseProjectExtents), expandProjectExtents: 0 !== (tree.flags & TreeFlags.ExpandProjectExtents), optimizeBRepProcessing: 0 !== (tree.flags & TreeFlags.OptimizeBRepProcessing), useLargerTiles: 0 !== (tree.flags & TreeFlags.UseLargerTiles), disableMagnification: false, alwaysSubdivideIncompleteTiles: false, edgeOptions, disablePolyfaceDecimation: 0 !== (tree.flags & TreeFlags.DisablePolyfaceDecimation), }; } TileOptions.fromTreeIdAndContentId = fromTreeIdAndContentId; })(TileOptions || (TileOptions = {})); class Parser { input; curPos = 0; constructor(input) { this.input = input; } parse(contentId) { this.require(this.input.length > 0); // Skip version and flags, they're handled by TileOptions.fromTreeIdAndContentId while (this.curPos < this.input.length && this.cur() !== "-") this.advance(); this.eat("-"); this.require(this.curPos < this.input.length); const classifier = this.cur() === "C" ? this.parseClassifier() : undefined; const animationId = this.parseAnimation(); const primary = classifier ? undefined : this.parsePrimary(); this.require((undefined === classifier) !== (undefined === primary)); const modelId = this.input.substring(this.curPos); this.require(Id64.isId64(modelId)); const { flags: treeFlags } = treeFlagsAndFormatVersionFromId(this.input); const options = TileOptions.fromTreeIdAndContentId(this.input, contentId); let parsedContentId; try { parsedContentId = ContentIdProvider.create(true, options).specFromId(contentId); } catch { this.reject("Invalid content Id"); } if (Object.keys(parsedContentId).some((key) => parsedContentId.hasOwnProperty(key) && typeof parsedContentId[key] === "number" && !Number.isFinite(parsedContentId[key]))) throw new Error("Invalid content Id"); const disablePolyfaceDecimation = options.disablePolyfaceDecimation; let treeId; if (classifier) { treeId = { ...classifier, animationId, disablePolyfaceDecimation }; } else { assert(undefined !== primary); const enforceDisplayPriority = (treeFlags & TreeFlags.EnforceDisplayPriority) !== 0 ? true : undefined; treeId = { ...primary, animationId, type: BatchType.Primary, enforceDisplayPriority, disablePolyfaceDecimation }; } return { contentId: parsedContentId, modelId, options, treeId, }; } cur() { this.require(this.curPos < this.input.length); return this.input[this.curPos]; } advance() { this.require(this.curPos < this.input.length); ++this.curPos; } eat(expectedChar) { this.require(this.cur() === expectedChar); this.advance(); } reject(message = "Invalid tree Id") { throw new Error(message); } require(condition, message = "Invalid tree Id") { if (!condition) this.reject(message); } parseClassifier() { this.eat("C"); let type = BatchType.VolumeClassifier; if (this.cur() === "P") { type = BatchType.PlanarClassifier; this.advance(); } this.eat(":"); // C: or CP: is always folowed by expansion then an underscore. let expansionStr = ""; while (this.curPos < this.input.length && (this.cur() >= "0" && this.cur() <= "9" || this.cur() === ".")) { expansionStr += this.cur(); this.advance(); } this.eat("_"); const expansion = Number.parseFloat(expansionStr); this.require(!Number.isNaN(expansion)); return { type, expansion }; } parseAnimation() { if (this.cur() !== "A") return undefined; this.eat("A"); this.eat(":"); const termPos = this.input.indexOf("_", this.curPos); this.require(termPos > this.curPos); const animationId = this.input.substring(this.curPos, termPos); this.require(Id64.isId64(animationId)); this.curPos = termPos + 1; // Skip "_" return animationId; } parsePrimary() { const edges = this.parseEdges(); const sectionCut = this.parseSectionCut(); return { edges, sectionCut }; } parseEdges() { if ("E" !== this.cur()) return { type: "non-indexed", smooth: false }; this.eat("E"); this.eat(":"); const flag = this.cur(); this.eat(flag); this.eat("_"); return "0" === flag ? false : edgeOptionsFromFlag(flag); } parseSectionCut() { if ("S" !== this.cur()) return undefined; this.eat("S"); const termPos = this.input.indexOf("s", this.curPos); this.require(termPos > this.curPos); const sectionCut = this.input.substring(this.curPos, termPos); this.curPos = termPos + 1; // Skip "_"; return sectionCut; } } /** @internal */ export function parseTileTreeIdAndContentId(treeId, contentId) { const parser = new Parser(treeId); return parser.parse(contentId); } /** @internal */ export const defaultTileOptions = Object.freeze({ maximumMajorTileFormatVersion: CurrentImdlVersion.Major, enableInstancing: true, enableImprovedElision: true, ignoreAreaPatterns: false, enableExternalTextures: true, useProjectExtents: true, expandProjectExtents: true, optimizeBRepProcessing: true, useLargerTiles: true, disableMagnification: false, alwaysSubdivideIncompleteTiles: false, disablePolyfaceDecimation: false, edgeOptions: { type: "compact", smooth: true, }, }); function contentFlagsFromId(id) { if (0 === id.length || "-" !== id[0]) throw new Error("Invalid content Id"); // V4: -flags-d-i-j-k-m - version in tree Id const end = id.indexOf("-", 1); if (-1 !== end) { const flags = Number.parseInt(id.substring(1, end), 16); if (!Number.isNaN(flags)) return flags; } throw new Error("Invalid content Id"); } function treeFlagsAndFormatVersionFromId(id) { if (0 === id.length) throw new Error("Invalid tree Id"); let parts = id.split("-"); if (parts.length > 0) { parts = parts[0].split("_"); if (parts.length === 2) { const version = Number.parseInt(parts[0], 16); const flags = Number.parseInt(parts[1], 16); if (!Number.isNaN(version) || !Number.isNaN(flags)) return { version, flags }; } } throw new Error("Invalid tree Id"); } function edgeOptionsFromTreeId(id) { const pos = id.indexOf("E:"); if (pos <= 0) return { type: "non-indexed", smooth: false }; return edgeOptionsFromFlag(id[pos + 2]); } function edgeOptionsFromFlag(flag) { if ("0" === flag) return defaultTileOptions.edgeOptions; const smooth = flag !== "2" && flag !== "5"; let type; switch (flag) { case "2": case "4": type = "indexed"; break; case "3": type = "non-indexed"; break; case "5": case "6": type = "compact"; break; default: throw new Error("Invalid tree Id"); } return { type, smooth }; } function edgeOptionsToString(options) { if (!options) return "E:0_"; switch (options.type) { case "non-indexed": return options.smooth ? "E:3_" : ""; case "indexed": return options.smooth ? "E:4_" : "E:2_"; case "compact": return options.smooth ? "E:6_" : "E:5_"; default: throw new Error("Invalid tree Id"); } } /** @internal */ export function getMaximumMajorTileFormatVersion(maxMajorVersion, formatVersion) { // The `formatVersion` input is from the backend, telling us precisely the maximum major+minor version it can produce. // Ensure we do not request tiles of a newer major version than backend can supply or it can read; and also limit major version // to that optionally configured by the app. let majorVersion = maxMajorVersion; if (undefined !== formatVersion) majorVersion = Math.min((formatVersion >>> 0x10), majorVersion); // Version number less than 1 is invalid - ignore majorVersion = Math.max(majorVersion, 1); // Version number greater than current known version ignored majorVersion = Math.min(majorVersion, CurrentImdlVersion.Major); // Version numbers are integers - round down return Math.max(Math.floor(majorVersion), 1); } /** Flags controlling the structure of a tile tree. The flags are part of the tile tree's Id. * @alpha */ export var TreeFlags; (function (TreeFlags) { TreeFlags[TreeFlags["None"] = 0] = "None"; TreeFlags[TreeFlags["UseProjectExtents"] = 1] = "UseProjectExtents"; TreeFlags[TreeFlags["EnforceDisplayPriority"] = 2] = "EnforceDisplayPriority"; TreeFlags[TreeFlags["OptimizeBRepProcessing"] = 4] = "OptimizeBRepProcessing"; TreeFlags[TreeFlags["UseLargerTiles"] = 8] = "UseLargerTiles"; TreeFlags[TreeFlags["ExpandProjectExtents"] = 16] = "ExpandProjectExtents"; TreeFlags[TreeFlags["DisablePolyfaceDecimation"] = 32] = "DisablePolyfaceDecimation"; })(TreeFlags || (TreeFlags = {})); function animationIdToString(animationId) { return `A:${animationId}_`; } /** Convert a tile tree Id to its string representation. * @internal */ export function iModelTileTreeIdToString(modelId, treeId, options) { let idStr = ""; let flags = options.useProjectExtents ? TreeFlags.UseProjectExtents : TreeFlags.None; if (options.optimizeBRepProcessing) flags |= TreeFlags.OptimizeBRepProcessing; if (options.disablePolyfaceDecimation) flags |= TreeFlags.DisablePolyfaceDecimation; if (options.useLargerTiles) flags |= TreeFlags.UseLargerTiles; if (options.expandProjectExtents) flags |= TreeFlags.ExpandProjectExtents; if (BatchType.Primary === treeId.type) { if (undefined !== treeId.animationId) idStr = `${idStr}${animationIdToString(treeId.animationId)}`; else if (treeId.enforceDisplayPriority) // animation and priority are currently mutually exclusive flags |= TreeFlags.EnforceDisplayPriority; const edges = edgeOptionsToString(treeId.edges); const sectionCut = treeId.sectionCut ? `S${treeId.sectionCut}s` : ""; idStr = `${idStr}${edges}${sectionCut}`; } else { const typeStr = BatchType.PlanarClassifier === treeId.type ? "CP" : "C"; idStr = `${idStr + typeStr}:${treeId.expansion.toFixed(6)}_`; if (BatchType.VolumeClassifier === treeId.type) { // Volume classifiers always use the exact project extents. flags |= TreeFlags.UseProjectExtents; flags &= ~TreeFlags.ExpandProjectExtents; } if (undefined !== treeId.animationId) idStr = `${idStr}${animationIdToString(treeId.animationId)}`; } const version = getMaximumMajorTileFormatVersion(options.maximumMajorTileFormatVersion); if (version >= 4) { const prefix = `${version.toString(16)}_${flags.toString(16)}-`; idStr = prefix + idStr; } return idStr + modelId; } /** Ordinal comparison of two tile tree Ids, e.g., for use in sorted containers. * @internal */ export function compareIModelTileTreeIds(lhs, rhs) { let cmp = compareNumbers(lhs.type, rhs.type); if (0 === cmp) cmp = compareStringsOrUndefined(lhs.animationId, rhs.animationId); if (0 !== cmp) return cmp; // NB: The redundant checks on BatchType below are to satisfy compiler. assert(lhs.type === rhs.type); if (BatchType.Primary === lhs.type && BatchType.Primary === rhs.type) { cmp = compareEdgeOptions(lhs.edges, rhs.edges); if (0 === cmp) { cmp = compareBooleansOrUndefined(lhs.enforceDisplayPriority, rhs.enforceDisplayPriority); if (0 === cmp) cmp = compareStringsOrUndefined(lhs.sectionCut, rhs.sectionCut); } } else if (BatchType.Primary !== lhs.type && BatchType.Primary !== rhs.type) { cmp = compareNumbers(lhs.expansion, rhs.expansion); } return cmp; } /** Flags controlling how tile content is produced. The flags are part of the ContentId. * @alpha */ export var ContentFlags; (function (ContentFlags) { ContentFlags[ContentFlags["None"] = 0] = "None"; ContentFlags[ContentFlags["AllowInstancing"] = 1] = "AllowInstancing"; ContentFlags[ContentFlags["ImprovedElision"] = 2] = "ImprovedElision"; ContentFlags[ContentFlags["IgnoreAreaPatterns"] = 4] = "IgnoreAreaPatterns"; ContentFlags[ContentFlags["ExternalTextures"] = 8] = "ExternalTextures"; })(ContentFlags || (ContentFlags = {})); /** Contains logic for working with tile content Ids according to a specific content Id scheme. Which scheme is used depends on * the major version of the tile format. * @internal */ export class ContentIdProvider { majorFormatVersion; contentFlags; constructor(formatVersion, contentFlags) { this.majorFormatVersion = formatVersion; this.contentFlags = contentFlags; } get rootContentId() { return this.computeId(0, 0, 0, 0, 1); } idFromParentAndMultiplier(parentId, multiplier) { const lastSepPos = parentId.lastIndexOf(this._separator); assert(-1 !== lastSepPos); return parentId.substring(0, lastSepPos + 1) + multiplier.toString(16); } specFromId(id) { const parts = id.split(this._separator); const len = parts.length; assert(len >= 5); return { depth: parseInt(parts[len - 5], 16), i: parseInt(parts[len - 4], 16), j: parseInt(parts[len - 3], 16), k: parseInt(parts[len - 2], 16), multiplier: parseInt(parts[len - 1], 16), }; } idFromSpec(spec) { return this.computeId(spec.depth, spec.i, spec.j, spec.k, spec.multiplier); } join(depth, i, j, k, mult) { const sep = this._separator; return depth.toString(16) + sep + i.toString(16) + sep + j.toString(16) + sep + k.toString(16) + sep + mult.toString(16); } /** formatVersion is the maximum major version supported by the back-end supplying the tile tree. * Must ensure front-end does not request tiles of a format the back-end cannot supply, and back-end does * not supply tiles of a format the front-end doesn't recognize. */ static create(allowInstancing, options, formatVersion) { const majorVersion = getMaximumMajorTileFormatVersion(options.maximumMajorTileFormatVersion, formatVersion); assert(majorVersion > 0); assert(Math.floor(majorVersion) === majorVersion); switch (majorVersion) { case 0: case 1: return new ContentIdV1Provider(majorVersion); case 2: case 3: return new ContentIdV2Provider(majorVersion, allowInstancing, options); default: return new ContentIdV4Provider(allowInstancing, options, majorVersion); } } } /** The original (major version 1) tile format used a content Id scheme of the format * `depth/i/j/k/multiplier`. * @internal */ class ContentIdV1Provider extends ContentIdProvider { constructor(majorVersion) { super(majorVersion, ContentFlags.None); } get _separator() { return "/"; } computeId(depth, i, j, k, mult) { return this.join(depth, i, j, k, mult); } } /** Tile formats 2 and 3 use a content Id scheme encoding styling flags and the major format version * into the content Id, of the format `_majorVersion_flags_depth_i_j_k_multiplier`. * @internal */ class ContentIdV2Provider extends ContentIdProvider { _prefix; constructor(majorVersion, allowInstancing, options) { const flags = (allowInstancing && options.enableInstancing) ? ContentFlags.AllowInstancing : ContentFlags.None; super(majorVersion, flags); this._prefix = this._separator + majorVersion.toString(16) + this._separator + flags.toString(16) + this._separator; } get _separator() { return "_"; } computeId(depth, i, j, k, mult) { return this._prefix + this.join(depth, i, j, k, mult); } } /** Tile formats 4+ encode styling flags but not major format version. (The version is specified by the tile tree's Id). * Format: `-flags-depth-i-j-k-multiplier`. * @internal */ class ContentIdV4Provider extends ContentIdProvider { _prefix; constructor(allowInstancing, options, majorVersion) { let flags = (allowInstancing && options.enableInstancing) ? ContentFlags.AllowInstancing : ContentFlags.None; if (options.enableImprovedElision) flags = flags | ContentFlags.ImprovedElision; if (options.ignoreAreaPatterns) flags = flags | ContentFlags.IgnoreAreaPatterns; if (options.enableExternalTextures) flags = flags | ContentFlags.ExternalTextures; super(majorVersion, flags); this._prefix = this._separator + flags.toString(16) + this._separator; } get _separator() { return "-"; } computeId(depth, i, j, k, mult) { return this._prefix + this.join(depth, i, j, k, mult); } } /** @internal */ export function bisectTileRange3d(range, takeUpper) { const diag = range.diagonal(); const pt = takeUpper ? range.high : range.low; if (diag.x > diag.y && diag.x > diag.z) pt.x = (range.low.x + range.high.x) / 2.0; else if (diag.y > diag.z) pt.y = (range.low.y + range.high.y) / 2.0; else pt.z = (range.low.z + range.high.z) / 2.0; } /** @internal */ export function bisectTileRange2d(range, takeUpper) { const diag = range.diagonal(); const pt = takeUpper ? range.high : range.low; if (diag.x > diag.y) pt.x = (range.low.x + range.high.x) / 2.0; else pt.y = (range.low.y + range.high.y) / 2.0; } /** Given a description of a tile, compute the ranges which would result from sub-dividing its range into 4 or 8 sub-volumes. * @internal */ export function computeChildTileRanges(tile, root) { const emptyMask = tile.emptySubRangeMask; const is2d = root.is2d; const bisectRange = is2d ? bisectTileRange2d : bisectTileRange3d; const ranges = []; for (let i = 0; i < 2; i++) { for (let j = 0; j < 2; j++) { for (let k = 0; k < (is2d ? 1 : 2); k++) { const emptyBit = 1 << (i + j * 2 + k * 4); const isEmpty = 0 !== (emptyMask & emptyBit); const range = tile.range.clone(); bisectRange(range, 0 === i); bisectRange(range, 0 === j); if (!is2d) bisectRange(range, 0 === k); ranges.push({ range, isEmpty }); } } } return ranges; } /** Given a description of the parent tile, obtain the properties of its child tiles, and the number of empty children. * @internal */ export function computeChildTileProps(parent, idProvider, root) { let numEmpty = 0; const children = []; // Leaf nodes have no children if (parent.isLeaf) return { children, numEmpty }; // One child, same volume as parent, but higher-resolution. if (undefined !== parent.sizeMultiplier) { const sizeMultiplier = parent.sizeMultiplier * 2; const contentId = idProvider.idFromParentAndMultiplier(parent.contentId, sizeMultiplier); children.push({ contentId, range: parent.range, contentRange: parent.contentRange, sizeMultiplier, isLeaf: false, maximumSize: root.tileScreenSize, }); return { children, numEmpty }; } // Sub-divide parent's range into 4 (for 2d trees) or 8 (for 3d trees) child tiles. const parentSpec = idProvider.specFromId(parent.contentId); const childSpec = { ...parentSpec }; childSpec.depth = parentSpec.depth + 1; // This mask is a bitfield in which an 'on' bit indicates sub-volume containing no geometry. // Don't bother creating children or requesting content for such empty volumes. const emptyMask = parent.emptySubRangeMask; // Spatial tree range == project extents; content range == model range. // Trivially reject children whose ranges are entirely outside model range. let treeContentRange = root.contentRange; if (undefined !== treeContentRange && treeContentRange.containsRange(parent.range)) { // Parent is wholly within model range - don't bother testing child ranges against it. treeContentRange = undefined; } const is2d = root.is2d; const bisectRange = is2d ? bisectTileRange2d : bisectTileRange3d; for (let i = 0; i < 2; i++) { for (let j = 0; j < 2; j++) { for (let k = 0; k < (is2d ? 1 : 2); k++) { const emptyBit = 1 << (i + j * 2 + k * 4); if (0 !== (emptyMask & emptyBit)) { // volume is known to contain no geometry. ++numEmpty; continue; } const range = parent.range.clone(); bisectRange(range, 0 === i); bisectRange(range, 0 === j); if (!is2d) bisectRange(range, 0 === k); if (undefined !== treeContentRange && !range.intersectsRange(treeContentRange)) { // volume is within project extents but entirely outside model range ++numEmpty; continue; } childSpec.i = parentSpec.i * 2 + i; childSpec.j = parentSpec.j * 2 + j; childSpec.k = parentSpec.k * 2 + k; const childId = idProvider.idFromSpec(childSpec); children.push({ contentId: childId, range, maximumSize: root.tileScreenSize }); } } } return { children, numEmpty }; } /** Deserializes tile content metadata. * @throws [[TileReadError]] * @internal * @deprecated in 4.0 - will not be removed until after 2026-06-13. Use decodeTileContentDescription. I think tile agents (or their tests) are using this function. */ export function readTileContentDescription(stream, sizeMultiplier, is2d, options, isVolumeClassifier) { return decodeTileContentDescription({ stream, sizeMultiplier, is2d, options, isVolumeClassifier }); } /** @internal */ export function decodeTileContentDescription(args) { const { stream, options } = args; const isVolumeClassifier = args.isVolumeClassifier ?? false; stream.reset(); const header = new ImdlHeader(stream); if (!header.isValid) throw new TileReadError(TileReadStatus.InvalidHeader); else if (!header.isReadableVersion) throw new TileReadError(TileReadStatus.NewerMajorVersion); // Skip the feature table. const featureTableStartPos = stream.curPos; const ftHeader = FeatureTableHeader.readFrom(stream); if (undefined === ftHeader) throw new TileReadError(TileReadStatus.InvalidFeatureTable); stream.curPos = featureTableStartPos + ftHeader.length; let sizeMultiplier = args.sizeMultiplier; let isLeaf = args.isLeaf; if (undefined === isLeaf) { // Determine subdivision based on header data. const completeTile = 0 === (header.flags & ImdlFlags.Incomplete); const emptyTile = completeTile && 0 === header.numElementsIncluded && 0 === header.numElementsExcluded; isLeaf = (emptyTile || isVolumeClassifier); // Current classifier algorithm supports only a single tile. if (!isLeaf) { // Non-spatial (2d) models are of arbitrary scale and contain geometry like line work and especially text which // can be adversely affected by quantization issues when zooming in closely. const maxLeafTolerance = 1.0; // Must sub-divide if tile explicitly specifies... let canSkipSubdivision = 0 === (header.flags & ImdlFlags.DisallowMagnification); // ...or in 2d, or if app explicitly disabled magnification, or tolerance large enough to risk quantization error... canSkipSubdivision = canSkipSubdivision && !args.is2d && !options.disableMagnification && header.tolerance <= maxLeafTolerance; // ...or app specifies incomplete tiles must always be sub-divided. canSkipSubdivision = canSkipSubdivision && (completeTile || !options.alwaysSubdivideIncompleteTiles); if (canSkipSubdivision) { const minElementsPerTile = 100; if (completeTile && 0 === header.numElementsExcluded && header.numElementsIncluded <= minElementsPerTile) { const containsCurves = 0 !== (header.flags & ImdlFlags.ContainsCurves); if (!containsCurves) isLeaf = true; else if (undefined === sizeMultiplier) sizeMultiplier = 1.0; } else if (undefined === sizeMultiplier && header.numElementsIncluded + header.numElementsExcluded <= minElementsPerTile) { sizeMultiplier = 1.0; } } } } return { featureTableStartPos, contentRange: header.contentRange, isLeaf, sizeMultiplier, emptySubRangeMask: header.emptySubRanges, }; } const scratchRangeDiagonal = new Vector3d(); /** Compute the chord tolerance for the specified tile of the given range with the specified size multiplier. * @internal */ export function computeTileChordTolerance(tile, is3d, tileScreenSize) { if (tile.range.isNull) return 0; const diagonal = tile.range.diagonal(scratchRangeDiagonal); const diagDist = is3d ? diagonal.magnitude() : diagonal.magnitudeXY(); const mult = Math.max(tile.sizeMultiplier ?? 1, 1); return diagDist / (tileScreenSize * Constants.minToleranceRatioMultiplier * Math.max(1, mult)); } /** Deserializes tile metadata. * @internal */ export class TileMetadataReader { _is2d; _isVolumeClassifier; _options; constructor(type, is2d, options) { this._is2d = is2d; this._isVolumeClassifier = BatchType.VolumeClassifier === type; this._options = options; } /** Produce metadata from the specified tile content. * @throws [[TileReadError]] */ read(stream, props) { const content = decodeTileContentDescription({ stream, sizeMultiplier: props.sizeMultiplier, is2d: this._is2d, options: this._options, isVolumeClassifier: this._isVolumeClassifier, }); return { contentRange: content.contentRange, isLeaf: content.isLeaf, sizeMultiplier: content.sizeMultiplier, emptySubRangeMask: content.emptySubRangeMask, range: Range3d.fromJSON(props.range), contentId: props.contentId, }; } } //# sourceMappingURL=TileMetadata.js.map