src/viewer/scene/webgl/occlusion/OcclusionTester.js
import {math} from '../../math/math.js';
import {createProgramVariablesState} from "../WebGLRenderer.js";
import {OcclusionLayer} from "./OcclusionLayer.js";
import {createRTCViewMat} from "../../math/rtcCoords.js";
const MARKER_COLOR = math.vec3([ 255, 0, 0 ]);
const POINT_SIZE = 20;
const tempVec3a = math.vec3();
/**
* Manages occlusion testing. Private member of a Renderer.
* @private
*/
export class OcclusionTester {
constructor(scene) {
this._scene = scene;
this._occlusionLayers = {};
this._occlusionLayersList = [];
this._occlusionLayersListDirty = false;
this._drawable = null;
this._markersToOcclusionLayersMap = {};
const camera = scene.camera;
const markOcclusionTestListDirty = () => { this._occlusionTestListDirty = true; };
this._onCameraViewMatrix = camera.on("viewMatrix", markOcclusionTestListDirty);
this._onCameraProjMatrix = camera.on("projMatrix", markOcclusionTestListDirty);
this._onCanvasBoundary = scene.canvas.on("boundary", markOcclusionTestListDirty);
}
_addMarker(marker, originHash) {
if (! this._occlusionLayers[originHash]) {
this._occlusionLayers[originHash] = new OcclusionLayer(marker.origin);
this._occlusionLayersListDirty = true;
}
const occlusionLayer = this._occlusionLayers[originHash];
occlusionLayer.addMarker(marker);
this._markersToOcclusionLayersMap[marker.id] = occlusionLayer;
}
_removeMarker(occlusionLayer, marker) {
if (occlusionLayer.numMarkers === 1) {
occlusionLayer.destroy();
delete this._occlusionLayers[occlusionLayer.originHash];
this._occlusionLayersListDirty = true;
} else {
occlusionLayer.removeMarker(marker);
}
delete this._markersToOcclusionLayersMap[marker.id];
}
/**
* Adds a Marker for occlusion testing.
* @param marker
*/
addMarker(marker) {
const originHash = marker.origin.join();
this._addMarker(marker, originHash);
this._occlusionTestListDirty = true;
}
/**
* Notifies OcclusionTester that a Marker has updated its World-space position.
* @param marker
*/
markerWorldPosUpdated(marker) {
const occlusionLayer = this._markersToOcclusionLayersMap[marker.id];
if (occlusionLayer) {
const originHash = marker.origin.join();
if (originHash !== occlusionLayer.originHash) {
this._removeMarker(occlusionLayer, marker);
this._addMarker(marker, originHash);
} else {
occlusionLayer.markerWorldPosUpdated(marker);
}
}
}
/**
* Removes a Marker from occlusion testing.
* @param marker
*/
removeMarker(marker) {
const originHash = marker.origin.join();
const occlusionLayer = this._occlusionLayers[originHash];
if (occlusionLayer) {
this._removeMarker(occlusionLayer, marker);
}
}
/**
* Returns true if an occlusion test is needed.
*
* @returns {Boolean}
*/
get needOcclusionTest() {
return this._occlusionTestListDirty;
}
/**
* Draws {@link Marker}s to the render buffer.
*/
drawMarkers() {
const scene = this._scene;
const canvas = scene.canvas;
const gl = canvas.gl;
const sectionPlanesState = scene._sectionPlanesState;
const shaderSourceHash = [canvas.canvas.id, sectionPlanesState.getHash()].join(";");
if ((! this._drawable) || (shaderSourceHash !== this._drawable.shaderSourceHash)) {
if (this._drawable) {
this._drawable.destroy();
}
const programVariablesState = createProgramVariablesState();
const programVariables = programVariablesState.programVariables;
const viewMatrix = programVariables.createUniform("mat4", "viewMatrix");
const projMatrix = programVariables.createUniform("mat4", "projMatrix");
const position = programVariables.createAttribute("vec3", "position");
const outColor = programVariables.createOutput("vec4", "outColor");
const clipPos = "clipPos";
const [program, errors] = programVariablesState.buildProgram(
gl,
"OcclusionTester",
{
sectionPlanesState: sectionPlanesState,
getLogDepth: scene.logarithmicDepthBufferEnabled && (vFragDepth => vFragDepth),
clippableTest: () => "true",
getVertexData: () => {
const src = [ ];
src.push(`vec4 worldPosition = vec4(${position}, 1.0);`);
src.push(`vec4 ${clipPos} = ${projMatrix} * ${viewMatrix} * worldPosition;`);
if ((! scene.logarithmicDepthBufferEnabled) && (scene.markerZOffset < 0.000)) {
src.push(`${clipPos}.z += ${scene.markerZOffset};`);
}
return src;
},
worldPositionAttribute: "worldPosition",
getPointSize: () => "20.0",
clipPos: clipPos,
appendFragmentOutputs: (src) => src.push(`${outColor} = vec4(1.0, 0.0, 0.0, 1.0);`)
});
if (errors) {
console.error(errors.join("\n"));
throw errors.join("\n");
} else {
this._occlusionTestListDirty = true;
this._drawable = {
shaderSourceHash: shaderSourceHash,
destroy: () => program.destroy(),
drawCall: () => {
const camera = scene.camera;
const project = camera.project;
program.bind();
projMatrix.setInputValue(project.matrix);
const boundary = canvas.boundary;
const canvasWidth = boundary[2];
const canvasHeight = boundary[3];
const near = camera.perspective.near; // Assume near enough to ortho near
const markerInView = marker => {
const viewPos = marker.viewPos;
const canvasPos = marker.canvasPos;
const canvasX = canvasPos[0];
const canvasY = canvasPos[1];
return (viewPos[2] <= -near) && (canvasX >= -10) && (canvasY >= -10) && (canvasX <= canvasWidth + 10) && (canvasY <= canvasHeight + 10);
};
this._occlusionLayersList.forEach(occlusionLayer => {
occlusionLayer.update(gl, markerInView);
const culled = sectionPlanesState.sectionPlanes.some((sectionPlane, i) => {
const intersect = sectionPlane.active ? math.planeAABB3Intersect(sectionPlane.dir, sectionPlane.dist, occlusionLayer.aabb) : 1;
const outside = (intersect === -1);
occlusionLayer.sectionPlanesActive[i] = (intersect === 0); // if outside, then sectionPlanesActive won't be even tested
return outside;
});
if (! culled) {
const origin = occlusionLayer.origin;
viewMatrix.setInputValue(createRTCViewMat(camera.viewMatrix, origin));
program.inputSetters.setUniforms(
null,
{
view: { far: project.far },
mesh: {
origin: origin,
renderFlags: { sectionPlanesActivePerLayer: occlusionLayer.sectionPlanesActive }
}
});
position.setInputValue(occlusionLayer.positionsBuf);
const indicesBuf = occlusionLayer.indicesBuf;
indicesBuf.bind();
gl.drawElements(gl.POINTS, indicesBuf.numItems, indicesBuf.itemType, 0);
}
});
}
};
}
}
if (this._occlusionLayersListDirty) {
let numOcclusionLayers = 0;
for (let originHash in this._occlusionLayers) {
if (this._occlusionLayers.hasOwnProperty(originHash)) {
this._occlusionLayersList[numOcclusionLayers++] = this._occlusionLayers[originHash];
}
}
this._occlusionLayersList.length = numOcclusionLayers;
this._occlusionLayersListDirty = false;
}
if (this._occlusionTestListDirty) {
for (let i = 0, len = this._occlusionLayersList.length; i < len; i++) {
const occlusionLayer = this._occlusionLayersList[i];
occlusionLayer.occlusionTestListDirty = true;
}
this._occlusionTestListDirty = false;
}
this._drawable.drawCall();
}
/**
* Sets visibilities of {@link Marker}s according to whether or not they are obscured by anything in the render buffer.
*/
doOcclusionTest(readColorPixel) {
this._occlusionLayersList.forEach(occlusionLayer => {
for (let i = 0; i < occlusionLayer.lenOcclusionTestList; i++) {
const j = i * 2;
const color = readColorPixel(occlusionLayer.pixels[j], occlusionLayer.pixels[j + 1]);
occlusionLayer.occlusionTestList[i]._setVisible(math.compareVec3(MARKER_COLOR, color));
}
});
}
/**
* Destroys this OcclusionTester.
*/
destroy() {
if (! this.destroyed) {
this._occlusionLayersList.forEach(layer => layer.destroy());
this._drawable && this._drawable.destroy();
this._scene.camera.off(this._onCameraViewMatrix);
this._scene.camera.off(this._onCameraProjMatrix);
this._scene.canvas.off(this._onCanvasBoundary);
this.destroyed = true;
}
}
}