UNPKG

@itwin/core-frontend

Version:
827 lines • 43.5 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 Utils */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RealityTreeReference = exports.RealityModelTileTree = exports.RealityModelTileTreeProps = exports.RealityModelTileUtils = exports.RealityTileRegion = void 0; exports.createRealityTileTreeReference = createRealityTileTreeReference; const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const core_geometry_1 = require("@itwin/core-geometry"); const BackgroundMapGeometry_1 = require("../../BackgroundMapGeometry"); const DisplayStyleState_1 = require("../../DisplayStyleState"); const IModelApp_1 = require("../../IModelApp"); const PlanarClipMaskState_1 = require("../../PlanarClipMaskState"); const RealityDataSource_1 = require("../../RealityDataSource"); const internal_1 = require("../../tile/internal"); const RealityDataSourceTilesetUrlImpl_1 = require("../../RealityDataSourceTilesetUrlImpl"); function getUrl(content) { return content ? (content.url ? content.url : content.uri) : undefined; } var RealityTreeId; (function (RealityTreeId) { function compareOrigins(lhs, rhs) { return (0, core_bentley_1.compareNumbers)(lhs.x, rhs.x) || (0, core_bentley_1.compareNumbers)(lhs.y, rhs.y) || (0, core_bentley_1.compareNumbers)(lhs.z, rhs.z); } function compareMatrices(lhs, rhs) { for (let i = 0; i < 9; i++) { const cmp = (0, core_bentley_1.compareNumbers)(lhs.coffs[i], rhs.coffs[i]); if (0 !== cmp) return cmp; } return 0; } function compareTransforms(lhs, rhs) { return compareOrigins(lhs.origin, rhs.origin) || compareMatrices(lhs.matrix, rhs.matrix); } function compareRealityDataSourceKeys(lhs, rhs) { return (0, core_bentley_1.compareStringsOrUndefined)(lhs.id, rhs.id) || (0, core_bentley_1.compareStringsOrUndefined)(lhs.format, rhs.format) || (0, core_bentley_1.compareStringsOrUndefined)(lhs.iTwinId, rhs.iTwinId); } function compareWithoutModelId(lhs, rhs) { return (compareRealityDataSourceKeys(lhs.rdSourceKey, rhs.rdSourceKey) || (0, core_bentley_1.compareBooleans)(lhs.deduplicateVertices, rhs.deduplicateVertices) || (0, core_bentley_1.compareStringsOrUndefined)(lhs.produceGeometry, rhs.produceGeometry) || (0, core_bentley_1.compareStringsOrUndefined)(lhs.maskModelIds, rhs.maskModelIds) || (0, core_bentley_1.comparePossiblyUndefined)((ltf, rtf) => compareTransforms(ltf, rtf), lhs.transform, rhs.transform)); } RealityTreeId.compareWithoutModelId = compareWithoutModelId; function compare(lhs, rhs) { return (0, core_bentley_1.compareStrings)(lhs.modelId, rhs.modelId) || compareWithoutModelId(lhs, rhs); } RealityTreeId.compare = compare; })(RealityTreeId || (RealityTreeId = {})); class RealityTreeSupplier { isEcefDependent = true; getOwner(treeId, iModel) { return iModel.tiles.getTileTreeOwner(treeId, this); } async createTileTree(treeId, iModel) { if (treeId.maskModelIds) await iModel.models.load(core_bentley_1.CompressedId64Set.decompressSet(treeId.maskModelIds)); const opts = { deduplicateVertices: treeId.deduplicateVertices, produceGeometry: treeId.produceGeometry }; return RealityModelTileTree.createRealityModelTileTree(treeId.rdSourceKey, iModel, treeId.modelId, treeId.transform, opts); } compareTileTreeIds(lhs, rhs) { return RealityTreeId.compare(lhs, rhs); } findCompatibleContextRealityModelId(id, style) { const owners = style.iModel.tiles.getTreeOwnersForSupplier(this); for (const owner of owners) { // Find an existing tree with the same Id, ignoring its model Id. if (0 === RealityTreeId.compareWithoutModelId(id, owner.id)) { const modelId = owner.id.modelId; (0, core_bentley_1.assert)(undefined !== modelId); // If the model Id is unused by any other context reality model in the view and does not identify a persistent reality model, use it. if (core_bentley_1.Id64.isTransientId64(modelId) && !style.contextRealityModelStates.some((model) => model.modelId === modelId)) return modelId; } } return undefined; } } const realityTreeSupplier = new RealityTreeSupplier(); function createRealityTileTreeReference(props) { return new RealityTreeReference(props); } const zeroPoint = core_geometry_1.Point3d.createZero(); const earthEllipsoid = core_geometry_1.Ellipsoid.createCenterMatrixRadii(zeroPoint, undefined, core_geometry_1.Constant.earthRadiusWGS84.equator, core_geometry_1.Constant.earthRadiusWGS84.equator, core_geometry_1.Constant.earthRadiusWGS84.polar); const scratchRay = core_geometry_1.Ray3d.createXAxis(); class RealityTileRegion { constructor(values) { this.minLongitude = values.minLongitude; this.minLatitude = values.minLatitude; this.minHeight = values.minHeight; this.maxLongitude = values.maxLongitude; this.maxLatitude = values.maxLatitude; this.maxHeight = values.maxHeight; } minLongitude; minLatitude; minHeight; maxLongitude; maxLatitude; maxHeight; static create(region) { const minHeight = region[4]; const maxHeight = region[5]; const minLongitude = region[0]; const maxLongitude = region[2]; const minLatitude = core_common_1.Cartographic.parametricLatitudeFromGeodeticLatitude(region[1]); const maxLatitude = core_common_1.Cartographic.parametricLatitudeFromGeodeticLatitude(region[3]); return new RealityTileRegion({ minLongitude, minLatitude, minHeight, maxLongitude, maxLatitude, maxHeight }); } static isGlobal(boundingVolume) { return Array.isArray(boundingVolume?.region) && (boundingVolume.region[2] - boundingVolume.region[0]) > core_geometry_1.Angle.piRadians && (boundingVolume.region[3] - boundingVolume.region[1]) > core_geometry_1.Angle.piOver2Radians; } getRange() { const maxAngle = Math.max(Math.abs(this.maxLatitude - this.minLatitude), Math.abs(this.maxLongitude - this.minLongitude)); let corners; let range; if (maxAngle < Math.PI / 8) { corners = new Array(8); const chordTolerance = (1 - Math.cos(maxAngle / 2)) * core_geometry_1.Constant.earthRadiusWGS84.polar; const addEllipsoidCorner = ((long, lat, index) => { const ray = (0, core_bentley_1.expectDefined)(earthEllipsoid.radiansToUnitNormalRay(long, lat, scratchRay)); corners[index] = ray.fractionToPoint(this.minHeight - chordTolerance); corners[index + 4] = ray.fractionToPoint(this.maxHeight + chordTolerance); }); addEllipsoidCorner(this.minLongitude, this.minLatitude, 0); addEllipsoidCorner(this.minLongitude, this.maxLatitude, 1); addEllipsoidCorner(this.maxLongitude, this.minLatitude, 2); addEllipsoidCorner(this.maxLongitude, this.maxLatitude, 3); range = core_geometry_1.Range3d.createArray(corners); } else { const minEq = core_geometry_1.Constant.earthRadiusWGS84.equator + this.minHeight, maxEq = core_geometry_1.Constant.earthRadiusWGS84.equator + this.maxHeight; const minEllipsoid = core_geometry_1.Ellipsoid.createCenterMatrixRadii(zeroPoint, undefined, minEq, minEq, core_geometry_1.Constant.earthRadiusWGS84.polar + this.minHeight); const maxEllipsoid = core_geometry_1.Ellipsoid.createCenterMatrixRadii(zeroPoint, undefined, maxEq, maxEq, core_geometry_1.Constant.earthRadiusWGS84.polar + this.maxHeight); range = minEllipsoid.patchRangeStartEndRadians(this.minLongitude, this.maxLongitude, this.minLatitude, this.maxLatitude); range.extendRange(maxEllipsoid.patchRangeStartEndRadians(this.minLongitude, this.maxLongitude, this.minLatitude, this.maxLatitude)); } return { range, corners }; } } exports.RealityTileRegion = RealityTileRegion; /** @internal */ class RealityModelTileUtils { static rangeFromBoundingVolume(boundingVolume) { if (undefined === boundingVolume) return undefined; let corners; let range; if (undefined !== boundingVolume.box) { const box = boundingVolume.box; const center = core_geometry_1.Point3d.create(box[0], box[1], box[2]); const ux = core_geometry_1.Vector3d.create(box[3], box[4], box[5]); const uy = core_geometry_1.Vector3d.create(box[6], box[7], box[8]); const uz = core_geometry_1.Vector3d.create(box[9], box[10], box[11]); corners = new Array(); for (let j = 0; j < 2; j++) { for (let k = 0; k < 2; k++) { for (let l = 0; l < 2; l++) { corners.push(center.plus3Scaled(ux, (j ? -1.0 : 1.0), uy, (k ? -1.0 : 1.0), uz, (l ? -1.0 : 1.0))); } } } range = core_geometry_1.Range3d.createArray(corners); } else if (Array.isArray(boundingVolume.sphere)) { const sphere = boundingVolume.sphere; const center = core_geometry_1.Point3d.create(sphere[0], sphere[1], sphere[2]); const radius = sphere[3]; range = core_geometry_1.Range3d.createXYZXYZ(center.x - radius, center.y - radius, center.z - radius, center.x + radius, center.y + radius, center.z + radius); } else if (Array.isArray(boundingVolume.region)) { const region = RealityTileRegion.create(boundingVolume.region); const regionRange = region.getRange(); return { range: regionRange.range, corners: regionRange.corners, region }; } return range ? { range, corners } : undefined; } static maximumSizeFromGeometricTolerance(range, geometricError) { const minToleranceRatio = true === IModelApp_1.IModelApp.renderSystem.isMobile ? IModelApp_1.IModelApp.tileAdmin.mobileRealityTileMinToleranceRatio : 1.0; // Nominally the error on screen size of a tile. Increasing generally increases performance (fewer draw calls) at expense of higher load times. // NB: We increase the above minToleranceRatio on mobile devices in order to help avoid pruning too often based on the memory threshold for // pruning currently used by reality tile trees on mobile. return minToleranceRatio * range.diagonal().magnitude() / geometricError; } static transformFromJson(jTrans) { return (jTrans === undefined) ? core_geometry_1.Transform.createIdentity() : core_geometry_1.Transform.createOriginAndMatrix(core_geometry_1.Point3d.create(jTrans[12], jTrans[13], jTrans[14]), core_geometry_1.Matrix3d.createRowValues(jTrans[0], jTrans[4], jTrans[8], jTrans[1], jTrans[5], jTrans[9], jTrans[2], jTrans[6], jTrans[10])); } } exports.RealityModelTileUtils = RealityModelTileUtils; var SMTextureType; (function (SMTextureType) { SMTextureType[SMTextureType["None"] = 0] = "None"; SMTextureType[SMTextureType["Embedded"] = 1] = "Embedded"; SMTextureType[SMTextureType["Streaming"] = 2] = "Streaming"; })(SMTextureType || (SMTextureType = {})); /** Exported strictly for tests. */ class RealityModelTileTreeProps { tilesetToEcef; location; tilesetJson; doDrapeBackgroundMap = false; dataSource; yAxisUp = false; root; maximumScreenSpaceError; get usesGeometricError() { return undefined !== this.maximumScreenSpaceError; } constructor(json, root, rdSource, tilesetToDbTransform, tilesetToEcef) { this.tilesetToEcef = tilesetToEcef; this.tilesetJson = root; this.dataSource = rdSource; this.location = tilesetToDbTransform; this.doDrapeBackgroundMap = (json.root && json.root.SMMasterHeader && SMTextureType.Streaming === json.root.SMMasterHeader.IsTextured); if (json.asset.gltfUpAxis === undefined || json.asset.gltfUpAxis === "y" || json.asset.gltfUpAxis === "Y") { this.yAxisUp = true; } const maxSSE = json.asset.extras?.maximumScreenSpaceError; if (typeof maxSSE === "number") { this.maximumScreenSpaceError = json.asset.extras?.maximumScreenSpaceError; } else if (rdSource.usesGeometricError) { this.maximumScreenSpaceError = rdSource.maximumScreenSpaceError ?? 16; } } } exports.RealityModelTileTreeProps = RealityModelTileTreeProps; class RealityModelTileTreeParams { gcsConverterAvailable; rootToEcef; id; modelId; iModel; is3d = true; loader; rootTile; baseUrl; reprojectGeometry; get location() { return this.loader.tree.location; } get yAxisUp() { return this.loader.tree.yAxisUp; } get priority() { return this.loader.priority; } constructor(tileTreeId, iModel, modelId, loader, gcsConverterAvailable, rootToEcef, baseUrl, reprojectGeometry) { this.gcsConverterAvailable = gcsConverterAvailable; this.rootToEcef = rootToEcef; this.loader = loader; this.id = tileTreeId; this.modelId = modelId; this.iModel = iModel; const refine = loader.tree.tilesetJson.refine; this.rootTile = new RealityModelTileProps({ json: loader.tree.tilesetJson, id: "", // If not specified explicitly, additiveRefinement is inherited from parent tile. additiveRefinement: undefined !== refine ? "ADD" === refine : undefined, usesGeometricError: loader.tree.usesGeometricError, }); this.baseUrl = baseUrl; this.reprojectGeometry = reprojectGeometry; } } class RealityModelTileProps { contentId; range; contentRange; maximumSize; isLeaf; transformToRoot; additiveRefinement; parent; noContentButTerminateOnSelection; rangeCorners; region; geometricError; constructor(args) { this.contentId = args.id; this.parent = args.parent; this.transformToRoot = args.transformToRoot; this.additiveRefinement = args.additiveRefinement; const json = args.json; const boundingVolume = RealityModelTileUtils.rangeFromBoundingVolume(json.boundingVolume); if (boundingVolume) { this.range = boundingVolume.range; this.rangeCorners = boundingVolume.corners; this.region = boundingVolume?.region; } else { this.range = core_geometry_1.Range3d.createNull(); (0, core_bentley_1.assert)(false, "Unbounded tile"); } this.isLeaf = !Array.isArray(json.children) || 0 === json.children.length; const hasContents = undefined !== getUrl(json.content); if (hasContents) this.contentRange = RealityModelTileUtils.rangeFromBoundingVolume(json.content.boundingVolume)?.range; else { // A node without content should probably be selectable even if not additive refinement - But restrict it to that case here // to avoid potential problems with existing reality models, but still avoid overselection in the OSM world building set. if (this.additiveRefinement || args.parent?.additiveRefinement) this.noContentButTerminateOnSelection = true; } this.maximumSize = (this.noContentButTerminateOnSelection || hasContents) ? RealityModelTileUtils.maximumSizeFromGeometricTolerance(core_geometry_1.Range3d.fromJSON(this.range), json.geometricError) : 0; if (args.usesGeometricError) this.geometricError = json.geometricError; } } class FindChildResult { id; json; transformToRoot; constructor(id, json, transformToRoot) { this.id = id; this.json = json; this.transformToRoot = transformToRoot; } } function assembleUrl(prefix, url) { if (url.startsWith("/")) { // Relative to base origin, not to parent tile return url.substring(1); } if (url.startsWith("./")) { // Relative to parent tile url = url.substring(2); } else { const prefixParts = prefix.split("/"); prefixParts.pop(); while (url.startsWith("../")) { prefixParts.pop(); url = url.substring(3); } prefixParts.push(""); prefix = prefixParts.join("/"); } return prefix + url; } function addUrlPrefix(subTree, prefix) { if (undefined === subTree) return; if (undefined !== subTree.content) { if (undefined !== subTree.content.url) subTree.content.url = assembleUrl(prefix, subTree.content.url); else if (undefined !== subTree.content.uri) subTree.content.uri = assembleUrl(prefix, subTree.content.uri); } if (undefined !== subTree.children) for (const child of subTree.children) addUrlPrefix(child, prefix); } async function expandSubTree(root, rdsource) { const childUrl = getUrl(root.content); if (undefined === childUrl || "tileset" !== rdsource.getTileContentType(childUrl)) return root; const subTree = await rdsource.getTileJson(childUrl); const prefixIndex = childUrl.lastIndexOf("/"); if (prefixIndex > 0) addUrlPrefix(subTree.root, childUrl.substring(0, prefixIndex + 1)); return subTree.root; } class RealityModelTileLoader extends internal_1.RealityTileLoader { tree; _batchedIdMap; _viewFlagOverrides; _deduplicateVertices; constructor(tree, batchedIdMap, opts) { super(opts?.produceGeometry); this.tree = tree; this._batchedIdMap = batchedIdMap; this._deduplicateVertices = opts?.deduplicateVertices ?? false; let clipVolume; if (RealityTileRegion.isGlobal(tree.tilesetJson.boundingVolume)) clipVolume = false; this._viewFlagOverrides = (0, internal_1.createDefaultViewFlagOverrides)({ lighting: true, clipVolume }); // Display edges if they are present (Cesium outline extension) and enabled for view. this._viewFlagOverrides.visibleEdges = undefined; this._viewFlagOverrides.hiddenEdges = undefined; // Allow wiremesh display. this._viewFlagOverrides.wiremesh = undefined; } get doDrapeBackgroundMap() { return this.tree.doDrapeBackgroundMap; } get wantDeduplicatedVertices() { return this._deduplicateVertices; } get maxDepth() { return Number.MAX_SAFE_INTEGER; } get minDepth() { return 0; } get priority() { return internal_1.TileLoadPriority.Context; } getBatchIdMap() { return this._batchedIdMap; } get clipLowResolutionTiles() { return true; } get viewFlagOverrides() { return this._viewFlagOverrides; } get maximumScreenSpaceError() { return this.tree.maximumScreenSpaceError; } async loadChildren(tile) { const props = await this.getChildrenProps(tile); if (undefined === props) return undefined; const children = []; for (const prop of props) children.push(tile.realityRoot.createTile(prop)); return children; } async getChildrenProps(parent) { const props = []; const thisId = parent.contentId; const prefix = thisId.length ? `${thisId}_` : ""; const findResult = await this.findTileInJson(this.tree.tilesetJson, thisId, "", undefined); if (undefined !== findResult && Array.isArray(findResult.json.children)) { for (let i = 0; i < findResult.json.children.length; i++) { const childId = prefix + i; const foundChild = await this.findTileInJson(this.tree.tilesetJson, childId, "", undefined); if (undefined !== foundChild) { const refine = foundChild.json.refine; props.push(new RealityModelTileProps({ json: foundChild.json, parent, id: foundChild.id, transformToRoot: foundChild.transformToRoot, // If not specified explicitly, additiveRefinement is inherited from parent tile. additiveRefinement: undefined !== refine ? refine === "ADD" : undefined, usesGeometricError: this.tree.usesGeometricError, })); } } } return props; } getRequestChannel(_tile) { // ###TODO: May want to extract the hostname from the URL. return IModelApp_1.IModelApp.tileAdmin.channels.getForHttp("itwinjs-reality-model"); } async requestTileContent(tile, isCanceled) { const foundChild = await this.findTileInJson(this.tree.tilesetJson, tile.contentId, ""); if (undefined === foundChild || undefined === foundChild.json.content || isCanceled()) return undefined; return this.tree.dataSource.getTileContent(getUrl(foundChild.json.content)); } async findTileInJson(tilesetJson, id, parentId, transformToRoot) { if (id.length === 0) return new FindChildResult(id, tilesetJson, transformToRoot); // Root. const separatorIndex = id.indexOf("_"); const childId = (separatorIndex < 0) ? id : id.substring(0, separatorIndex); const childIndex = parseInt(childId, 10); if (isNaN(childIndex) || tilesetJson === undefined || tilesetJson.children === undefined || childIndex >= tilesetJson.children.length) { (0, core_bentley_1.assert)(false, "scalable mesh child not found."); return undefined; } const foundChild = tilesetJson.children[childIndex]; const thisParentId = parentId.length ? (`${parentId}_${childId}`) : childId; if (foundChild.transform) { const thisTransform = RealityModelTileUtils.transformFromJson(foundChild.transform); // Accumulate tile's transform to apply it to this tile's children transformToRoot = transformToRoot ? transformToRoot.multiplyTransformTransform(thisTransform) : thisTransform; } if (separatorIndex >= 0) { return this.findTileInJson(foundChild, id.substring(separatorIndex + 1), thisParentId, transformToRoot); } tilesetJson.children[childIndex] = await expandSubTree(foundChild, this.tree.dataSource); return new FindChildResult(thisParentId, tilesetJson.children[childIndex], transformToRoot); } } /** @internal */ class RealityModelTileTree extends internal_1.RealityTileTree { _isContentUnbounded; _layerHandler; layerImageryTrees = []; get layerHandler() { return this._layerHandler; } constructor(params) { super(params); this._layerHandler = new internal_1.LayerTileTreeHandler(this); this._isContentUnbounded = this.rootTile.contentRange.diagonal().magnitude() > 2 * core_geometry_1.Constant.earthRadiusWGS84.equator; } get isContentUnbounded() { return this._isContentUnbounded; } collectClassifierGraphics(args, selectedTiles) { super.collectClassifierGraphics(args, selectedTiles); this._layerHandler.collectClassifierGraphics(args, selectedTiles); } } exports.RealityModelTileTree = RealityModelTileTree; (function (RealityModelTileTree) { class Reference extends internal_1.TileTreeReference { _name; _transform; _isGlobal; _source; _planarClipMask; _classifier; _mapDrapeTree; _getDisplaySettings; _layerRefHandler; iModel; get planarClipMask() { return this._planarClipMask; } set planarClipMask(planarClipMask) { this._planarClipMask = planarClipMask; } get planarClipMaskPriority() { if (this._planarClipMask?.settings.priority !== undefined) return this._planarClipMask.settings.priority; return this.isGlobal ? core_common_1.PlanarClipMaskPriority.GlobalRealityModel : core_common_1.PlanarClipMaskPriority.RealityModel; } get maskModelIds() { return this._planarClipMask?.settings.compressedModelIds; } shouldDrapeLayer(layerTreeRef) { const mapLayerSettings = layerTreeRef?.layerSettings; if (mapLayerSettings && mapLayerSettings instanceof core_common_1.ModelMapLayerSettings) return core_common_1.ModelMapLayerDrapeTarget.RealityData === mapLayerSettings.drapeTarget; return false; } constructor(props) { super(); this.iModel = props.iModel; this._layerRefHandler = new internal_1.LayerTileTreeReferenceHandler(this, false, props.getBackgroundBase?.(), props.getBackgroundLayers?.(), false); this._name = undefined !== props.name ? props.name : ""; let transform; if (undefined !== props.tilesetToDbTransform) { const tf = core_geometry_1.Transform.fromJSON(props.tilesetToDbTransform); if (!tf.isIdentity) transform = tf; this._transform = transform; } this._source = props.source; this._getDisplaySettings = () => props.getDisplaySettings(); if (props.planarClipMask) this._planarClipMask = PlanarClipMaskState_1.PlanarClipMaskState.create(props.planarClipMask); if (undefined !== props.classifiers) this._classifier = (0, internal_1.createClassifierTileTreeReference)(props.classifiers, this, props.iModel, props.source); } get planarClassifierTreeRef() { return this._classifier && this._classifier.activeClassifier && this._classifier.isPlanar ? this._classifier : undefined; } unionFitRange(union) { const contentRange = this.computeWorldContentRange(); if (!contentRange.isNull && contentRange.diagonal().magnitude() < core_geometry_1.Constant.earthRadiusWGS84.equator) union.extendRange(contentRange); } get isGlobal() { if (undefined === this._isGlobal) { const range = this.computeWorldContentRange(); if (!range.isNull) this._isGlobal = range.diagonal().magnitude() > 2 * core_geometry_1.Constant.earthRadiusWGS84.equator; } return this._isGlobal === undefined ? false : this._isGlobal; } addToScene(context) { const tree = this.treeOwner.load(); if (undefined === tree || !this._layerRefHandler.initializeLayers(context)) return; // Not loaded yet. // NB: The classifier must be added first, so we can find it when adding our own tiles. if (this._classifier && this._classifier.activeClassifier) this._classifier.addToScene(context); this.addPlanarClassifierOrMaskToScene(context); super.addToScene(context); } addPlanarClassifierOrMaskToScene(context) { // A planarClassifier is required if there is a classification tree OR planar masking is required. const classifierTree = this.planarClassifierTreeRef; const planarClipMask = this._planarClipMask ?? context.viewport.displayStyle.getPlanarClipMaskState(this.modelId); if (!classifierTree && !planarClipMask) return; if (classifierTree && !classifierTree.treeOwner.load()) return; context.addPlanarClassifier(this.modelId, classifierTree, planarClipMask); } discloseTileTrees(trees) { super.discloseTileTrees(trees); if (undefined !== this._classifier) this._classifier.discloseTileTrees(trees); if (undefined !== this._mapDrapeTree) this._mapDrapeTree.discloseTileTrees(trees); if (undefined !== this._planarClipMask) this._planarClipMask.discloseTileTrees(trees); this._layerRefHandler.discloseTileTrees(trees); } collectStatistics(stats) { super.collectStatistics(stats); const tree = undefined !== this._classifier ? this._classifier.treeOwner.tileTree : undefined; if (undefined !== tree) tree.collectStatistics(stats); } createDrawArgs(context) { const args = super.createDrawArgs(context); if (args) { args.graphics.realityModelDisplaySettings = this._getDisplaySettings(); args.graphics.realityModelRange = args.tree.rootTile.contentRange; if (args.tree instanceof internal_1.RealityTileTree) { const maxSSE = args.tree.loader.maximumScreenSpaceError; if (undefined !== maxSSE) args.maximumScreenSpaceError = maxSSE; } } return args; } } RealityModelTileTree.Reference = Reference; async function createRealityModelTileTree(rdSourceKey, iModel, modelId, tilesetToDb, opts) { const rdSource = await RealityDataSource_1.RealityDataSource.fromKey(rdSourceKey, iModel.iTwinId); // If we can get a valid connection from sourceKey, returns the tile tree if (rdSource) { // Serialize the reality data source key into a string to uniquely identify this tile tree const tileTreeId = core_common_1.RealityDataSourceKey.convertToString(rdSource.key); if (tileTreeId === undefined) return undefined; const props = await getTileTreeProps(rdSource, tilesetToDb, iModel); const loader = new RealityModelTileLoader(props, new internal_1.BatchedTileIdMap(iModel), opts); const gcsConverterAvailable = await (0, internal_1.getGcsConverterAvailable)(iModel); // The full tileset url is needed so that it includes the url's search parameters if any are present const baseUrl = rdSource instanceof RealityDataSourceTilesetUrlImpl_1.RealityDataSourceTilesetUrlImpl ? rdSource.getTilesetUrl() : undefined; const params = new RealityModelTileTreeParams(tileTreeId, iModel, modelId, loader, gcsConverterAvailable, props.tilesetToEcef, baseUrl, opts?.produceGeometry === "reproject"); return new RealityModelTileTree(params); } return undefined; } RealityModelTileTree.createRealityModelTileTree = createRealityModelTileTree; async function getTileTreeProps(rdSource, tilesetToDbJson, iModel) { const json = await rdSource.getRootDocument(iModel.iTwinId); let rootTransform = iModel.ecefLocation ? iModel.getMapEcefToDb(0) : core_geometry_1.Transform.createIdentity(); const geoConverter = iModel.noGcsDefined ? undefined : iModel.geoServices.getConverter("WGS84"); if (geoConverter !== undefined) { let realityTileRange = (0, core_bentley_1.expectDefined)(RealityModelTileUtils.rangeFromBoundingVolume(json.root.boundingVolume)).range; if (json.root.transform) { const realityToEcef = RealityModelTileUtils.transformFromJson(json.root.transform); realityTileRange = realityToEcef.multiplyRange(realityTileRange); } if (iModel.ecefLocation) { // In initial publishing version the iModel ecef Transform was used to locate the reality model. // This would work well only for tilesets published from that iModel but for iModels the ecef transform is calculated // at the center of the project extents and the reality model location may differ greatly, and the curvature of the earth // could introduce significant errors. // The publishing was modified to calculate the ecef transform at the reality model range center and at the same time the "iModelPublishVersion" // member was added to the root object. In order to continue to locate reality models published from older versions at the // project extents center we look for Tileset version 0.0 and no root.iModelVersion. const ecefOrigin = (0, core_bentley_1.expectDefined)(realityTileRange.localXYZToWorld(.5, .5, .5)); const dbOrigin = rootTransform.multiplyPoint3d(ecefOrigin); const realityOriginToProjectDistance = iModel.projectExtents.distanceToPoint(dbOrigin); const maxProjectDistance = 1E5; // Only use the project GCS projection if within 100KM of the project. Don't attempt to use GCS if global reality model or in another locale - Results will be unreliable. if (realityOriginToProjectDistance < maxProjectDistance && json.asset?.version !== "0.0" || undefined !== json.root?.iModelPublishVersion) { const cartographicOrigin = core_common_1.Cartographic.fromEcef(ecefOrigin); if (cartographicOrigin !== undefined) { const geoOrigin = core_geometry_1.Point3d.create(cartographicOrigin.longitudeDegrees, cartographicOrigin.latitudeDegrees, cartographicOrigin.height); const response = await geoConverter.getIModelCoordinatesFromGeoCoordinates([geoOrigin]); if (response.iModelCoords[0].s === core_common_1.GeoCoordStatus.Success) { const ecefToDb = await (0, BackgroundMapGeometry_1.calculateEcefToDbTransformAtLocation)(core_geometry_1.Point3d.fromJSON(response.iModelCoords[0].p), iModel); if (ecefToDb) rootTransform = ecefToDb; } } } } } let tilesetToEcef = core_geometry_1.Transform.createIdentity(); if (json.root.transform) { tilesetToEcef = RealityModelTileUtils.transformFromJson(json.root.transform); rootTransform = rootTransform.multiplyTransformTransform(tilesetToEcef); } if (undefined !== tilesetToDbJson) rootTransform = core_geometry_1.Transform.fromJSON(tilesetToDbJson).multiplyTransformTransform(rootTransform); const root = await expandSubTree(json.root, rdSource); return new RealityModelTileTreeProps(json, root, rdSource, rootTransform, tilesetToEcef); } })(RealityModelTileTree || (exports.RealityModelTileTree = RealityModelTileTree = {})); /** Supplies a reality data [[TileTree]] from a URL. May be associated with a persistent [[GeometricModelState]], or attached at run-time via a [[ContextRealityModelState]]. */ class RealityTreeReference extends RealityModelTileTree.Reference { _rdSourceKey; _produceGeometry; _modelId; useCachedDecorations; constructor(props) { super(props); this._produceGeometry = props.produceGeometry; // Maybe we should throw if both props.rdSourceKey && props.url are undefined if (props.rdSourceKey) this._rdSourceKey = props.rdSourceKey; else this._rdSourceKey = RealityDataSource_1.RealityDataSource.createKeyFromUrl(props.url ?? "", core_common_1.RealityDataProvider.ContextShare); if (this._produceGeometry) this.collectTileGeometry = (collector) => this._collectTileGeometry(collector); let modelId = props.modelId; if (undefined === modelId && this._source instanceof DisplayStyleState_1.DisplayStyleState) { const treeId = this.createTreeId(core_bentley_1.Id64.invalid); modelId = realityTreeSupplier.findCompatibleContextRealityModelId(treeId, this._source); } this._modelId = modelId ?? props.iModel.transientIds.getNext(); const provider = IModelApp_1.IModelApp.realityDataSourceProviders.find(this._rdSourceKey.provider); this.useCachedDecorations = provider?.useCachedDecorations; } get modelId() { return this._modelId; } createTreeId(modelId) { return { rdSourceKey: this._rdSourceKey, transform: this._transform, modelId, maskModelIds: this.maskModelIds, deduplicateVertices: this._wantWiremesh, produceGeometry: this._produceGeometry, displaySettings: this._getDisplaySettings(), }; } get treeOwner() { return realityTreeSupplier.getOwner(this.createTreeId(this.modelId), this.iModel); } _createGeometryTreeReference(options) { const ref = new RealityTreeReference({ iModel: this.iModel, modelId: this.modelId, source: this._source, rdSourceKey: this._rdSourceKey, name: this._name, produceGeometry: options?.reprojectGeometry ? "reproject" : "yes", getDisplaySettings: () => core_common_1.RealityModelDisplaySettings.defaults, }); (0, core_bentley_1.assert)(undefined !== ref.collectTileGeometry); return ref; } get _wantWiremesh() { return this._source.viewFlags.wiremesh; } get castsShadows() { return true; } get _isLoadingComplete() { return !this._mapDrapeTree || this._mapDrapeTree.isLoadingComplete; } createDrawArgs(context) { // For global reality models (OSM Building layer only) - offset the reality model by the BIM elevation bias. This would not be necessary // if iModels had their elevation set correctly but unfortunately many GCS erroneously report Sea (Geoid) elevation rather than // Geodetic. const tree = this.treeOwner.load(); if (undefined === tree) return undefined; const drawArgs = super.createDrawArgs(context); if (drawArgs !== undefined && this.iModel.isGeoLocated && tree.isContentUnbounded) { const elevationBias = context.viewport.view.displayStyle.backgroundMapElevationBias; if (undefined !== elevationBias) drawArgs.location.origin.z -= elevationBias; } return drawArgs; } addToScene(context) { const tree = this.treeOwner.tileTree; if (undefined !== tree && context.viewport.iModel.isGeoLocated && tree.loader.doDrapeBackgroundMap) { // NB: We save this off strictly so that discloseTileTrees() can find it...better option? this._mapDrapeTree = context.viewport.backgroundDrapeMap; context.addBackgroundDrapedModel(this, undefined); } super.addToScene(context); } canSupplyToolTip(hit) { const classifier = this._classifier?.activeClassifier?.tileTreeReference; if (classifier && classifier.canSupplyToolTip(hit)) { return true; } const tree = this.treeOwner.tileTree; return tree instanceof internal_1.RealityTileTree && hit.iModel === tree.iModel && undefined !== tree.batchTableProperties?.getFeatureProperties(hit.sourceId); } async getToolTip(hit) { const tooltip = this._getToolTip(hit); if (tooltip) { return tooltip; } const classifierTree = this._classifier?.activeClassifier?.tileTreeReference; if (classifierTree) { return classifierTree.getToolTip(hit); } return undefined; } _getToolTip(hit) { const tree = this.treeOwner.tileTree; if (!(tree instanceof internal_1.RealityTileTree) || hit.iModel !== tree.iModel) return undefined; const batch = tree.batchTableProperties?.getFeatureProperties(hit.sourceId); if (undefined === batch && tree.modelId !== hit.sourceId) return undefined; const strings = []; const loader = tree.loader; const type = loader.tree.dataSource.realityDataType; // If a type is specified, display it if (type !== undefined) { // Case insensitive switch (type.toUpperCase()) { case core_common_1.DefaultSupportedTypes.RealityMesh3dTiles.toUpperCase(): strings.push(IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:RealityModelTypes.RealityMesh3DTiles")); break; case core_common_1.DefaultSupportedTypes.Terrain3dTiles.toUpperCase(): strings.push(IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:RealityModelTypes.Terrain3DTiles")); break; case core_common_1.DefaultSupportedTypes.Cesium3dTiles.toUpperCase(): strings.push(IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:RealityModelTypes.Cesium3DTiles")); break; } } if (this._name) { strings.push(`${IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:TooltipInfo.Name")} ${this._name}`); } else { const cesiumAsset = this._rdSourceKey.provider === core_common_1.RealityDataProvider.CesiumIonAsset ? internal_1.CesiumIonAssetProvider.parseCesiumUrl(this._rdSourceKey.id) : undefined; strings.push(cesiumAsset ? `Cesium Asset: ${cesiumAsset.id}` : this._rdSourceKey.id); } if (batch !== undefined) for (const key of Object.keys(batch)) if (-1 === key.indexOf("#")) // Avoid internal cesium strings.push(`${key}: ${JSON.stringify(batch[key])}`); const div = document.createElement("div"); div.innerHTML = strings.join("<br>"); return div; } /** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [addAttributions] instead. */ addLogoCards(cards) { if (this._rdSourceKey.provider === core_common_1.RealityDataProvider.CesiumIonAsset && !cards.dataset.openStreetMapLogoCard) { cards.dataset.openStreetMapLogoCard = "true"; cards.appendChild(IModelApp_1.IModelApp.makeLogoCard({ heading: "OpenStreetMap", notice: `&copy;<a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> ${IModelApp_1.IModelApp.localization.getLocalizedString("iModelJs:BackgroundMap:OpenStreetMapContributors")}` })); } } async addAttributions(cards, vp) { const provider = IModelApp_1.IModelApp.realityDataSourceProviders.find(this._rdSourceKey.provider); if (provider?.addAttributions) { await provider.addAttributions(cards, vp); } } decorate(_context) { const provider = IModelApp_1.IModelApp.realityDataSourceProviders.find(this._rdSourceKey.provider); if (provider?.decorate) { provider.decorate(_context); } } } exports.RealityTreeReference = RealityTreeReference; //# sourceMappingURL=RealityModelTileTree.js.map