UNPKG

@itwin/core-frontend

Version:
243 lines • 10.1 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. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module WebGL */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ClipStack = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const IModelApp_1 = require("../../../IModelApp"); const FloatRGBA_1 = require("./FloatRGBA"); const Texture_1 = require("./Texture"); const ClipVolume_1 = require("./ClipVolume"); const GL_1 = require("./GL"); const emptyClipData = new Uint8Array(0); const emptyClip = { numRows: 0, getData: () => emptyClipData, }; const scratchRangeCorners = [ new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), new core_geometry_1.Point3d(), ]; function getRangeCorners(r) { return r.corners(scratchRangeCorners); } /** Maintains a stack of ClipVolumes. The volumes nest such that the stack represents the intersection of all the volumes. * The bottom of the stack represents the view's clip volume and is always present even if the view has no clip. * It also maintains the inside/outside clip colors, where the alpha component is 1 if the color should be applied and 0 if not. * @internal */ class ClipStack { /** Encoded data for all clips on the stack, update when the stack or the transform changes. */ _cpuBuffer; /** A view of the encoded buffer in the format expected by the GPU. */ _gpuBuffer; _texture; /** The maximum number of rows we have ever required. Determines the texture height. Grows as needed, reallocating a larger texture, but never shrinks. */ _numTotalRows; /** The number of rows in the texture actually required to encode the current contents of the stack. */ _numRowsInUse; /** The first entry always represents the view clip. The rest are pushed and popped with GraphicBranches. */ _stack = [emptyClip]; /** True if we need to recompute the texture. */ _isStackDirty = false; /** Obtain the transform to be applied to the clips - i.e., the view matrix. */ _getTransform; /** If this returns false, the clip at the bottom of the stack is ignored. */ _wantViewClip; /** If alpha is 1 then geometry inside the clip will be drawn in this color. */ _insideColor = FloatRGBA_1.FloatRgba.from(0, 0, 0, 0); /** If alpha is 1 then geometry outside the clip will be drawn in this color. */ _outsideColor = FloatRGBA_1.FloatRgba.from(0, 0, 0, 0); /** For detecting whether the transform changed from one invocation of setViewClip to the next. */ _prevTransform = core_geometry_1.Transform.createZero(); /** True if we want to colorize geometry intersecting clip planes */ _colorizeIntersection = false; /** The style to colorize the geometry intersecting clip planes */ _intersectionStyle = FloatRGBA_1.FloatRgba.from(0, 0, 0, 0); constructor(getTransform, wantViewClip) { this._getTransform = getTransform; this._wantViewClip = wantViewClip; this._numTotalRows = this._numRowsInUse = 0; this._cpuBuffer = new Uint8Array(this._numTotalRows); this._gpuBuffer = this.allocateGpuBuffer(); } get insideColor() { return this._insideColor; } get outsideColor() { return this._outsideColor; } get hasOutsideColor() { return this.outsideColor.alpha !== 0; } get colorizeIntersection() { return this._colorizeIntersection; } set colorizeIntersection(b) { this._colorizeIntersection = b; } get intersectionStyle() { return this._intersectionStyle; } get bytesUsed() { return this._texture ? this._texture.bytesUsed : 0; } setViewClip(clip, style) { (0, core_bentley_1.assert)(this._stack.length === 1); this.updateColor(style.insideColor, this._insideColor); this.updateColor(style.outsideColor, this._outsideColor); this.updateIntersectionStyle(style.colorizeIntersection, style.intersectionStyle, this._intersectionStyle); const transform = this._getTransform(); if (!transform.isAlmostEqual(this._prevTransform)) { transform.clone(this._prevTransform); this._isStackDirty = true; } const cur = this._stack[0]; if (cur === emptyClip) { if (!clip) return; // no change. } else if (!clip) { this._stack[0] = emptyClip; this._numRowsInUse = 0; this._isStackDirty = true; return; } else { (0, core_bentley_1.assert)(cur instanceof ClipVolume_1.ClipVolume); if (cur.clipVector === clip) { // We assume that the active view's ClipVector is never mutated in place, so if we are given the same ClipVector, we expect our RenderClipVolume to match it. return; } } // ClipVector has changed. const newClip = IModelApp_1.IModelApp.renderSystem.createClipVolume(clip); if (!newClip) { this._isStackDirty = this._stack[0] !== emptyClip; this._stack[0] = emptyClip; this._numRowsInUse = 0; } else { this.pop(); this.push(newClip); } } push(clip) { (0, core_bentley_1.assert)(clip instanceof ClipVolume_1.ClipVolume); this._stack.push(clip); this._numRowsInUse += clip.numRows; this._numTotalRows = Math.max(this._numRowsInUse, this._numTotalRows); this._isStackDirty = true; } pop() { (0, core_bentley_1.assert)(this._stack.length > 0); const clip = this._stack.pop(); this._numRowsInUse -= (clip ? clip.numRows : 0); } get hasClip() { return this.startIndex < this.endIndex; } get hasViewClip() { return emptyClip !== this._stack[0] && this._wantViewClip(); } get startIndex() { (0, core_bentley_1.assert)(this._stack.length > 0); return this._wantViewClip() ? 0 : this._stack[0].numRows; } get endIndex() { return this._numRowsInUse; } get textureHeight() { return this._numTotalRows; } get texture() { this.updateTexture(); return this._texture; } isRangeClipped(range, transform) { if (this.hasOutsideColor || !this.hasClip) return false; range = transform.multiplyRange(range, range); const corners = getRangeCorners(range); const startIndex = this._wantViewClip() && emptyClip !== this._stack[0] ? 0 : 1; for (let i = startIndex; i < this._stack.length; i++) { const clip = this._stack[i]; (0, core_bentley_1.assert)(clip instanceof ClipVolume_1.ClipVolume); if (core_geometry_1.ClipPlaneContainment.StronglyOutside === clip.clipVector.classifyPointContainment(corners)) return true; } return false; } /** Exposed strictly for tests. */ get clips() { return this._stack; } /** Exposed strictly for tests. */ static get emptyViewClip() { return emptyClip; } updateTexture() { if (this._numTotalRows > 0 && (!this._texture || this._texture.height < this._numTotalRows)) { // We need to resize the texture. (0, core_bentley_1.assert)(this._isStackDirty); this._isStackDirty = true; this._texture = (0, core_bentley_1.dispose)(this._texture); this._cpuBuffer = new Uint8Array(this._numTotalRows * 4 * 4); this._gpuBuffer = this.allocateGpuBuffer(); } if (this._isStackDirty) { this._isStackDirty = false; this.recomputeTexture(); } } recomputeTexture() { // Copy each clip's data to the buffer, recording whether the buffer's contents actually changed. let bufferDirty = false; const transform = this._getTransform(); let bufferIndex = 0; for (const clip of this._stack) { const data = clip.getData(transform); for (let i = 0; i < data.byteLength; i++) { const byte = data[i]; bufferDirty = bufferDirty || byte !== this._cpuBuffer[bufferIndex]; this._cpuBuffer[bufferIndex++] = byte; } } // If the contents have changed, upload the new texture data to the GPU. if (bufferDirty) { this.uploadTexture(); } } uploadTexture() { if (this._texture) this._texture.replaceTextureData(this._gpuBuffer); else this._texture = Texture_1.Texture2DHandle.createForData(1, this._numTotalRows, this._gpuBuffer, false, GL_1.GL.Texture.WrapMode.ClampToEdge, GL_1.GL.Texture.Format.Rgba); (0, core_bentley_1.assert)((this._texture?.height ?? -1) === this._numTotalRows); } allocateGpuBuffer() { return new Float32Array(this._cpuBuffer.buffer); } updateColor(rgb, rgba) { rgba.alpha = undefined !== rgb ? 1 : 0; if (rgb) rgba.setRgbColor(rgb); } updateIntersectionStyle(colorizeIntersection, style, _thisStyle) { this._colorizeIntersection = colorizeIntersection === true ? true : false; if (style !== undefined) { if (style.color !== undefined) _thisStyle.setRgbColor(style.color); if (style.width !== undefined) _thisStyle.alpha = style.width; } } } exports.ClipStack = ClipStack; //# sourceMappingURL=ClipStack.js.map