@itwin/core-frontend
Version:
iTwin.js frontend components
248 lines • 13.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 Tiles
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RealityTileLoader = void 0;
exports.createReaderPropsWithBaseUrl = createReaderPropsWithBaseUrl;
const core_bentley_1 = require("@itwin/core-bentley");
const core_geometry_1 = require("@itwin/core-geometry");
const core_common_1 = require("@itwin/core-common");
const IModelApp_1 = require("../../IModelApp");
const GraphicBranch_1 = require("../../render/GraphicBranch");
const Viewport_1 = require("../../Viewport");
const GltfSchema_1 = require("../../common/gltf/GltfSchema");
const internal_1 = require("../../tile/internal");
const defaultViewFlagOverrides = (0, internal_1.createDefaultViewFlagOverrides)({});
const scratchTileCenterWorld = new core_geometry_1.Point3d();
const scratchTileCenterView = new core_geometry_1.Point3d();
/** Serves as a "handler" for a specific type of [[TileTree]]. Its primary responsibilities involve loading tile content.
* @internal
*/
class RealityTileLoader {
_produceGeometry;
_containsPointClouds = false;
preloadRealityParentDepth;
preloadRealityParentSkip;
constructor(_produceGeometry) {
this._produceGeometry = _produceGeometry;
this.preloadRealityParentDepth = IModelApp_1.IModelApp.tileAdmin.contextPreloadParentDepth;
this.preloadRealityParentSkip = IModelApp_1.IModelApp.tileAdmin.contextPreloadParentSkip;
}
computeTilePriority(tile, viewports, _users) {
// ###TODO: Handle case where tile tree reference(s) have a transform different from tree's (background map with ground bias).
return RealityTileLoader.computeTileLocationPriority(tile, viewports, tile.tree.iModelTransform);
}
get wantDeduplicatedVertices() { return false; }
get _batchType() { return core_common_1.BatchType.Primary; }
get _loadEdges() { return true; }
getBatchIdMap() { return undefined; }
get isContentUnbounded() { return false; }
get containsPointClouds() { return this._containsPointClouds; }
get parentsAndChildrenExclusive() { return true; }
forceTileLoad(_tile) { return false; }
get maximumScreenSpaceError() { return undefined; }
processSelectedTiles(selected, _args) { return selected; }
// NB: The isCanceled arg is chiefly for tests...in usual case it just returns false if the tile is no longer in 'loading' state.
async loadTileContent(tile, data, system, isCanceled) {
(0, core_bentley_1.assert)(data instanceof Uint8Array);
const blob = data;
const streamBuffer = core_bentley_1.ByteStream.fromUint8Array(blob);
const realityTile = tile;
return (this._produceGeometry && this._produceGeometry !== "no") ? this.loadGeometryFromStream(realityTile, streamBuffer, system) : this.loadGraphicsFromStream(realityTile, streamBuffer, system, isCanceled);
}
_getFormat(streamBuffer) {
const position = streamBuffer.curPos;
const format = streamBuffer.readUint32();
streamBuffer.curPos = position;
return format;
}
async loadGeometryFromStream(tile, streamBuffer, system) {
const format = this._getFormat(streamBuffer);
if (format !== core_common_1.TileFormat.B3dm)
return {};
const { is3d, yAxisUp, iModel, modelId } = tile.realityRoot;
const reader = internal_1.B3dmReader.create(streamBuffer, iModel, modelId, is3d, tile.contentRange, system, yAxisUp, tile.isLeaf, tile.center, tile.transformToRoot, undefined, this.getBatchIdMap());
if (reader)
reader.defaultWrapMode = GltfSchema_1.GltfWrapMode.ClampToEdge;
let transform = tile.tree.iModelTransform;
if (tile.transformToRoot) {
transform = transform.multiplyTransformTransform(tile.transformToRoot);
}
const geom = reader?.readGltfAndCreateGeometry(transform);
// See RealityTileTree.reprojectAndResolveChildren for how reprojectionTransform is calculated
const xForm = tile.reprojectionTransform;
if (tile.tree.reprojectGeometry && geom?.polyfaces && xForm) {
const polyfaces = geom.polyfaces.map((pf) => pf.cloneTransformed(xForm));
return { geometry: { polyfaces } };
}
else {
return { geometry: geom };
}
}
async loadGraphicsFromStream(tile, streamBuffer, system, isCanceled) {
const format = this._getFormat(streamBuffer);
if (undefined === isCanceled)
isCanceled = () => !tile.isLoading;
const { is3d, yAxisUp, iModel, modelId } = tile.realityRoot;
let reader;
const ecefTransform = tile.tree.iModel.isGeoLocated ? tile.tree.iModel.getEcefTransform() : core_geometry_1.Transform.createIdentity();
const tileData = {
ecefTransform,
range: tile.range,
layerClassifiers: tile.tree.layerHandler?.layerClassifiers,
};
switch (format) {
case core_common_1.TileFormat.IModel:
reader = internal_1.ImdlReader.create({
stream: streamBuffer,
iModel,
modelId,
is3d,
system,
isCanceled,
});
break;
case core_common_1.TileFormat.Pnts:
this._containsPointClouds = true;
const res = await (0, internal_1.readPointCloudTileContent)(streamBuffer, iModel, modelId, is3d, tile, system);
let graphic = res.graphic;
const rtcCenter = res.rtcCenter;
if (graphic && (rtcCenter || tile.transformToRoot && !tile.transformToRoot.isIdentity)) {
const transformBranch = new GraphicBranch_1.GraphicBranch(true);
transformBranch.add(graphic);
let xform;
if (!tile.transformToRoot && rtcCenter)
xform = core_geometry_1.Transform.createTranslation(rtcCenter);
else {
if (undefined === tile.transformToRoot) {
throw new Error("RealityTileLoader.loadGraphicsFromStream: tile.transformToRoot is undefined");
}
if (rtcCenter)
xform = core_geometry_1.Transform.createOriginAndMatrix(rtcCenter.plus(tile.transformToRoot.origin), tile.transformToRoot.matrix);
else
xform = tile.transformToRoot;
}
graphic = system.createBranch(transformBranch, xform);
}
return { graphic };
case core_common_1.TileFormat.B3dm:
reader = internal_1.B3dmReader.create(streamBuffer, iModel, modelId, is3d, tile.contentRange, system, yAxisUp, tile.isLeaf, tile.center, tile.transformToRoot, isCanceled, this.getBatchIdMap(), this.wantDeduplicatedVertices, tileData);
if (reader) {
// glTF spec defaults wrap mode to "repeat" but many reality tiles omit the wrap mode and should not repeat.
// The render system also currently only produces mip-maps for repeating textures, and we don't want mip-maps for reality tile textures.
(0, core_bentley_1.assert)(reader instanceof internal_1.GltfReader);
reader.defaultWrapMode = GltfSchema_1.GltfWrapMode.ClampToEdge;
}
break;
case core_common_1.TileFormat.I3dm:
reader = internal_1.I3dmReader.create(streamBuffer, iModel, modelId, is3d, tile.contentRange, system, yAxisUp, tile.isLeaf, isCanceled, undefined, this.wantDeduplicatedVertices, tileData);
break;
case core_common_1.TileFormat.Gltf:
const tree = tile.tree;
const baseUrl = tree.baseUrl;
const props = createReaderPropsWithBaseUrl(streamBuffer, yAxisUp, baseUrl);
if (props) {
reader = new internal_1.GltfGraphicsReader(props, {
iModel,
gltf: props.glTF,
contentRange: tile.contentRange,
transform: tile.transformToRoot,
hasChildren: !tile.isLeaf,
pickableOptions: { id: modelId },
idMap: this.getBatchIdMap(),
tileData
});
}
break;
case core_common_1.TileFormat.Cmpt:
const header = new core_common_1.CompositeTileHeader(streamBuffer);
if (!header.isValid)
return {};
const branch = new GraphicBranch_1.GraphicBranch(true);
for (let i = 0; i < header.tileCount; i++) {
const tilePosition = streamBuffer.curPos;
streamBuffer.advance(8); // Skip magic and version.
const tileBytes = streamBuffer.readUint32();
streamBuffer.curPos = tilePosition;
const result = await this.loadGraphicsFromStream(tile, streamBuffer, system, isCanceled);
if (result.graphic)
branch.add(result.graphic);
streamBuffer.curPos = tilePosition + tileBytes;
}
return { graphic: branch.isEmpty ? undefined : system.createBranch(branch, core_geometry_1.Transform.createIdentity()), isLeaf: tile.isLeaf };
default:
(0, core_bentley_1.assert)(false, `unknown tile format ${format}`);
break;
}
let content = {};
if (undefined !== reader) {
try {
content = await reader.read();
if (content.containsPointCloud)
this._containsPointClouds = true;
}
catch {
// Failure to load should prevent us from trying to load children
content.isLeaf = true;
}
}
return content;
}
get viewFlagOverrides() { return defaultViewFlagOverrides; }
static computeTileLocationPriority(tile, viewports, location) {
// Compute a priority value for tiles that are:
// * Closer to the eye;
// * Closer to the center of attention (center of the screen or zoom target).
// This way, we can load in priority tiles that are more likely to be important.
let center;
let minDistance = 1.0;
const currentInputState = IModelApp_1.IModelApp.toolAdmin.currentInputState;
const now = Date.now();
const wheelEventRelevanceTimeout = 1000; // Wheel events older than this value will not be considered
for (const viewport of viewports) {
center = center ?? location.multiplyPoint3d(tile.center, scratchTileCenterWorld);
const npc = viewport.worldToNpc(center, scratchTileCenterView);
let focusPoint = new core_geometry_1.Point2d(0.5, 0.5);
if (currentInputState.viewport === viewport && viewport instanceof Viewport_1.ScreenViewport) {
// Try to get a better target point from the last zoom target
const { lastWheelEvent } = currentInputState;
if (lastWheelEvent !== undefined && now - lastWheelEvent.time < wheelEventRelevanceTimeout) {
const focusPointCandidate = core_geometry_1.Point2d.fromJSON(viewport.worldToNpc(lastWheelEvent.point));
if (focusPointCandidate.x > 0 && focusPointCandidate.x < 1 && focusPointCandidate.y > 0 && focusPointCandidate.y < 1)
focusPoint = focusPointCandidate;
}
}
// NB: In NPC coords, 0 = far plane, 1 = near plane.
const distanceToEye = 1.0 - npc.z;
const distanceToCenter = Math.min(npc.distanceXY(focusPoint) / 0.707, 1.0); // Math.sqrt(0.5) = 0.707
// Distance is a mix of the two previously computed values, still in range [0; 1]
// We use this factor to determine how much the distance to the center of attention is important compared to distance to the eye
const distanceToCenterWeight = 0.3;
const distance = distanceToEye * (1.0 - distanceToCenterWeight) + distanceToCenter * distanceToCenterWeight;
minDistance = Math.min(distance, minDistance);
}
return minDistance;
}
}
exports.RealityTileLoader = RealityTileLoader;
/** Exposed strictly for testing purposes.
* @internal
*/
function createReaderPropsWithBaseUrl(streamBuffer, yAxisUp, baseUrl) {
let url;
if (baseUrl) {
try {
url = new URL(baseUrl);
}
catch {
url = undefined;
}
}
return internal_1.GltfReaderProps.create(streamBuffer.nextBytes(streamBuffer.arrayBuffer.byteLength), yAxisUp, url);
}
//# sourceMappingURL=RealityTileLoader.js.map