UNPKG

@itwin/frontend-devtools

Version:

Debug menu and supporting UI widgets

394 lines • 18.3 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. *--------------------------------------------------------------------------------------------*/ Object.defineProperty(exports, "__esModule", { value: true }); exports.ToggleShadowFrustumTool = exports.ToggleSelectedViewFrustumTool = exports.ToggleFrustumSnapshotTool = exports.FrustumDecorator = void 0; /** @packageDocumentation * @module Tools */ const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const core_frontend_1 = require("@itwin/core-frontend"); const parseToggle_1 = require("./parseToggle"); /** * Decorates the viewport with a graphical depiction of a Frustum. * This is obviously only useful when drawn inside a viewport using a *different* Frustum. * Options for doing so include: * - Having more than one viewport, and drawing the frustum of one viewport inside the other viewports; and * - Allowing the user to take a snapshot of the current frustum, then navigate the view to inspect it within the same viewport. */ class FrustumDecoration { _options; _worldFrustum; _adjustedWorldFrustum; _preloadFrustum; _npcFrustum; _worldToNpcMap; _eyePoint; _focalPlane; _isCameraOn; constructor(vp, view, _options) { this._options = _options; this._worldFrustum = vp.getFrustum(core_frontend_1.CoordSystem.World, false); this._adjustedWorldFrustum = vp.getFrustum(core_frontend_1.CoordSystem.World, true); this._preloadFrustum = vp.viewingSpace.getPreloadFrustum(); this._npcFrustum = vp.getFrustum(core_frontend_1.CoordSystem.Npc, true); this._worldToNpcMap = vp.viewingSpace.worldToNpcMap.clone(); this._eyePoint = view.camera.getEyePoint().clone(); this._focalPlane = vp.worldToNpc(view.getTargetPoint()).z; this._isCameraOn = vp.isCameraOn; } static create(vp, options) { const view = vp.view.isSpatialView() ? vp.view : undefined; return undefined !== view ? new FrustumDecoration(vp, view, options) : undefined; } decorate(context) { const builder = context.createGraphicBuilder(core_frontend_1.GraphicType.WorldDecoration); if (this._isCameraOn) FrustumDecoration.drawEyePositionAndFocalPlane(builder, this._npcFrustum, this._worldToNpcMap, this._eyePoint, this._focalPlane, context.viewport); FrustumDecoration.drawFrustumBox(builder, this._worldFrustum, false, context.viewport); // show original frustum... FrustumDecoration.drawFrustumBox(builder, this._adjustedWorldFrustum, true, context.viewport); // show adjusted frustum... const options = this._options; if (options !== undefined) { if (options.showPreloadFrustum) FrustumDecoration.drawPreloadFrustum(builder, this._preloadFrustum); if (options?.showBackgroundIntersections) { const backgroundMapGeometry = context.viewport.view.displayStyle.getBackgroundMapGeometry(); if (backgroundMapGeometry) backgroundMapGeometry.addFrustumDecorations(builder, this._adjustedWorldFrustum); } } context.addDecorationFromBuilder(builder); } static drawPreloadFrustum(builder, frustum) { const preloadColor = core_common_1.ColorDef.create(core_common_1.ColorByName.coral); builder.setSymbology(preloadColor, preloadColor, 1, core_common_1.LinePixels.Code2); builder.addFrustum(frustum); } static drawFrustumBox(builder, frustum, adjustedBox, vp) { const backPts = this.getPlanePts(frustum.points, false); // back plane const frontPts = this.getPlanePts(frustum.points, true); // front plane const bgColor = vp.view.backgroundColor; const backAndBottomColor = core_common_1.ColorDef.red.adjustedForContrast(bgColor); const frontAndTopLeftColor = core_common_1.ColorDef.blue.adjustedForContrast(bgColor); const frontAndTopRightColor = core_common_1.ColorDef.green.adjustedForContrast(bgColor); const edgeWeight = adjustedBox ? 2 : 1; const edgeStyle = adjustedBox ? core_common_1.LinePixels.Solid : core_common_1.LinePixels.Code2; // Back plane builder.setSymbology(backAndBottomColor, core_common_1.ColorDef.black, edgeWeight, edgeStyle); builder.addLineString(backPts); // Front plane builder.setSymbology(frontAndTopLeftColor, core_common_1.ColorDef.black, edgeWeight, edgeStyle); builder.addLineString(frontPts); // Bottom edge builder.setSymbology(backAndBottomColor, core_common_1.ColorDef.black, edgeWeight, edgeStyle); builder.addLineString(this.getEdgePts(backPts, frontPts, 0)); builder.addLineString(this.getEdgePts(backPts, frontPts, 1)); // Top edge builder.setSymbology(frontAndTopRightColor, core_common_1.ColorDef.black, edgeWeight, edgeStyle); builder.addLineString(this.getEdgePts(backPts, frontPts, 2)); builder.setSymbology(frontAndTopLeftColor, core_common_1.ColorDef.black, edgeWeight, edgeStyle); builder.addLineString(this.getEdgePts(backPts, frontPts, 3)); } static getEdgePts(startPts, endPts, index) { return [ startPts[index], endPts[index], ]; } static getPlanePts(frustPts, front) { const baseIndex = front ? core_common_1.Npc._001 : core_common_1.Npc._000; const planePts = [ frustPts[baseIndex + core_common_1.Npc._000], frustPts[baseIndex + core_common_1.Npc._100], frustPts[baseIndex + core_common_1.Npc._110], frustPts[baseIndex + core_common_1.Npc._010], ]; planePts.push(planePts[0]); return planePts; } static drawEyePositionAndFocalPlane(builder, npcFrustum, worldToNpcMap, eyePoint, focusPlaneNpc, vp) { // Eye position... const contrastColor = vp.getContrastToBackgroundColor(); builder.setSymbology(contrastColor, core_common_1.ColorDef.black, 8); builder.addPointString([eyePoint]); // Focal plane... const focalPtsNpc = FrustumDecoration.getPlanePts(npcFrustum.points, false); const focalPtsWorld = []; for (const npcPt of focalPtsNpc) focalPtsWorld.push(core_geometry_1.Point3d.create(npcPt.x, npcPt.y, focusPlaneNpc)); worldToNpcMap.transform1.multiplyPoint3dArrayQuietNormalize(focalPtsWorld); const bgColor = vp.view.backgroundColor; const focalPlaneColor = core_common_1.ColorDef.green.adjustedForContrast(bgColor); const focalTransColor = focalPlaneColor.withTransparency(100); builder.setSymbology(focalPlaneColor, focalTransColor, 2); builder.addLineString(focalPtsWorld); builder.addShape(focalPtsWorld); } } /** * Decorates the viewport with a graphical depiction of a Frustum. * This is obviously only useful when drawn inside a viewport using a *different* Frustum. * Options for doing so include: * - Having more than one viewport, and drawing the frustum of one viewport inside the other viewports; and * - Allowing the user to take a snapshot of the current frustum, then navigate the view to inspect it within the same viewport. * @beta */ class FrustumDecorator { _decoration; constructor(vp, options) { this._decoration = FrustumDecoration.create(vp, options); } /** This will allow the render system to cache and reuse the decorations created by this decorator's decorate() method. */ useCachedDecorations = true; decorate(context) { if (undefined !== this._decoration) this._decoration.decorate(context); } static _instance; /** Add the decoration to the specified viewport. */ static enable(vp, options) { FrustumDecorator.disable(); FrustumDecorator._instance = new FrustumDecorator(vp, options); core_frontend_1.IModelApp.viewManager.addDecorator(FrustumDecorator._instance); } /** Remove the decoration from the specified viewport. */ static disable() { const instance = FrustumDecorator._instance; if (undefined !== instance) { core_frontend_1.IModelApp.viewManager.dropDecorator(instance); FrustumDecorator._instance = undefined; } } static get isEnabled() { return undefined !== FrustumDecorator._instance; } } exports.FrustumDecorator = FrustumDecorator; /** Enable ("ON"), disable ("OFF"), or toggle ("TOGGLE" or omitted) the [[FrustumDecorator]]. * @beta */ class ToggleFrustumSnapshotTool extends core_frontend_1.Tool { static toolId = "ToggleFrustumSnapshot"; static get minArgs() { return 0; } static get maxArgs() { return 2; } async run(enable, showPreloadFrustum, showBackgroundIntersections) { const vp = core_frontend_1.IModelApp.viewManager.selectedView; if (undefined === vp) return true; if (undefined === enable) enable = !FrustumDecorator.isEnabled; if (enable !== FrustumDecorator.isEnabled) { if (enable) { FrustumDecorator.enable(vp, { showPreloadFrustum, showBackgroundIntersections }); vp.onChangeView.addOnce(() => FrustumDecorator.disable()); } else { FrustumDecorator.disable(); } } return true; } async parseAndRun(...args) { let showPreload, showBackgroundIntersections, enable; for (const arg of args) { if (arg === "preload") showPreload = true; else if (arg === "background") showBackgroundIntersections = true; else enable = (0, parseToggle_1.parseToggle)(arg); } if (typeof enable !== "string") await this.run(enable, showPreload, showBackgroundIntersections); return true; } } exports.ToggleFrustumSnapshotTool = ToggleFrustumSnapshotTool; /** * Decorates the viewport with a graphical depiction of a Frustum from the currently selected viewport. * Only useful when more than one spatial viewport is open. */ class SelectedViewFrustumDecoration { _options; static _decorator; _targetVp; _removeDecorationListener; _removeViewChangedListener; /** This will allow the render system to cache and reuse the decorations created by this decorator's decorate() method. */ useCachedDecorations = true; constructor(vp, _options) { this._options = _options; this._targetVp = vp; this._removeDecorationListener = core_frontend_1.IModelApp.viewManager.addDecorator(this); this._removeViewChangedListener = vp.onViewChanged.addListener(this.onViewChanged, this); // eslint-disable-line @typescript-eslint/unbound-method core_frontend_1.IModelApp.viewManager.invalidateCachedDecorationsAllViews(this); } stop() { if (this._removeDecorationListener) { this._removeDecorationListener(); this._removeDecorationListener = undefined; } if (this._removeViewChangedListener) { this._removeViewChangedListener(); this._removeViewChangedListener = undefined; } core_frontend_1.IModelApp.viewManager.invalidateCachedDecorationsAllViews(this); } onViewChanged(targetVp) { if (targetVp !== this._targetVp) return; const decorator = SelectedViewFrustumDecoration._decorator; if (undefined !== decorator) { for (const vp of core_frontend_1.IModelApp.viewManager) { if (vp !== this._targetVp) vp.invalidateCachedDecorations(decorator); } } } decorate(context) { const vp = context.viewport; if (!this._targetVp.view.isSpatialView() || vp === this._targetVp || !vp.view.isSpatialView()) return; const builder = context.createGraphicBuilder(core_frontend_1.GraphicType.WorldDecoration); if (this._targetVp.isCameraOn) { const npcFrustum = this._targetVp.getFrustum(core_frontend_1.CoordSystem.Npc, true); const focalPlane = this._targetVp.worldToNpc(this._targetVp.view.getTargetPoint()).z; FrustumDecoration.drawEyePositionAndFocalPlane(builder, npcFrustum, this._targetVp.viewingSpace.worldToNpcMap, this._targetVp.view.camera.getEyePoint(), focalPlane, context.viewport); } const worldFrustum = this._targetVp.getFrustum(core_frontend_1.CoordSystem.World, false); const adjustedWorldFrustum = this._targetVp.getFrustum(core_frontend_1.CoordSystem.World, true); FrustumDecoration.drawFrustumBox(builder, worldFrustum, false, context.viewport); // show original frustum... FrustumDecoration.drawFrustumBox(builder, adjustedWorldFrustum, true, context.viewport); // show adjusted frustum... if (this._options && this._options.showPreloadFrustum) FrustumDecoration.drawPreloadFrustum(builder, context.viewport.viewingSpace.getPreloadFrustum()); context.addDecorationFromBuilder(builder); } // Returns true if decoration becomes enabled. static toggle(vp, enabled) { if (undefined !== enabled) { const alreadyEnabled = undefined !== SelectedViewFrustumDecoration._decorator; if (enabled === alreadyEnabled) return alreadyEnabled; } if (undefined === SelectedViewFrustumDecoration._decorator) { SelectedViewFrustumDecoration._decorator = new SelectedViewFrustumDecoration(vp); return true; } else { SelectedViewFrustumDecoration._decorator.stop(); SelectedViewFrustumDecoration._decorator = undefined; return false; } } } /** Enable ("ON"), disable ("OFF"), or toggle ("TOGGLE" or omitted) the selected view frustum decoration. * @beta */ class ToggleSelectedViewFrustumTool extends core_frontend_1.Tool { static toolId = "ToggleSelectedViewFrustum"; static get minArgs() { return 0; } static get maxArgs() { return 1; } async run(enable) { const vp = core_frontend_1.IModelApp.viewManager.selectedView; if (undefined === vp || !vp.view.isSpatialView()) return false; if (SelectedViewFrustumDecoration.toggle(vp, enable)) { const remove = vp.onChangeView.addListener((_vp, prev) => { if (!prev.hasSameCoordinates(vp.view)) { SelectedViewFrustumDecoration.toggle(vp, false); remove(); } }); } return true; } async parseAndRun(...args) { const enable = (0, parseToggle_1.parseToggle)(args[0]); if (typeof enable !== "string") await this.run(enable); return true; } } exports.ToggleSelectedViewFrustumTool = ToggleSelectedViewFrustumTool; class ShadowFrustumDecoration { static _instance; _targetVp; _cleanup; /** This will allow the render system to cache and reuse the decorations created by this decorator's decorate() method. */ useCachedDecorations = true; constructor(vp) { this._targetVp = vp; const removeDecorator = core_frontend_1.IModelApp.viewManager.addDecorator(this); const removeOnRender = vp.onRender.addListener((_) => this.onRender()); this._cleanup = () => { removeDecorator(); removeOnRender(); }; core_frontend_1.IModelApp.viewManager.invalidateCachedDecorationsAllViews(this); } stop() { if (undefined !== this._cleanup) { this._cleanup(); this._cleanup = undefined; } core_frontend_1.IModelApp.viewManager.invalidateCachedDecorationsAllViews(this); } onRender() { const decorator = ShadowFrustumDecoration._instance; if (undefined !== decorator) { for (const vp of core_frontend_1.IModelApp.viewManager) { if (vp !== this._targetVp) vp.invalidateCachedDecorations(decorator); } } } decorate(context) { const frustum = this._targetVp.target.debugControl.shadowFrustum; if (undefined === frustum) return; const thisVp = context.viewport; if (thisVp === this._targetVp || !thisVp.view.isSpatialView()) return; const builder = context.createGraphicBuilder(core_frontend_1.GraphicType.WorldDecoration); FrustumDecoration.drawFrustumBox(builder, frustum, false, thisVp); context.addDecorationFromBuilder(builder); } static toggle(vp, enabled) { const instance = ShadowFrustumDecoration._instance; if (undefined !== enabled) { const alreadyEnabled = undefined !== instance; if (enabled === alreadyEnabled) return; } if (undefined === instance) { ShadowFrustumDecoration._instance = new ShadowFrustumDecoration(vp); } else { instance.stop(); ShadowFrustumDecoration._instance = undefined; } } } /** Toggle visualization of the selected viewport's shadow frustum in all other viewports. * @beta */ class ToggleShadowFrustumTool extends core_frontend_1.Tool { static toolId = "ToggleShadowFrustum"; static get minArgs() { return 0; } static get maxArgs() { return 1; } async run(enable) { const vp = core_frontend_1.IModelApp.viewManager.selectedView; if (undefined !== vp && vp.view.isSpatialView()) ShadowFrustumDecoration.toggle(vp, enable); return true; } async parseAndRun(...args) { const enable = (0, parseToggle_1.parseToggle)(args[0]); if (typeof enable !== "string") await this.run(enable); return true; } } exports.ToggleShadowFrustumTool = ToggleShadowFrustumTool; //# sourceMappingURL=FrustumDecoration.js.map