@itwin/core-frontend
Version:
iTwin.js frontend components
827 lines • 43.5 kB
JavaScript
;
/*---------------------------------------------------------------------------------------------
* 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: `©<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