@itwin/core-frontend
Version:
iTwin.js frontend components
426 lines • 15.3 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 WebGL
*/
import { assert, dispose } from "@itwin/core-bentley";
import { ThematicDisplayMode } from "@itwin/core-common";
import { Range3d, Transform } from "@itwin/core-geometry";
import { FeatureSymbology } from "../../../render/FeatureSymbology";
import { GraphicBranch } from "../../../render/GraphicBranch";
import { RenderGraphic } from "../../../render/RenderGraphic";
import { EdgeSettings } from "./EdgeSettings";
import { FeatureOverrides } from "./FeatureOverrides";
import { PlanarClassifier } from "./PlanarClassifier";
import { TextureDrape } from "./TextureDrape";
import { ThematicSensors } from "./ThematicSensors";
import { Contours } from "./Contours";
/** @internal */
export class Graphic extends RenderGraphic {
addHiliteCommands(_commands, _pass) { assert(false); }
toPrimitive() { return undefined; }
}
export class GraphicOwner extends Graphic {
_graphic;
constructor(graphic) {
super();
this._graphic = graphic;
}
get graphic() { return this._graphic; }
_isDisposed = false;
get isDisposed() { return this._isDisposed; }
dispose() { this._isDisposed = true; }
disposeGraphic() {
this.graphic[Symbol.dispose]();
}
collectStatistics(stats) {
this.graphic.collectStatistics(stats);
}
unionRange(range) {
this.graphic.unionRange(range);
}
addCommands(commands) {
this._graphic.addCommands(commands);
}
get isPickable() {
return this._graphic.isPickable;
}
addHiliteCommands(commands, pass) {
this._graphic.addHiliteCommands(commands, pass);
}
toPrimitive() {
return this._graphic.toPrimitive();
}
}
/** @internal exported strictly for tests. */
export class PerTargetBatchData {
target;
_featureOverrides = new Map();
_contours;
_thematicSensors;
constructor(target) {
this.target = target;
}
[Symbol.dispose]() {
this._thematicSensors = dispose(this._thematicSensors);
this._contours = this._contours?.[Symbol.dispose]();
for (const value of this._featureOverrides.values())
dispose(value);
this._featureOverrides.clear();
}
getThematicSensors(batch) {
if (this._thematicSensors && !this._thematicSensors.matchesTarget(this.target))
this._thematicSensors = dispose(this._thematicSensors);
if (!this._thematicSensors)
this._thematicSensors = ThematicSensors.create(this.target, batch.range);
this._thematicSensors.update(this.target.uniforms.frustum.viewMatrix);
return this._thematicSensors;
}
getFeatureOverrides(batch, provider) {
const source = this.target.currentFeatureSymbologyOverrides?.source;
let ovrs = this._featureOverrides.get(source);
if (!ovrs) {
const cleanup = source ? source.onSourceDisposed.addOnce(() => this.onSourceDisposed(source)) : undefined;
this._featureOverrides.set(source, ovrs = FeatureOverrides.createFromTarget(this.target, batch.options, cleanup));
ovrs.initFromMap(batch.featureTable, provider);
}
ovrs.update(batch.featureTable, provider);
return ovrs;
}
getContours(batch) {
if (this._contours && !this._contours.matchesTargetAndFeatureCount(this.target, batch.featureTable))
this._contours = this._contours[Symbol.dispose]();
if (!this._contours) {
this._contours = Contours.createFromTarget(this.target, batch.options);
this._contours.initFromMap(batch.featureTable);
}
else {
this._contours.update(batch.featureTable);
}
return this._contours;
}
collectStatistics(stats) {
if (this._thematicSensors)
stats.addThematicTexture(this._thematicSensors.bytesUsed);
for (const ovrs of this._featureOverrides.values())
stats.addFeatureOverrides(ovrs.byteLength);
if (this._contours)
stats.addContours(this._contours.byteLength);
}
/** Exposed strictly for tests. */
get featureOverrides() { return this._featureOverrides; }
onSourceDisposed(source) {
const ovrs = this._featureOverrides.get(source);
if (ovrs) {
this._featureOverrides.delete(source);
ovrs[Symbol.dispose]();
}
}
}
/** @internal exported strictly for tests. */
export class PerTargetData {
_batch;
_data = [];
constructor(batch) {
this._batch = batch;
}
[Symbol.dispose]() {
for (const data of this._data) {
data.target.onBatchDisposed(this._batch);
data[Symbol.dispose]();
}
this._data.length = 0;
}
get isDisposed() {
return this._data.length === 0;
}
/** Exposed strictly for tests. */
get data() { return this._data; }
onTargetDisposed(target) {
const index = this._data.findIndex((x) => x.target === target);
if (-1 === index)
return;
const data = this._data[index];
data[Symbol.dispose]();
this._data.splice(index, 1);
}
collectStatistics(stats) {
for (const data of this._data)
data.collectStatistics(stats);
}
getThematicSensors(target) {
return this.getBatchData(target).getThematicSensors(this._batch);
}
getFeatureOverrides(target, provider) {
return this.getBatchData(target).getFeatureOverrides(this._batch, provider);
}
getContours(target) {
return this.getBatchData(target).getContours(this._batch);
}
getBatchData(target) {
let data = this._data.find((x) => x.target === target);
if (!data) {
this._data.push(data = new PerTargetBatchData(target));
target.addBatch(this._batch);
}
return data;
}
}
/** @internal */
export class Batch extends Graphic {
graphic;
featureTable;
range;
_context = { batchId: 0 };
/** Public strictly for tests. */
perTargetData = new PerTargetData(this);
options;
// Chiefly for debugging.
get tileId() {
return this.options.tileId;
}
get locateOnly() {
return true === this.options.locateOnly;
}
/** The following are valid only during a draw and reset afterward. */
get batchId() { return this._context.batchId; }
get batchIModel() { return this._context.iModel; }
get transformFromBatchIModel() { return this._context.transformFromIModel; }
get viewAttachmentId() { return this._context.viewAttachmentId; }
get inSectionDrawingAttachment() { return this._context.inSectionDrawingAttachment; }
setContext(batchId, branch) {
this._context.batchId = batchId;
this._context.iModel = branch.iModel;
this._context.transformFromIModel = branch.transformFromIModel;
this._context.viewAttachmentId = branch.viewAttachmentId;
this._context.inSectionDrawingAttachment = branch.inSectionDrawingAttachment;
}
resetContext() {
this._context.batchId = 0;
this._context.iModel = undefined;
this._context.transformFromIModel = undefined;
this._context.viewAttachmentId = undefined;
this._context.inSectionDrawingAttachment = undefined;
}
constructor(graphic, features, range, options) {
super();
this.graphic = graphic;
this.featureTable = features;
this.range = range;
this.options = options ?? {};
}
_isDisposed = false;
get isDisposed() {
return this._isDisposed && this.perTargetData.isDisposed;
}
// Note: This does not remove FeatureOverrides from the array, but rather disposes of the WebGL resources they contain
dispose() {
dispose(this.graphic);
this.perTargetData[Symbol.dispose]();
this._isDisposed = true;
}
collectStatistics(stats) {
this.graphic.collectStatistics(stats);
stats.addFeatureTable(this.featureTable.byteLength);
this.perTargetData.collectStatistics(stats);
}
unionRange(range) {
range.extendRange(this.range);
}
addCommands(commands) {
commands.addBatch(this);
}
get isPickable() {
return true;
}
getThematicSensors(target) {
assert(target.plan.thematic !== undefined, "thematic display settings must exist");
assert(target.plan.thematic.displayMode === ThematicDisplayMode.InverseDistanceWeightedSensors, "thematic display mode must be sensor-based");
assert(target.plan.thematic.sensorSettings.sensors.length > 0, "must have at least one sensor to process");
return this.perTargetData.getThematicSensors(target);
}
getOverrides(target, provider) {
return this.perTargetData.getFeatureOverrides(target, provider);
}
getContours(target) {
return this.perTargetData.getContours(target);
}
onTargetDisposed(target) {
this.perTargetData.onTargetDisposed(target);
}
}
/** @internal */
export class Branch extends Graphic {
branch;
localToWorldTransform;
clips;
planarClassifier;
textureDrape;
layerClassifiers;
edgeSettings;
iModel; // used chiefly for readPixels to identify context of picked Ids.
frustum;
appearanceProvider;
secondaryClassifiers;
viewAttachmentId;
inSectionDrawingAttachment;
disableClipStyle;
transformFromExternalIModel;
contourLine;
constructor(branch, localToWorld, viewFlags, opts) {
super();
this.branch = branch;
this.localToWorldTransform = localToWorld;
if (undefined !== viewFlags)
branch.setViewFlags(viewFlags);
if (!opts)
return;
this.appearanceProvider = opts.appearanceProvider;
this.clips = opts.clipVolume;
this.iModel = opts.iModel;
this.frustum = opts.frustum;
this.viewAttachmentId = opts.viewAttachmentId;
this.inSectionDrawingAttachment = opts.inSectionDrawingAttachment;
this.disableClipStyle = opts.disableClipStyle;
this.transformFromExternalIModel = opts.transformFromIModel;
this.contourLine = opts.contours;
if (opts.hline)
this.edgeSettings = EdgeSettings.create(opts.hline);
if (opts.classifierOrDrape instanceof PlanarClassifier)
this.planarClassifier = opts.classifierOrDrape;
else if (opts.classifierOrDrape instanceof TextureDrape)
this.textureDrape = opts.classifierOrDrape;
if (opts.secondaryClassifiers) {
this.secondaryClassifiers = new Array();
opts.secondaryClassifiers.forEach((classifier) => {
if (classifier instanceof PlanarClassifier)
this.secondaryClassifiers?.push(classifier);
});
}
}
get isDisposed() {
return 0 === this.branch.entries.length;
}
dispose() {
this.branch[Symbol.dispose]();
}
get isPickable() {
return this.branch.entries.some((gf) => gf.isPickable);
}
collectStatistics(stats) {
this.branch.collectStatistics(stats);
}
unionRange(range) {
const thisRange = new Range3d();
for (const graphic of this.branch.entries)
graphic.unionRange(thisRange);
this.localToWorldTransform.multiplyRange(thisRange, thisRange);
range.extendRange(thisRange);
}
shouldAddCommands(commands) {
const group = commands.target.currentBranch.groupNodeId;
if (undefined !== group && undefined !== this.branch.groupNodeId && this.branch.groupNodeId !== group)
return false;
const nodeId = commands.target.getAnimationTransformNodeId(this.branch.animationNodeId);
return undefined === nodeId || nodeId === commands.target.currentAnimationTransformNodeId;
}
addCommands(commands) {
if (this.shouldAddCommands(commands))
commands.addBranch(this);
}
addHiliteCommands(commands, pass) {
if (this.shouldAddCommands(commands))
commands.addHiliteBranch(this, pass);
}
}
/** @internal */
export class AnimationTransformBranch extends Graphic {
nodeId;
graphic;
constructor(graphic, nodeId) {
super();
assert(graphic instanceof Graphic);
this.graphic = graphic;
this.nodeId = nodeId;
}
dispose() {
this.graphic[Symbol.dispose]();
}
get isDisposed() {
return this.graphic.isDisposed;
}
get isPickable() {
return this.graphic.isPickable;
}
collectStatistics(stats) {
this.graphic.collectStatistics(stats);
}
unionRange(range) {
this.graphic.unionRange(range);
}
addCommands(commands) {
commands.target.currentAnimationTransformNodeId = this.nodeId;
this.graphic.addCommands(commands);
commands.target.currentAnimationTransformNodeId = undefined;
}
addHiliteCommands(commands, pass) {
commands.target.currentAnimationTransformNodeId = this.nodeId;
this.graphic.addHiliteCommands(commands, pass);
commands.target.currentAnimationTransformNodeId = undefined;
}
}
/** @internal */
export class WorldDecorations extends Branch {
constructor(viewFlags) {
super(new GraphicBranch(), Transform.identity, viewFlags);
// World decorations ignore all the symbology overrides for the "scene" geometry...
this.branch.symbologyOverrides = new FeatureSymbology.Overrides();
// Make all subcategories visible.
this.branch.symbologyOverrides.ignoreSubCategory = true;
}
init(decs) {
this.branch.clear();
for (const dec of decs) {
this.branch.add(dec);
}
}
}
/** @internal */
export class GraphicsArray extends Graphic {
graphics;
// Note: We assume the graphics array we get contains undisposed graphics to start
constructor(graphics) {
super();
this.graphics = graphics;
}
get isDisposed() { return 0 === this.graphics.length; }
get isPickable() {
return this.graphics.some((x) => x.isPickable);
}
dispose() {
for (const graphic of this.graphics)
dispose(graphic);
this.graphics.length = 0;
}
addCommands(commands) {
for (const graphic of this.graphics) {
graphic.addCommands(commands);
}
}
addHiliteCommands(commands, pass) {
for (const graphic of this.graphics) {
graphic.addHiliteCommands(commands, pass);
}
}
collectStatistics(stats) {
for (const graphic of this.graphics)
graphic.collectStatistics(stats);
}
unionRange(range) {
for (const graphic of this.graphics)
graphic.unionRange(range);
}
}
//# sourceMappingURL=Graphic.js.map