UNPKG

@itwin/core-frontend

Version:
558 lines • 28.4 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.PlanarClassifier = exports.PlanarClassifierContent = void 0; /** @packageDocumentation * @module WebGL */ const core_bentley_1 = require("@itwin/core-bentley"); const core_common_1 = require("@itwin/core-common"); const core_geometry_1 = require("@itwin/core-geometry"); const internal_1 = require("../../../tile/internal"); const RenderPlanarClassifier_1 = require("../RenderPlanarClassifier"); const BatchState_1 = require("./BatchState"); const BranchStack_1 = require("./BranchStack"); const CachedGeometry_1 = require("./CachedGeometry"); const FrameBuffer_1 = require("./FrameBuffer"); const GL_1 = require("./GL"); const Graphic_1 = require("./Graphic"); const PlanarTextureProjection_1 = require("./PlanarTextureProjection"); const RenderCommands_1 = require("./RenderCommands"); const RenderState_1 = require("./RenderState"); const ScratchDrawParams_1 = require("./ScratchDrawParams"); const System_1 = require("./System"); const Texture_1 = require("./Texture"); var PlanarClassifierContent; (function (PlanarClassifierContent) { PlanarClassifierContent[PlanarClassifierContent["None"] = 0] = "None"; PlanarClassifierContent[PlanarClassifierContent["MaskOnly"] = 1] = "MaskOnly"; PlanarClassifierContent[PlanarClassifierContent["ClassifierOnly"] = 2] = "ClassifierOnly"; PlanarClassifierContent[PlanarClassifierContent["ClassifierAndMask"] = 3] = "ClassifierAndMask"; })(PlanarClassifierContent || (exports.PlanarClassifierContent = PlanarClassifierContent = {})); function createTexture(handle) { return new Texture_1.Texture({ handle, ownership: "external", type: core_common_1.RenderTexture.Type.TileSection, transparency: core_common_1.TextureTransparency.Opaque, }); } function createTextureHandle(width, height, heightMult = 1.0) { return Texture_1.TextureHandle.createForAttachment(width, height * heightMult, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte); } class ClassifierTextures { color; feature; hilite; constructor(color, feature, hilite) { this.color = color; this.feature = feature; this.hilite = hilite; } get isDisposed() { return this.color.isDisposed && this.feature.isDisposed && this.hilite.isDisposed; } [Symbol.dispose]() { (0, core_bentley_1.dispose)(this.color); (0, core_bentley_1.dispose)(this.feature); (0, core_bentley_1.dispose)(this.hilite); } collectStatistics(stats) { stats.addPlanarClassifier(this.color.bytesUsed); stats.addPlanarClassifier(this.feature.bytesUsed); stats.addPlanarClassifier(this.hilite.bytesUsed); } static create(width, height) { const hColor = createTextureHandle(width, height); const hFeature = createTextureHandle(width, height); const hHilite = createTextureHandle(width, height); if (!hColor || !hFeature || !hHilite) return undefined; const color = createTexture(hColor); const feature = createTexture(hFeature); const hilite = createTexture(hHilite); if (!color || !feature || !hilite) return undefined; return new ClassifierTextures(color, feature, hilite); } } class ClassifierFrameBuffers { textures; _hilite; _fbo; _clearGeom; constructor(textures, _hilite, _fbo, _clearGeom) { this.textures = textures; this._hilite = _hilite; this._fbo = _fbo; this._clearGeom = _clearGeom; } get isDisposed() { return this.textures.isDisposed && this._hilite.isDisposed && this._fbo.isDisposed && this._clearGeom.isDisposed; } [Symbol.dispose]() { (0, core_bentley_1.dispose)(this._fbo); (0, core_bentley_1.dispose)(this._clearGeom); (0, core_bentley_1.dispose)(this.textures); (0, core_bentley_1.dispose)(this._hilite); } draw(cmds, target) { System_1.System.instance.frameBufferStack.execute(this._fbo, true, false, () => { target.techniques.draw((0, ScratchDrawParams_1.getDrawParams)(target, this._clearGeom)); target.techniques.execute(target, cmds, 19 /* RenderPass.PlanarClassification */); }); } drawHilite(cmds, target) { const system = System_1.System.instance; const gl = system.context; system.frameBufferStack.execute(this._hilite, true, false, () => { gl.clearColor(0, 0, 0, 0); gl.clear(GL_1.GL.BufferBit.Color); target.techniques.execute(target, cmds, 10 /* RenderPass.Hilite */); }); } static create(width, height) { const textures = ClassifierTextures.create(width, height); if (undefined === textures) return undefined; const hiliteFbo = FrameBuffer_1.FrameBuffer.create([textures.hilite.texture]); if (undefined === hiliteFbo) return undefined; const fbo = FrameBuffer_1.FrameBuffer.create([textures.color.texture, textures.feature.texture]); if (undefined === fbo) return undefined; const geom = CachedGeometry_1.ViewportQuadGeometry.create(21 /* TechniqueId.ClearPickAndColor */); return undefined !== geom ? new this(textures, hiliteFbo, fbo, geom) : undefined; } } class SingleTextureFrameBuffer { texture; fbo; get isDisposed() { return this.texture.isDisposed && this.fbo.isDisposed; } collectStatistics(stats) { stats.addPlanarClassifier(this.texture.bytesUsed); } constructor(textureAndFbo) { this.texture = textureAndFbo.texture; this.fbo = textureAndFbo.fbo; } [Symbol.dispose]() { (0, core_bentley_1.dispose)(this.texture); (0, core_bentley_1.dispose)(this.fbo); } static createTextureAndFrameBuffer(width, height) { const hTexture = Texture_1.TextureHandle.createForAttachment(width, height, GL_1.GL.Texture.Format.Rgba, GL_1.GL.Texture.DataType.UnsignedByte); if (!hTexture) return undefined; const texture = new Texture_1.Texture({ type: core_common_1.RenderTexture.Type.TileSection, ownership: "external", handle: hTexture, transparency: core_common_1.TextureTransparency.Opaque }); if (!texture) return undefined; const fbo = FrameBuffer_1.FrameBuffer.create([texture.texture]); if (undefined === fbo) return undefined; return { texture, fbo }; } } class MaskFrameBuffer extends SingleTextureFrameBuffer { static create(width, height) { const textureFbo = SingleTextureFrameBuffer.createTextureAndFrameBuffer(width, height); return undefined === textureFbo ? undefined : new MaskFrameBuffer(textureFbo); } draw(cmds, target) { const system = System_1.System.instance; const gl = system.context; system.frameBufferStack.execute(this.fbo, true, false, () => { gl.clearColor(0, 0, 0, 0); gl.clear(GL_1.GL.BufferBit.Color); target.techniques.execute(target, cmds, 19 /* RenderPass.PlanarClassification */); }); } } class CombineTexturesFrameBuffer extends SingleTextureFrameBuffer { _combineGeom; _width; _height; _heightMult; constructor(textureAndFbo, _combineGeom, _width, _height, _heightMult) { super(textureAndFbo); this._combineGeom = _combineGeom; this._width = _width; this._height = _height; this._heightMult = _heightMult; } compose(target) { const system = System_1.System.instance; const gl = system.context; system.context.viewport(0, 0, this._width, this._heightMult * this._height); system.frameBufferStack.execute(this.fbo, true, false, () => { gl.clearColor(0, 0, 0, 0); gl.clear(GL_1.GL.BufferBit.Color); target.techniques.draw((0, ScratchDrawParams_1.getDrawParams)(target, this._combineGeom)); }); } } class ClassifierCombinationBuffer extends CombineTexturesFrameBuffer { static create(width, height, classifierColor, classifierFeature) { const combineGeom = CachedGeometry_1.CombineTexturesGeometry.createGeometry(classifierColor.texture.getHandle(), classifierFeature.texture.getHandle()); if (undefined === combineGeom) return undefined; const textureFbo = SingleTextureFrameBuffer.createTextureAndFrameBuffer(width, 2 * height); return undefined === textureFbo ? undefined : new ClassifierCombinationBuffer(textureFbo, combineGeom, width, height, 2); } } class ClassifierAndMaskCombinationBuffer extends CombineTexturesFrameBuffer { static create(width, height, classifierColor, classifierFeature, mask) { const combineGeom = CachedGeometry_1.Combine3TexturesGeometry.createGeometry(classifierColor.texture.getHandle(), classifierFeature.texture.getHandle(), mask.texture.getHandle()); if (undefined === combineGeom) return undefined; const textureFbo = SingleTextureFrameBuffer.createTextureAndFrameBuffer(width, 3 * height); return undefined === textureFbo ? undefined : new ClassifierAndMaskCombinationBuffer(textureFbo, combineGeom, width, height, 3); } } const scratchPrevRenderState = new RenderState_1.RenderState(); /** @internal */ class PlanarClassifier extends RenderPlanarClassifier_1.RenderPlanarClassifier { _classifierBuffers; _maskBuffer; _classifierCombinedBuffer; _classifierAndMaskCombinedBuffer; _projectionMatrix = core_geometry_1.Matrix4d.createIdentity(); _graphics; _classifierGraphics = []; _maskGraphics = []; _frustum; _width = 0; _height = 0; _baseBatchId = 0; _anyHilited = false; _anyOpaque = false; _anyTranslucent = false; _classifier; _plane = core_geometry_1.Plane3dByOriginAndUnitNormal.create(new core_geometry_1.Point3d(0, 0, 0), new core_geometry_1.Vector3d(0, 0, 1)); // TBD -- Support other planes - default to X-Y for now. _renderState = new RenderState_1.RenderState(); _renderCommands; _branchStack = new BranchStack_1.BranchStack(); _batchState; _planarClipMask; _classifierTreeRef; _planarClipMaskOverrides; _contentMode = PlanarClassifierContent.None; _removeMe; _featureSymbologySource = { onSourceDisposed: new core_bentley_1.BeEvent(), }; ; static _postProjectionMatrix = core_geometry_1.Matrix4d.createRowValues(0, 1, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0, 0, 0, 0, 1); _debugFrustum; _doDebugFrustum = false; _debugFrustumGraphic = undefined; _isClassifyingPointCloud; // we will detect this the first time we draw _bgColor = core_common_1.ColorDef.from(0, 0, 0, 255); constructor(classifier, target) { super(); this._classifier = classifier; const flags = this._renderState.flags; flags.depthMask = flags.blend = flags.depthTest = false; this._batchState = new BatchState_1.BatchState(this._branchStack); this._renderCommands = new RenderCommands_1.RenderCommands(target, this._branchStack, this._batchState); } get textureImageCount() { return this._contentMode; } getParams(params) { params[0] = this.insideDisplay; params[1] = this.outsideDisplay; params[2] = this._contentMode; if (this._planarClipMask?.settings.invert) // If the mask sense is inverted, negate the contentMode to indicate this to the shader. params[2] = -params[2]; params[3] = (this._planarClipMask?.settings.transparency === undefined) ? -1 : this._planarClipMask.settings.transparency; } get hiliteTexture() { return undefined !== this._classifierBuffers ? this._classifierBuffers.textures.hilite : undefined; } get projectionMatrix() { return this._projectionMatrix; } // public get properties(): SpatialClassifier { return this._classifier; } get baseBatchId() { return this._baseBatchId; } get anyHilited() { return this._anyHilited; } get anyOpaque() { return this._anyOpaque; } get anyTranslucent() { return this._anyTranslucent; } get insideDisplay() { return this._classifier ? this._classifier.flags.inside : core_common_1.SpatialClassifierInsideDisplay.Off; } get outsideDisplay() { return this._classifier ? this._classifier.flags.outside : core_common_1.SpatialClassifierOutsideDisplay.On; } get isClassifyingPointCloud() { return true === this._isClassifyingPointCloud; } addGraphic(graphic) { this._graphics.push(graphic); } static create(properties, target) { return new PlanarClassifier(properties, target); } collectStatistics(stats) { if (undefined !== this._classifierBuffers) this._classifierBuffers.textures.collectStatistics(stats); if (undefined !== this._maskBuffer) this._maskBuffer.collectStatistics(stats); if (undefined !== this._classifierCombinedBuffer) this._classifierCombinedBuffer.collectStatistics(stats); if (undefined !== this._classifierAndMaskCombinedBuffer) this._classifierAndMaskCombinedBuffer.collectStatistics(stats); } get isDisposed() { return undefined === this._classifierBuffers; } [Symbol.dispose]() { this._classifierBuffers = (0, core_bentley_1.dispose)(this._classifierBuffers); this._maskBuffer = (0, core_bentley_1.dispose)(this._maskBuffer); this._classifierCombinedBuffer = (0, core_bentley_1.dispose)(this._classifierCombinedBuffer); this._classifierAndMaskCombinedBuffer = (0, core_bentley_1.dispose)(this._classifierAndMaskCombinedBuffer); if (this._removeMe) { this._removeMe(); this._removeMe = undefined; } this._featureSymbologySource.onSourceDisposed.raiseEvent(); } get texture() { switch (this._contentMode) { case PlanarClassifierContent.None: return undefined; case PlanarClassifierContent.ClassifierOnly: return this._classifierCombinedBuffer?.texture; case PlanarClassifierContent.MaskOnly: return this._maskBuffer?.texture; case PlanarClassifierContent.ClassifierAndMask: return this._classifierAndMaskCombinedBuffer?.texture; } } getOrCreateClassifierTexture() { if (undefined === this._classifierBuffers) this._classifierBuffers = ClassifierFrameBuffers.create(this._width, this._height); if (undefined !== this._classifierBuffers && undefined === this._classifierCombinedBuffer) this._classifierCombinedBuffer = ClassifierCombinationBuffer.create(this._width, this._height, this._classifierBuffers.textures.color, this._classifierBuffers.textures.feature); return this._classifierCombinedBuffer?.texture; } pushBatches(batchState, graphics) { graphics.forEach((graphic) => { if (graphic instanceof Graphic_1.Batch) { batchState.push(graphic, true); batchState.pop(); } else if (graphic instanceof Graphic_1.Branch) { this.pushBatches(batchState, graphic.branch.entries); } }); } get sourceTransparency() { return this._classifierTreeRef?.transparency; } pushBatchState(batchState) { this._baseBatchId = batchState.nextBatchId - 1; if (undefined !== this._classifierGraphics) this.pushBatches(batchState, this._classifierGraphics); } setSource(classifierTreeRef, planarClipMask) { this._classifierTreeRef = classifierTreeRef; this._classifier = classifierTreeRef?.activeClassifier; this._planarClipMask = planarClipMask; } collectGraphics(context, target) { this._classifierGraphics.length = this._maskGraphics.length = 0; if (undefined === context.viewingSpace) return; const viewState = context.viewingSpace.view; if (!viewState.isSpatialView()) return; this._doDebugFrustum = context.target.debugControl?.displayMaskFrustum ?? false; const maxTextureSize = System_1.System.instance.maxTexSizeAllow; const requiredHeight = maxTextureSize; const requiredWidth = maxTextureSize; if (requiredWidth !== this._width || requiredHeight !== this._height) this[Symbol.dispose](); this._width = requiredWidth; this._height = requiredHeight; const maskRange = core_geometry_1.Range3d.createNull(); const maskTrees = this._planarClipMask?.getTileTrees(context, target.modelId, maskRange); if (!maskTrees && !this._classifierTreeRef) return; const allTrees = maskTrees ? maskTrees.slice() : new Array(); if (this._classifierTreeRef) allTrees.push(this._classifierTreeRef); const projection = PlanarTextureProjection_1.PlanarTextureProjection.computePlanarTextureProjection(this._plane, context, target, allTrees, viewState, this._width, this._height, maskRange); if (!projection.textureFrustum || !projection.projectionMatrix || !projection.worldToViewMap) return; this._projectionMatrix = projection.projectionMatrix; this._frustum = projection.textureFrustum; this._debugFrustum = projection.debugFrustum; this._planarClipMaskOverrides = this._planarClipMask?.getPlanarClipMaskSymbologyOverrides(context, this._featureSymbologySource); if (!this._planarClipMask?.usingViewportOverrides && this._removeMe) { this._removeMe(); this._removeMe = undefined; } else if (this._planarClipMask?.usingViewportOverrides && !this._removeMe) { this._removeMe = context.viewport.onFeatureOverridesChanged.addListener(() => { this._planarClipMaskOverrides = this._planarClipMask?.getPlanarClipMaskSymbologyOverrides(context, this._featureSymbologySource); context.viewport.requestRedraw(); }); } const drawTree = (treeRef, graphics) => { this._graphics = graphics; const frustumPlanes = this._frustum ? core_common_1.FrustumPlanes.fromFrustum(this._frustum) : core_common_1.FrustumPlanes.createEmpty(); const drawArgs = internal_1.GraphicsCollectorDrawArgs.create(context, this, treeRef, frustumPlanes, projection.worldToViewMap); if (undefined !== drawArgs) treeRef.draw(drawArgs); this._graphics = undefined; }; if (this._classifierTreeRef) drawTree(this._classifierTreeRef, this._classifierGraphics); if (maskTrees) maskTrees.forEach((maskTree) => drawTree(maskTree, this._maskGraphics)); // Shader behaves slightly differently when classifying surfaces vs point clouds. this._isClassifyingPointCloud = target.isPointCloud; if (this._doDebugFrustum) { this._debugFrustumGraphic = (0, core_bentley_1.dispose)(this._debugFrustumGraphic); const builder = context.createSceneGraphicBuilder(); builder.setSymbology(core_common_1.ColorDef.green, core_common_1.ColorDef.green, 2); builder.addFrustum(context.viewingSpace.getFrustum()); builder.setSymbology(core_common_1.ColorDef.red, core_common_1.ColorDef.red, 2); builder.addFrustum(this._debugFrustum); builder.setSymbology(core_common_1.ColorDef.blue, core_common_1.ColorDef.blue, 2); builder.addFrustum(this._frustum); builder.setSymbology(core_common_1.ColorDef.from(0, 200, 0, 222), core_common_1.ColorDef.from(0, 200, 0, 222), 2); builder.addFrustumSides(context.viewingSpace.getFrustum()); builder.setSymbology(core_common_1.ColorDef.from(200, 0, 0, 222), core_common_1.ColorDef.from(200, 0, 0, 222), 2); builder.addFrustumSides(this._debugFrustum); builder.setSymbology(core_common_1.ColorDef.from(0, 0, 200, 222), core_common_1.ColorDef.from(0, 0, 200, 222), 2); builder.addFrustumSides(this._frustum); this._debugFrustumGraphic = builder.finish(); context.outputGraphic(this._debugFrustumGraphic); } } draw(target) { if (undefined === this._frustum) return; this._contentMode = PlanarClassifierContent.None; let combinationBuffer; if (this._classifierGraphics.length === 0) { if (this._maskGraphics.length === 0) { return; } else { if (undefined === this._maskBuffer) { this._maskBuffer = MaskFrameBuffer.create(this._width, this._height); if (undefined === this._maskBuffer) return; } this._contentMode = PlanarClassifierContent.MaskOnly; } } else { if (undefined === this._classifierBuffers) { this._classifierBuffers = ClassifierFrameBuffers.create(this._width, this._height); if (undefined === this._classifierBuffers) return; } if (this._maskGraphics.length === 0) { if (undefined === this._classifierCombinedBuffer) { combinationBuffer = this._classifierCombinedBuffer = ClassifierCombinationBuffer.create(this._width, this._height, this._classifierBuffers.textures.color, this._classifierBuffers.textures.feature); if (undefined === this._classifierCombinedBuffer) return; } this._contentMode = PlanarClassifierContent.ClassifierOnly; combinationBuffer = this._classifierCombinedBuffer; } else { if (undefined === this._maskBuffer) { this._maskBuffer = MaskFrameBuffer.create(this._width, this._height); if (undefined === this._maskBuffer) return; } if (undefined === this._classifierAndMaskCombinedBuffer) { combinationBuffer = this._classifierAndMaskCombinedBuffer = ClassifierAndMaskCombinationBuffer.create(this._width, this._height, this._classifierBuffers.textures.color, this._classifierBuffers.textures.feature, this._maskBuffer.texture); if (undefined === this._classifierAndMaskCombinedBuffer) return; } combinationBuffer = this._classifierAndMaskCombinedBuffer; this._contentMode = PlanarClassifierContent.ClassifierAndMask; } } // Temporarily override the Target's state. const system = System_1.System.instance; const maskViewFlags = { renderMode: core_common_1.RenderMode.SmoothShade, wiremesh: false, transparency: !this.isClassifyingPointCloud, // point clouds don't support transparency. textures: false, lighting: false, shadows: false, monochrome: false, materials: false, ambientOcclusion: false, visibleEdges: false, hiddenEdges: false, }; const prevState = system.currentRenderState.clone(scratchPrevRenderState); system.context.viewport(0, 0, this._width, this._height); const vf = target.currentViewFlags.copy(this._classifierTreeRef ? this._classifierTreeRef.viewFlags : maskViewFlags); system.applyRenderState(this._renderState); const prevPlan = target.plan; const prevOverrides = target.currentFeatureSymbologyOverrides; target.uniforms.style.changeBackgroundColor(this._bgColor); // Avoid white on white reversal. Will be reset in changeRenderPlan below. target.changeFrustum(this._frustum, this._frustum.getFraction(), true); this._anyTranslucent = false; const prevProjMatrix = target.uniforms.frustum.projectionMatrix; target.uniforms.frustum.changeProjectionMatrix(PlanarClassifier._postProjectionMatrix.multiplyMatrixMatrix(prevProjMatrix)); target.uniforms.branch.changeRenderPlan(vf, target.plan.is3d, target.plan.hline); const addCmds = (oldCmds, newCmds) => { if (undefined === newCmds) return oldCmds; if (newCmds.length > 50000) { // This method is slower for smaller array sizes, but when the size of newCmds gets larger it's performance is ok. return oldCmds.concat(newCmds); } else { // This method runs faster, but gets a stack overflow when the size of newCmds is too large. oldCmds.push(...newCmds); return oldCmds; } }; const renderCommands = this._renderCommands; const getDrawCommands = (graphics) => { this._batchState.reset(); renderCommands.reset(target, this._branchStack, this._batchState); renderCommands.collectGraphicsForPlanarProjection(graphics); // Draw the classifiers into our attachments. // When using Display.ElementColor, the color and transparency come from the classifier geometry. Therefore we may need to draw the classified geometry // in a different pass - or both passes - depending on the transparency of the classifiers. // NB: "Outside" geometry by definition cannot take color/transparency from element... let cmds = renderCommands.getCommands(3 /* RenderPass.OpaquePlanar */); // NB: We don't strictly require the classifier geometry to be planar, and sometimes (e.g., "planar" polyface/bspsurf) we do not detect planarity. cmds = addCmds(cmds, renderCommands.getCommands(5 /* RenderPass.OpaqueGeneral */)); cmds = addCmds(cmds, renderCommands.getCommands(2 /* RenderPass.OpaqueLinear */)); this._anyOpaque = cmds.length > 0; const transCmds = renderCommands.getCommands(8 /* RenderPass.Translucent */); if (transCmds.length > 0) { cmds = addCmds(cmds, renderCommands.getCommands(8 /* RenderPass.Translucent */)); this._anyTranslucent = true; } return cmds; }; if (this._classifierGraphics.length > 0 && this._classifierBuffers) { this._classifierBuffers.draw(getDrawCommands(this._classifierGraphics), target); // Draw any hilited classifiers. const hiliteCommands = renderCommands.getCommands(10 /* RenderPass.Hilite */); this._anyHilited = 0 !== hiliteCommands.length; if (this._anyHilited) this._classifierBuffers.drawHilite(hiliteCommands, target); } if (this._maskGraphics.length > 0 && this._maskBuffer) { if (this._planarClipMaskOverrides) target.overrideFeatureSymbology(this._planarClipMaskOverrides); if (this._planarClipMask && this._planarClipMask.settings.transparency !== undefined && this._planarClipMask.settings.transparency > 0.0) this._anyTranslucent = true; this._maskBuffer.draw(getDrawCommands(this._maskGraphics), target); } if (combinationBuffer) combinationBuffer.compose(target); this._batchState.reset(); target.changeRenderPlan(prevPlan); target.overrideFeatureSymbology(prevOverrides); system.applyRenderState(prevState); system.context.viewport(0, 0, target.viewRect.width, target.viewRect.height); } } exports.PlanarClassifier = PlanarClassifier; //# sourceMappingURL=PlanarClassifier.js.map