@itwin/frontend-devtools
Version:
Debug menu and supporting UI widgets
394 lines • 18.3 kB
JavaScript
"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