UNPKG

@itwin/core-frontend

Version:
426 lines • 15.3 kB
/*--------------------------------------------------------------------------------------------- * 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