@itwin/core-frontend
Version:
iTwin.js frontend components
243 lines • 10.1 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.
*--------------------------------------------------------------------------------------------*/
/** @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