UNPKG

@itwin/core-frontend

Version:
248 lines • 13.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 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