src/viewer/scene/PerformanceModel/lib/layers/linesInstancing/LinesInstancingLayer.js
import {WEBGL_INFO} from "../../../../webglInfo.js";
import {ENTITY_FLAGS} from '../../ENTITY_FLAGS.js';
import {RENDER_PASSES} from '../../RENDER_PASSES.js';
import {math} from "../../../../math/math.js";
import {RenderState} from "../../../../webgl/RenderState.js";
import {ArrayBuf} from "../../../../webgl/ArrayBuf.js";
import {geometryCompressionUtils} from "../../../../math/geometryCompressionUtils.js";
import {getInstancingRenderers} from "./LinesInstancingRenderers.js";
import {quantizePositions} from "../../compression.js";
const bigIndicesSupported = WEBGL_INFO.SUPPORTED_EXTENSIONS["OES_element_index_uint"];
const tempUint8Vec4 = new Uint8Array(4);
const tempVec4a = math.vec4([0, 0, 0, 1]);
const tempVec4b = math.vec4([0, 0, 0, 1]);
const tempVec4c = math.vec4([0, 0, 0, 1]);
const tempVec3fa = new Float32Array(3);
/**
* @private
*/
class LinesInstancingLayer {
/**
* @param model
* @param cfg
* @param cfg.layerIndex
* @param cfg.positions Flat float Local-space positions array.
* @param cfg.indices Flat int indices array.
* @param cfg.origin
*/
constructor(model, cfg) {
/**
* State sorting key.
* @type {string}
*/
this.sortId = "LinesInstancingLayer";
/**
* Index of this InstancingLayer in PerformanceModel#_layerList
* @type {Number}
*/
this.layerIndex = cfg.layerIndex;
this._linesInstancingRenderers = getInstancingRenderers(model.scene);
this.model = model;
this._aabb = math.collapseAABB3();
const gl = model.scene.canvas.gl;
const stateCfg = {
positionsDecodeMatrix: math.mat4(),
numInstances: 0,
obb: math.OBB3(),
origin: null
};
const preCompressed = (!!cfg.positionsDecodeMatrix);
if (cfg.positions) {
if (preCompressed) {
let normalized = false;
stateCfg.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, cfg.positions, cfg.positions.length, 3, gl.STATIC_DRAW, normalized);
stateCfg.positionsDecodeMatrix.set(cfg.positionsDecodeMatrix);
let localAABB = math.collapseAABB3();
math.expandAABB3Points3(localAABB, cfg.positions);
geometryCompressionUtils.decompressAABB(localAABB, stateCfg.positionsDecodeMatrix);
math.AABB3ToOBB3(localAABB, stateCfg.obb);
} else {
let lenPositions = cfg.positions.length;
let localAABB = math.collapseAABB3();
math.expandAABB3Points3(localAABB, cfg.positions);
math.AABB3ToOBB3(localAABB, stateCfg.obb);
const quantizedPositions = quantizePositions(cfg.positions, localAABB, stateCfg.positionsDecodeMatrix);
let normalized = false;
stateCfg.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, quantizedPositions, lenPositions, 3, gl.STATIC_DRAW, normalized);
}
}
if (cfg.indices) {
stateCfg.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, bigIndicesSupported ? new Uint32Array(cfg.indices) : new Uint16Array(cfg.indices), cfg.indices.length, 1, gl.STATIC_DRAW);
}
this._state = new RenderState(stateCfg);
// These counts are used to avoid unnecessary render passes
this._numPortions = 0;
this._numVisibleLayerPortions = 0;
this._numTransparentLayerPortions = 0;
this._numXRayedLayerPortions = 0;
this._numHighlightedLayerPortions = 0;
this._numSelectedLayerPortions = 0;
this._numClippableLayerPortions = 0;
this._numEdgesLayerPortions = 0;
this._numPickableLayerPortions = 0;
this._numCulledLayerPortions = 0;
/** @private */
this.numIndices = (cfg.indices) ? cfg.indices.length / 3 : 0;
// Vertex arrays
this._colors = [];
this._offsets = [];
// Modeling matrix per instance, array for each column
this._modelMatrixCol0 = [];
this._modelMatrixCol1 = [];
this._modelMatrixCol2 = [];
this._portions = [];
if (cfg.origin) {
this._state.origin = math.vec3(cfg.origin);
}
this._finalized = false;
/**
* The axis-aligned World-space boundary of this InstancingLayer's positions.
* @type {*|Float64Array}
*/
this.aabb = math.collapseAABB3();
}
/**
* Creates a new portion within this InstancingLayer, returns the new portion ID.
*
* The portion will instance this InstancingLayer's geometry.
*
* Gives the portion the specified color and matrix.
*
* @param cfg Portion params
* @param cfg.color Color [0..255,0..255,0..255]
* @param cfg.opacity Opacity [0..255].
* @param cfg.meshMatrix Flat float 4x4 matrix.
* @param [cfg.worldMatrix] Flat float 4x4 matrix.
* @param cfg.aabb Flat float AABB.
* @returns {number} Portion ID.
*/
createPortion(cfg) {
const color = cfg.color;
const opacity = cfg.opacity;
const meshMatrix = cfg.meshMatrix;
const worldMatrix = cfg.worldMatrix;
const worldAABB = cfg.aabb;
if (this._finalized) {
throw "Already finalized";
}
// TODO: find AABB for portion by transforming the geometry local AABB by the given meshMatrix?
const r = color[0]; // Color is pre-quantized by PerformanceModel
const g = color[1];
const b = color[2];
const a = color[3];
this._colors.push(r);
this._colors.push(g);
this._colors.push(b);
this._colors.push(opacity);
if (this.model.scene.entityOffsetsEnabled) {
this._offsets.push(0);
this._offsets.push(0);
this._offsets.push(0);
}
this._modelMatrixCol0.push(meshMatrix[0]);
this._modelMatrixCol0.push(meshMatrix[4]);
this._modelMatrixCol0.push(meshMatrix[8]);
this._modelMatrixCol0.push(meshMatrix[12]);
this._modelMatrixCol1.push(meshMatrix[1]);
this._modelMatrixCol1.push(meshMatrix[5]);
this._modelMatrixCol1.push(meshMatrix[9]);
this._modelMatrixCol1.push(meshMatrix[13]);
this._modelMatrixCol2.push(meshMatrix[2]);
this._modelMatrixCol2.push(meshMatrix[6]);
this._modelMatrixCol2.push(meshMatrix[10]);
this._modelMatrixCol2.push(meshMatrix[14]);
// Expand AABB
math.collapseAABB3(worldAABB);
const obb = this._state.obb;
const lenPositions = obb.length;
for (let i = 0; i < lenPositions; i += 4) {
tempVec4a[0] = obb[i + 0];
tempVec4a[1] = obb[i + 1];
tempVec4a[2] = obb[i + 2];
math.transformPoint4(meshMatrix, tempVec4a, tempVec4b);
if (worldMatrix) {
math.transformPoint4(worldMatrix, tempVec4b, tempVec4c);
math.expandAABB3Point3(worldAABB, tempVec4c);
} else {
math.expandAABB3Point3(worldAABB, tempVec4b);
}
}
if (this._state.origin) {
const origin = this._state.origin;
worldAABB[0] += origin[0];
worldAABB[1] += origin[1];
worldAABB[2] += origin[2];
worldAABB[3] += origin[0];
worldAABB[4] += origin[1];
worldAABB[5] += origin[2];
}
math.expandAABB3(this.aabb, worldAABB);
this._state.numInstances++;
const portionId = this._portions.length;
this._portions.push({});
this._numPortions++;
this.model.numPortions++;
return portionId;
}
finalize() {
if (this._finalized) {
throw "Already finalized";
}
const gl = this.model.scene.canvas.gl;
const colorsLength = this._colors.length;
const flagsLength = colorsLength;
if (colorsLength > 0) {
let notNormalized = false;
this._state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Uint8Array(this._colors), this._colors.length, 4, gl.DYNAMIC_DRAW, notNormalized);
this._colors = []; // Release memory
}
if (flagsLength > 0) {
// Because we only build flags arrays here,
// get their length from the colors array
let notNormalized = false;
let normalized = true;
this._state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Uint8Array(flagsLength), flagsLength, 4, gl.DYNAMIC_DRAW, notNormalized);
this._state.flags2Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Uint8Array(flagsLength), flagsLength, 4, gl.DYNAMIC_DRAW, normalized);
}
if (this.model.scene.entityOffsetsEnabled) {
if (this._offsets.length > 0) {
const notNormalized = false;
this._state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._offsets), this._offsets.length, 3, gl.DYNAMIC_DRAW, notNormalized);
this._offsets = []; // Release memory
}
}
if (this._modelMatrixCol0.length > 0) {
const normalized = false;
this._state.modelMatrixCol0Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol0), this._modelMatrixCol0.length, 4, gl.STATIC_DRAW, normalized);
this._state.modelMatrixCol1Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol1), this._modelMatrixCol1.length, 4, gl.STATIC_DRAW, normalized);
this._state.modelMatrixCol2Buf = new ArrayBuf(gl, gl.ARRAY_BUFFER, new Float32Array(this._modelMatrixCol2), this._modelMatrixCol2.length, 4, gl.STATIC_DRAW, normalized);
this._modelMatrixCol0 = [];
this._modelMatrixCol1 = [];
this._modelMatrixCol2 = [];
}
this._finalized = true;
}
// The following setters are called by PerformanceMesh, in turn called by PerformanceNode, only after the layer is finalized.
// It's important that these are called after finalize() in order to maintain integrity of counts like _numVisibleLayerPortions etc.
initFlags(portionId, flags, meshTransparent) {
if (flags & ENTITY_FLAGS.VISIBLE) {
this._numVisibleLayerPortions++;
this.model.numVisibleLayerPortions++;
}
if (flags & ENTITY_FLAGS.HIGHLIGHTED) {
this._numHighlightedLayerPortions++;
this.model.numHighlightedLayerPortions++;
}
if (flags & ENTITY_FLAGS.XRAYED) {
this._numXRayedLayerPortions++;
this.model.numXRayedLayerPortions++;
}
if (flags & ENTITY_FLAGS.SELECTED) {
this._numSelectedLayerPortions++;
this.model.numSelectedLayerPortions++;
}
if (flags & ENTITY_FLAGS.CLIPPABLE) {
this._numClippableLayerPortions++;
this.model.numClippableLayerPortions++;
}
if (flags & ENTITY_FLAGS.EDGES) {
this._numEdgesLayerPortions++;
this.model.numEdgesLayerPortions++;
}
if (flags & ENTITY_FLAGS.PICKABLE) {
this._numPickableLayerPortions++;
this.model.numPickableLayerPortions++;
}
if (flags & ENTITY_FLAGS.CULLED) {
this._numCulledLayerPortions++;
this.model.numCulledLayerPortions++;
}
if (meshTransparent) {
this._numTransparentLayerPortions++;
this.model.numTransparentLayerPortions++;
}
this._setFlags(portionId, flags, meshTransparent);
this._setFlags2(portionId, flags);
}
setVisible(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.VISIBLE) {
this._numVisibleLayerPortions++;
this.model.numVisibleLayerPortions++;
} else {
this._numVisibleLayerPortions--;
this.model.numVisibleLayerPortions--;
}
this._setFlags(portionId, flags, meshTransparent);
}
setHighlighted(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.HIGHLIGHTED) {
this._numHighlightedLayerPortions++;
this.model.numHighlightedLayerPortions++;
} else {
this._numHighlightedLayerPortions--;
this.model.numHighlightedLayerPortions--;
}
this._setFlags(portionId, flags, meshTransparent);
}
setXRayed(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.XRAYED) {
this._numXRayedLayerPortions++;
this.model.numXRayedLayerPortions++;
} else {
this._numXRayedLayerPortions--;
this.model.numXRayedLayerPortions--;
}
this._setFlags(portionId, flags, meshTransparent);
}
setSelected(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.SELECTED) {
this._numSelectedLayerPortions++;
this.model.numSelectedLayerPortions++;
} else {
this._numSelectedLayerPortions--;
this.model.numSelectedLayerPortions--;
}
this._setFlags(portionId, flags, meshTransparent);
}
setEdges(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.EDGES) {
this._numEdgesLayerPortions++;
this.model.numEdgesLayerPortions++;
} else {
this._numEdgesLayerPortions--;
this.model.numEdgesLayerPortions--;
}
this._setFlags(portionId, flags, meshTransparent);
}
setClippable(portionId, flags) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.CLIPPABLE) {
this._numClippableLayerPortions++;
this.model.numClippableLayerPortions++;
} else {
this._numClippableLayerPortions--;
this.model.numClippableLayerPortions--;
}
this._setFlags2(portionId, flags);
}
setCollidable(portionId, flags) {
if (!this._finalized) {
throw "Not finalized";
}
}
setPickable(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.PICKABLE) {
this._numPickableLayerPortions++;
this.model.numPickableLayerPortions++;
} else {
this._numPickableLayerPortions--;
this.model.numPickableLayerPortions--;
}
this._setFlags2(portionId, flags, meshTransparent);
}
setCulled(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
if (flags & ENTITY_FLAGS.CULLED) {
this._numCulledLayerPortions++;
this.model.numCulledLayerPortions++;
} else {
this._numCulledLayerPortions--;
this.model.numCulledLayerPortions--;
}
this._setFlags(portionId, flags, meshTransparent);
}
setColor(portionId, color) { // RGBA color is normalized as ints
if (!this._finalized) {
throw "Not finalized";
}
tempUint8Vec4[0] = color[0];
tempUint8Vec4[1] = color[1];
tempUint8Vec4[2] = color[2];
tempUint8Vec4[3] = color[3];
this._state.colorsBuf.setData(tempUint8Vec4, portionId * 4, 4);
}
setTransparent(portionId, flags, transparent) {
if (transparent) {
this._numTransparentLayerPortions++;
this.model.numTransparentLayerPortions++;
} else {
this._numTransparentLayerPortions--;
this.model.numTransparentLayerPortions--;
}
this._setFlags(portionId, flags, transparent);
}
_setFlags(portionId, flags, meshTransparent) {
if (!this._finalized) {
throw "Not finalized";
}
const visible = !!(flags & ENTITY_FLAGS.VISIBLE);
const xrayed = !!(flags & ENTITY_FLAGS.XRAYED);
const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED);
const selected = !!(flags & ENTITY_FLAGS.SELECTED);
const edges = !!(flags & ENTITY_FLAGS.EDGES);
const pickable = !!(flags & ENTITY_FLAGS.PICKABLE);
const culled = !!(flags & ENTITY_FLAGS.CULLED);
// Normal fill
let f0;
if (!visible || culled || xrayed || (highlighted && !this.model.scene.highlightMaterial.glowThrough) || (selected && !this.model.scene.selectedMaterial.glowThrough)) { // Highlight & select are layered on top of color - not mutually exclusive
f0 = RENDER_PASSES.NOT_RENDERED;
} else {
if (meshTransparent) {
f0 = RENDER_PASSES.COLOR_TRANSPARENT;
} else {
f0 = RENDER_PASSES.COLOR_OPAQUE;
}
}
// Emphasis fill
let f1;
if (!visible || culled) {
f1 = RENDER_PASSES.NOT_RENDERED;
} else if (selected) {
f1 = RENDER_PASSES.SILHOUETTE_SELECTED;
} else if (highlighted) {
f1 = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED;
} else if (xrayed) {
f1 = RENDER_PASSES.SILHOUETTE_XRAYED;
} else {
f1 = RENDER_PASSES.NOT_RENDERED;
}
// Edges
let f2 = 0;
if (!visible || culled) {
f2 = RENDER_PASSES.NOT_RENDERED;
} else if (selected) {
f2 = RENDER_PASSES.EDGES_SELECTED;
} else if (highlighted) {
f2 = RENDER_PASSES.EDGES_HIGHLIGHTED;
} else if (xrayed) {
f2 = RENDER_PASSES.EDGES_XRAYED;
} else if (edges) {
if (meshTransparent) {
f2 = RENDER_PASSES.EDGES_COLOR_TRANSPARENT;
} else {
f2 = RENDER_PASSES.EDGES_COLOR_OPAQUE;
}
} else {
f2 = RENDER_PASSES.NOT_RENDERED;
}
// Pick
let f3 = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED;
tempUint8Vec4[0] = f0; // x - normal fill
tempUint8Vec4[1] = f1; // y - emphasis fill
tempUint8Vec4[2] = f2; // z - edges
tempUint8Vec4[3] = f3; // w - pick
this._state.flagsBuf.setData(tempUint8Vec4, portionId * 4, 4);
}
_setFlags2(portionId, flags) {
if (!this._finalized) {
throw "Not finalized";
}
const clippable = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 255 : 0;
tempUint8Vec4[0] = clippable;
this._state.flags2Buf.setData(tempUint8Vec4, portionId * 4, 4);
}
setOffset(portionId, offset) {
if (!this._finalized) {
throw "Not finalized";
}
if (!this.model.scene.entityOffsetsEnabled) {
this.model.error("Entity#offset not enabled for this Viewer"); // See Viewer entityOffsetsEnabled
return;
}
tempVec3fa[0] = offset[0];
tempVec3fa[1] = offset[1];
tempVec3fa[2] = offset[2];
this._state.offsetsBuf.setData(tempVec3fa, portionId * 3, 3);
}
// ---------------------- NORMAL RENDERING -----------------------------------
drawColorOpaque(renderFlags, frameCtx) {
if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) {
return;
}
if (this._linesInstancingRenderers.colorRenderer) {
this._linesInstancingRenderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_OPAQUE);
}
}
drawColorTransparent(renderFlags, frameCtx) {
if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === 0 || this._numXRayedLayerPortions === this._numPortions) {
return;
}
if (this._linesInstancingRenderers.colorRenderer) {
this._linesInstancingRenderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT);
}
}
// ---------------------- RENDERING SAO POST EFFECT TARGETS --------------
drawDepth(renderFlags, frameCtx) {
}
drawNormals(renderFlags, frameCtx) {
}
// ---------------------- EMPHASIS RENDERING -----------------------------------
drawSilhouetteXRayed(renderFlags, frameCtx) {
if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) {
return;
}
if (this._linesInstancingRenderers.silhouetteRenderer) {
this._linesInstancingRenderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_XRAYED);
}
}
drawSilhouetteHighlighted(renderFlags, frameCtx) {
if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numHighlightedLayerPortions === 0) {
return;
}
if (this._linesInstancingRenderers.silhouetteRenderer) {
this._linesInstancingRenderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_HIGHLIGHTED);
}
}
drawSilhouetteSelected(renderFlags, frameCtx) {
if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numSelectedLayerPortions === 0) {
return;
}
if (this._linesInstancingRenderers.silhouetteRenderer) {
this._linesInstancingRenderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED);
}
}
// ---------------------- EDGES RENDERING -----------------------------------
drawEdgesColorOpaque(renderFlags, frameCtx) {
}
drawEdgesColorTransparent(renderFlags, frameCtx) {
}
drawEdgesXRayed(renderFlags, frameCtx) {
}
drawEdgesHighlighted(renderFlags, frameCtx) {
}
drawEdgesSelected(renderFlags, frameCtx) {
}
// ---------------------- OCCLUSION CULL RENDERING -----------------------------------
drawOcclusion(renderFlags, frameCtx) {
}
// ---------------------- SHADOW BUFFER RENDERING -----------------------------------
drawShadow(renderFlags, frameCtx) {
}
//---- PICKING ----------------------------------------------------------------------------------------------------
drawPickMesh(renderFlags, frameCtx) {
}
drawPickDepths(renderFlags, frameCtx) {
}
drawPickNormals(renderFlags, frameCtx) {
}
destroy() {
const state = this._state;
if (state.positionsBuf) {
state.positionsBuf.destroy();
state.positionsBuf = null;
}
if (state.colorsBuf) {
state.colorsBuf.destroy();
state.colorsBuf = null;
}
if (state.flagsBuf) {
state.flagsBuf.destroy();
state.flagsBuf = null;
}
if (state.flags2Buf) {
state.flags2Buf.destroy();
state.flags2Buf = null;
}
if (state.offsetsBuf) {
state.offsetsBuf.destroy();
state.offsetsBuf = null;
}
if (state.modelMatrixCol0Buf) {
state.modelMatrixCol0Buf.destroy();
state.modelMatrixCol0Buf = null;
}
if (state.modelMatrixCol1Buf) {
state.modelMatrixCol1Buf.destroy();
state.modelMatrixCol1Buf = null;
}
if (state.modelMatrixCol2Buf) {
state.modelMatrixCol2Buf.destroy();
state.modelMatrixCol2Buf = null;
}
if (state.indicesBuf) {
state.indicesBuf.destroy();
state.indicessBuf = null;
}
state.destroy();
}
}
export {LinesInstancingLayer};