UNPKG

@vrame/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

705 lines (614 loc) 24.9 kB
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 {getRenderers} from "./renderers/VBOBatchingLinesRenderers.js"; import {VBOBatchingLinesBuffer} from "./lib/VBOBatchingLinesBuffer.js"; import {quantizePositions} from "../../../compression.js"; /** * @private */ export class VBOBatchingLinesLayer { /** * @param model * @param cfg * @param cfg.layerIndex * @param cfg.positionsDecodeMatrix * @param cfg.maxGeometryBatchSize * @param cfg.origin * @param cfg.scratchMemory */ constructor(cfg) { // console.info("Creating VBOBatchingLinesLayer"); /** * Index of this LinesBatchingLayer in {@link VBOSceneModel#_layerList}. * @type {Number} */ this.layerIndex = cfg.layerIndex; this._renderers = getRenderers(cfg.model.scene); this.model = cfg.model; this._buffer = new VBOBatchingLinesBuffer(cfg.maxGeometryBatchSize); this._scratchMemory = cfg.scratchMemory; this._state = new RenderState({ positionsBuf: null, offsetsBuf: null, colorsBuf: null, flagsBuf: null, indicesBuf: null, positionsDecodeMatrix: math.mat4(), origin: null }); // These counts are used to avoid unnecessary render passes this._numPortions = 0; this._numVisibleLayerPortions = 0; this._numTransparentLayerPortions = 0; this._numXRayedLayerPortions = 0; this._numSelectedLayerPortions = 0; this._numHighlightedLayerPortions = 0; this._numClippableLayerPortions = 0; this._numEdgesLayerPortions = 0; this._numPickableLayerPortions = 0; this._numCulledLayerPortions = 0; this._modelAABB = math.collapseAABB3(); // Model-space AABB this._portions = []; this._meshes = []; this._numVerts = 0; this._aabb = math.collapseAABB3(); this.aabbDirty = true; this._finalized = false; if (cfg.positionsDecodeMatrix) { this._state.positionsDecodeMatrix.set(cfg.positionsDecodeMatrix); this._preCompressedPositionsExpected = true; } else { this._preCompressedPositionsExpected = false; } if (cfg.origin) { this._state.origin = math.vec3(cfg.origin); } /** * The type of primitives in this layer. */ this.primitive = cfg.primitive; } get aabb() { if (this.aabbDirty) { math.collapseAABB3(this._aabb); for (let i = 0, len = this._meshes.length; i < len; i++) { math.expandAABB3(this._aabb, this._meshes[i].aabb); } this.aabbDirty = false; } return this._aabb; } /** * Tests if there is room for another portion in this LinesBatchingLayer. * * @param lenPositions Number of positions we'd like to create in the portion. * @param lenIndices Number of indices we'd like to create in this portion. * @returns {Boolean} True if OK to create another portion. */ canCreatePortion(lenPositions, lenIndices) { if (this._finalized) { throw "Already finalized"; } return ((this._buffer.positions.length + lenPositions) < (this._buffer.maxVerts * 3) && (this._buffer.indices.length + lenIndices) < (this._buffer.maxIndices)); } /** * Creates a new portion within this LinesBatchingLayer, returns the new portion ID. * * Gives the portion the specified geometry, color and matrix. * * @param mesh The SceneModelMesh that owns the portion * @param cfg.positions Flat float Local-space positions array. * @param cfg.positionsCompressed Flat quantized positions array - decompressed with TrianglesBatchingLayer positionsDecodeMatrix * @param cfg.indices Flat int indices array. * @param cfg.color Quantized RGB color [0..255,0..255,0..255,0..255] * @param cfg.opacity Opacity [0..255] * @param [cfg.meshMatrix] Flat float 4x4 matrix * @param cfg.aabb Flat float AABB World-space AABB * @param cfg.pickColor Quantized pick color * @returns {number} Portion ID */ createPortion(mesh, cfg) { if (this._finalized) { throw "Already finalized"; } const positions = cfg.positions; const positionsCompressed = cfg.positionsCompressed; const indices = cfg.indices; const color = cfg.color; const opacity = cfg.opacity; const buffer = this._buffer; const positionsIndex = buffer.positions.length; const vertsIndex = positionsIndex / 3; let numVerts; math.expandAABB3(this._modelAABB, cfg.aabb); if (this._preCompressedPositionsExpected) { if (!positionsCompressed) { throw "positionsCompressed expected"; } numVerts = positionsCompressed.length / 3; for (let i = 0, len = positionsCompressed.length; i < len; i++) { buffer.positions.push(positionsCompressed[i]); } } else { if (!positions) { throw "positions expected"; } numVerts = positions.length / 3; for (let i = 0, len = positions.length; i < len; i++) { buffer.positions.push(positions[i]); } } if (color) { const r = color[0]; // Color is pre-quantized by VBOSceneModel const g = color[1]; const b = color[2]; const a = opacity; for (let i = 0; i < numVerts; i++) { buffer.colors.push(r); buffer.colors.push(g); buffer.colors.push(b); buffer.colors.push(a); } } if (indices) { for (let i = 0, len = indices.length; i < len; i++) { buffer.indices.push(indices[i] + vertsIndex); } } if (this.model.scene.entityOffsetsEnabled) { for (let i = 0; i < numVerts; i++) { buffer.offsets.push(0); buffer.offsets.push(0); buffer.offsets.push(0); } } const portionId = this._portions.length / 2; this._portions.push(vertsIndex); this._portions.push(numVerts); this._numPortions++; this.model.numPortions++; this._numVerts += numVerts; this._meshes.push(mesh); return portionId; } /** * Builds batch VBOs from appended geometries. * No more portions can then be created. */ finalize() { if (this._finalized) { return; } const state = this._state; const gl = this.model.scene.canvas.gl; const buffer = this._buffer; if (buffer.positions.length > 0) { if (this._preCompressedPositionsExpected) { const positions = new Uint16Array(buffer.positions); state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, positions, buffer.positions.length, 3, gl.STATIC_DRAW); } else { const positions = new Float32Array(buffer.positions); const quantizedPositions = quantizePositions(positions, this._modelAABB, state.positionsDecodeMatrix); state.positionsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, quantizedPositions, buffer.positions.length, 3, gl.STATIC_DRAW); } } if (buffer.colors.length > 0) { const colors = new Uint8Array(buffer.colors); let normalized = false; state.colorsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, colors, buffer.colors.length, 4, gl.DYNAMIC_DRAW, normalized); } if (buffer.colors.length > 0) { // Because we build flags arrays here, get their length from the colors array const flagsLength = buffer.colors.length / 4; const flags = new Float32Array(flagsLength); let notNormalized = false; state.flagsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, flags, flags.length, 1, gl.DYNAMIC_DRAW, notNormalized); } if (this.model.scene.entityOffsetsEnabled) { if (buffer.offsets.length > 0) { const offsets = new Float32Array(buffer.offsets); state.offsetsBuf = new ArrayBuf(gl, gl.ARRAY_BUFFER, offsets, buffer.offsets.length, 3, gl.DYNAMIC_DRAW); } } if (buffer.indices.length > 0) { const indices = new Uint32Array(buffer.indices); state.indicesBuf = new ArrayBuf(gl, gl.ELEMENT_ARRAY_BUFFER, indices, buffer.indices.length, 1, gl.STATIC_DRAW); } this._buffer = null; this._finalized = true; } 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++; } const deferred = true; this._setFlags(portionId, flags, meshTransparent, deferred); } flushInitFlags() { this._setDeferredFlags(); } setVisible(portionId, flags, transparent) { 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, transparent); } setHighlighted(portionId, flags, transparent) { 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, transparent); } setXRayed(portionId, flags, transparent) { 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, transparent); } setSelected(portionId, flags, transparent) { 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, transparent); } setEdges(portionId, flags, transparent) { 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, transparent); } 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._setFlags(portionId, flags); } setCulled(portionId, flags, transparent) { 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, transparent); } setCollidable(portionId, flags) { if (!this._finalized) { throw "Not finalized"; } } setPickable(portionId, flags, transparent) { if (!this._finalized) { throw "Not finalized"; } if (flags & ENTITY_FLAGS.PICKABLE) { this._numPickableLayerPortions++; this.model.numPickableLayerPortions++; } else { this._numPickableLayerPortions--; this.model.numPickableLayerPortions--; } this._setFlags(portionId, flags, transparent); } setColor(portionId, color) { if (!this._finalized) { throw "Not finalized"; } const portionsIdx = portionId * 2; const vertexBase = this._portions[portionsIdx]; const numVerts = this._portions[portionsIdx + 1]; const firstColor = vertexBase * 4; const lenColor = numVerts * 4; const tempArray = this._scratchMemory.getUInt8Array(lenColor); const r = color[0]; const g = color[1]; const b = color[2]; const a = color[3]; for (let i = 0; i < lenColor; i += 4) { tempArray[i + 0] = r; tempArray[i + 1] = g; tempArray[i + 2] = b; tempArray[i + 3] = a; } this._state.colorsBuf.setData(tempArray, firstColor, lenColor); } setTransparent(portionId, flags, transparent) { if (transparent) { this._numTransparentLayerPortions++; this.model.numTransparentLayerPortions++; } else { this._numTransparentLayerPortions--; this.model.numTransparentLayerPortions--; } this._setFlags(portionId, flags, transparent); } /** * flags are 4bits values encoded on a 32bit base. color flag on the first 4 bits, silhouette flag on the next 4 bits and so on for edge, pick and clippable. */ _setFlags(portionId, flags, transparent, deferred = false) { if (!this._finalized) { throw "Not finalized"; } const portionsIdx = portionId * 2; const vertexBase = this._portions[portionsIdx]; const numVerts = this._portions[portionsIdx + 1]; const firstFlag = vertexBase; const lenFlags = numVerts; const visible = !!(flags & ENTITY_FLAGS.VISIBLE); const xrayed = !!(flags & ENTITY_FLAGS.XRAYED); const highlighted = !!(flags & ENTITY_FLAGS.HIGHLIGHTED); const selected = !!(flags & ENTITY_FLAGS.SELECTED); // no edges const pickable = !!(flags & ENTITY_FLAGS.PICKABLE); const culled = !!(flags & ENTITY_FLAGS.CULLED); let colorFlag; if (!visible || culled || xrayed || (highlighted && !this.model.scene.highlightMaterial.glowThrough) || (selected && !this.model.scene.selectedMaterial.glowThrough)) { colorFlag = RENDER_PASSES.NOT_RENDERED; } else { if (transparent) { colorFlag = RENDER_PASSES.COLOR_TRANSPARENT; } else { colorFlag = RENDER_PASSES.COLOR_OPAQUE; } } let silhouetteFlag; if (!visible || culled) { silhouetteFlag = RENDER_PASSES.NOT_RENDERED; } else if (selected) { silhouetteFlag = RENDER_PASSES.SILHOUETTE_SELECTED; } else if (highlighted) { silhouetteFlag = RENDER_PASSES.SILHOUETTE_HIGHLIGHTED; } else if (xrayed) { silhouetteFlag = RENDER_PASSES.SILHOUETTE_XRAYED; } else { silhouetteFlag = RENDER_PASSES.NOT_RENDERED; } let pickFlag = (visible && !culled && pickable) ? RENDER_PASSES.PICK : RENDER_PASSES.NOT_RENDERED; const clippableFlag = !!(flags & ENTITY_FLAGS.CLIPPABLE) ? 1 : 0; if (deferred) { // Avoid zillions of individual WebGL bufferSubData calls - buffer them to apply in one shot if (!this._deferredFlagValues) { this._deferredFlagValues = new Float32Array(this._numVerts); } for (let i = firstFlag, len = (firstFlag + lenFlags); i < len; i++) { let vertFlag = 0; vertFlag |= colorFlag; vertFlag |= silhouetteFlag << 4; // no edges vertFlag |= pickFlag << 12; vertFlag |= clippableFlag << 16; this._deferredFlagValues[i] = vertFlag; } } else if (this._state.flagsBuf) { const tempArray = this._scratchMemory.getFloat32Array(lenFlags); for (let i = 0; i < lenFlags; i++) { let vertFlag = 0; vertFlag |= colorFlag; vertFlag |= silhouetteFlag << 4; // no edges vertFlag |= pickFlag << 12; vertFlag |= clippableFlag << 16; tempArray[i] = vertFlag; } this._state.flagsBuf.setData(tempArray, firstFlag, lenFlags); } } _setDeferredFlags() { if (this._deferredFlagValues) { this._state.flagsBuf.setData(this._deferredFlagValues); this._deferredFlagValues = null; } } 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; } const portionsIdx = portionId * 2; const vertexBase = this._portions[portionsIdx]; const numVerts = this._portions[portionsIdx + 1]; const firstOffset = vertexBase * 3; const lenOffsets = numVerts * 3; const tempArray = this._scratchMemory.getFloat32Array(lenOffsets); const x = offset[0]; const y = offset[1]; const z = offset[2]; for (let i = 0; i < lenOffsets; i += 3) { tempArray[i + 0] = x; tempArray[i + 1] = y; tempArray[i + 2] = z; } this._state.offsetsBuf.setData(tempArray, firstOffset, lenOffsets); } //-- RENDERING ---------------------------------------------------------------------------------------------- drawColorOpaque(renderFlags, frameCtx) { if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numTransparentLayerPortions === this._numPortions || this._numXRayedLayerPortions === this._numPortions) { return; } if (this._renderers.colorRenderer) { this._renderers.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._renderers.colorRenderer) { this._renderers.colorRenderer.drawLayer(frameCtx, this, RENDER_PASSES.COLOR_TRANSPARENT); } } drawDepth(renderFlags, frameCtx) { } drawNormals(renderFlags, frameCtx) { } drawSilhouetteXRayed(renderFlags, frameCtx) { if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0 || this._numXRayedLayerPortions === 0) { return; } if (this._renderers.silhouetteRenderer) { this._renderers.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._renderers.silhouetteRenderer) { this._renderers.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._renderers.silhouetteRenderer) { this._renderers.silhouetteRenderer.drawLayer(frameCtx, this, RENDER_PASSES.SILHOUETTE_SELECTED); } } drawEdgesColorOpaque(renderFlags, frameCtx) { } drawEdgesColorTransparent(renderFlags, frameCtx) { } drawEdgesHighlighted(renderFlags, frameCtx) { } drawEdgesSelected(renderFlags, frameCtx) { } drawEdgesXRayed(renderFlags, frameCtx) { } drawPickMesh(frameCtx) { } drawPickDepths(frameCtx) { } drawPickNormals(frameCtx) { } drawSnapInit(renderFlags, frameCtx) { if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) { return; } if (this._renderers.snapInitRenderer) { this._renderers.snapInitRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK); } } drawSnap(renderFlags, frameCtx) { if (this._numCulledLayerPortions === this._numPortions || this._numVisibleLayerPortions === 0) { return; } if (this._renderers.snapRenderer) { this._renderers.snapRenderer.drawLayer(frameCtx, this, RENDER_PASSES.PICK); } } drawOcclusion(frameCtx) { } drawShadow(frameCtx) { } destroy() { const state = this._state; if (state.positionsBuf) { state.positionsBuf.destroy(); state.positionsBuf = null; } if (state.offsetsBuf) { state.offsetsBuf.destroy(); state.offsetsBuf = null; } if (state.colorsBuf) { state.colorsBuf.destroy(); state.colorsBuf = null; } if (state.flagsBuf) { state.flagsBuf.destroy(); state.flagsBuf = null; } if (state.indicesBuf) { state.indicesBuf.destroy(); state.indicesBuf = null; } state.destroy(); } }