viewer/fatlinerenderer.js
import { Utils } from "./utils.js";
import { ProgramManager } from "./programmanager.js";
import { VERTEX_QUANTIZATION } from "./programmanager.js";
import { LINE_PRIMITIVES } from "./programmanager.js";
/**
*
* As you many know, line rendering with thickness is not well-supported in
* WebGL implementations (mostly due to WebGL implementations on Windows
* defaulting to emulating OpenGL calls via ANGLE on DirectX 9). Therefore
* lines are rendered using triangles. This greatly blows up the memory
* requirements as enough information needs to be passed to expand lines
* in the vertex shader to a constant thickness. See below:
*
* @todo: we should probably see if some of this overhead can be reduced
* with drawElementsInstanced() or by cleverly aligning strides and
* or offsets.
*
* (A,B,-1) (B,A,-1)
* +-------------------------------------------------+
* | |
* | (A) (B) |
* | +-------------------------------------------+ |
* | |
* | |
* +-------------------------------------------------+
* (A,B,1) (B,A,1)
*
*/
export class FatLineRenderer {
constructor(viewer, gl, settings, unquantizationMatrix) {
this.viewer = viewer;
settings = settings || {};
this.idx = 0;
this.gl = gl;
this.vertexPosition = Array();
this.nextVertexPosition = Array();
this.direction = Array();
this.indices = Array();
this.quantize = settings.quantize || false;
this.matrixMap = new Map();
this.uniqueModelId = null;
this.unquantizationMatrix = unquantizationMatrix;
this.nrIndices = 0;
this.defaultDirection = new Float32Array([1, 1, -1, -1]);
var key = 0;
key |= (this.quantize ? VERTEX_QUANTIZATION : 0);
key |= LINE_PRIMITIVES;
this.programInfo = this.viewer.programManager.getProgram(key);
}
init(size) {
// This method initializes the arrays as typed arrays with a known size, otherwise the arrays are used
this.indexType = (size * 4 < 256) ? this.gl.UNSIGNED_BYTE : this.gl.UNSIGNED_SHORT;
if (size * 4 > 65536) {
this.indexType = this.gl.UNSIGNED_INT;
}
const elemType = this.quantize ? this.gl.SHORT : this.gl.FLOAT;
const typedArrFn = Utils.glTypeToTypedArray(elemType);
this.vertexPosition = new typedArrFn(size * 12);
this.vertexPosition.pos = 0;
this.nextVertexPosition = new typedArrFn(size * 12);
this.nextVertexPosition.pos = 0;
// TODO this is always 1, 1, -1, -1, why?
this.direction = new Float32Array(size * 4);
this.direction.pos = 0;
for (var i = 0; i < size; i++) {
this.direction.set(this.defaultDirection, this.direction.pos);
this.direction.pos += 4;
}
this.indices = this.indexType == this.gl.UNSIGNED_BYTE ? new Uint8Array(size * 6) : (this.indexType == this.gl.UNSIGNED_SHORT ? new Uint16Array(size * 6) : new Uint32Array(size * 6));
this.indices.pos = 0;
this.nrIndices = size * 6;
}
finalize() {
const gl = this.gl;
this.setupFunctions = ["vertexPosition", "nextVertexPosition", "direction", "indices"].map((bufferName, i) => {
const buf = this[bufferName + "Buffer"] = gl.createBuffer();
const bufType = bufferName === "indices" ? gl.ELEMENT_ARRAY_BUFFER : gl.ARRAY_BUFFER;
// @todo, somehow just cannot get direction as a byte to work :(
const elemType = [this.quantize ? gl.SHORT : gl.FLOAT, this.quantize ? gl.SHORT : gl.FLOAT, gl.FLOAT, this.indexType][i];
if (Array.isArray(this[bufferName])) {
const typedArrFn = Utils.glTypeToTypedArray(elemType);
var typedArr = new typedArrFn(this[bufferName]);
} else {
var typedArr = this[bufferName];
}
const numElements = bufferName === "direction" ? 1 : 3;
gl.bindBuffer(bufType, buf);
gl.bufferData(bufType, typedArr, this.gl.STATIC_DRAW);
return (programInfo) => {
// TODO this could be done with a VAO?
gl.bindBuffer(bufType, buf);
if (bufType != gl.ELEMENT_ARRAY_BUFFER) {
var loc = programInfo.attribLocations[bufferName];
if (elemType == gl.FLOAT) {
gl.vertexAttribPointer(loc, numElements, elemType, false, 0, 0);
} else {
gl.vertexAttribIPointer(loc, numElements, elemType, 0, 0);
}
gl.enableVertexAttribArray(loc);
}
};
});
// Time to cleanup the CPU buffers?
this.vertexPosition = null;
this.nextVertexPosition = null;
this.direction = null;
this.indices = null;
}
pushVertices(a, b) {
this.vertexPosition.set(a, this.vertexPosition.pos);
this.vertexPosition.pos += 3;
this.vertexPosition.set(b, this.vertexPosition.pos);
this.vertexPosition.pos += 3;
this.vertexPosition.set(a, this.vertexPosition.pos);
this.vertexPosition.pos += 3;
this.vertexPosition.set(b, this.vertexPosition.pos);
this.vertexPosition.pos += 3;
this.nextVertexPosition.set(b, this.nextVertexPosition.pos);
this.nextVertexPosition.pos += 3;
this.nextVertexPosition.set(a, this.nextVertexPosition.pos);
this.nextVertexPosition.pos += 3;
this.nextVertexPosition.set(b, this.nextVertexPosition.pos);
this.nextVertexPosition.pos += 3;
this.nextVertexPosition.set(a, this.nextVertexPosition.pos);
this.nextVertexPosition.pos += 3;
// This is faster than using .set because that requires us to create an array first
this.indices[this.indices.pos++] = this.idx;
this.indices[this.indices.pos++] = this.idx + 1;
this.indices[this.indices.pos++] = this.idx + 2;
this.indices[this.indices.pos++] = this.idx + 1;
this.indices[this.indices.pos++] = this.idx + 3;
this.indices[this.indices.pos++] = this.idx;
this.idx += 4;
}
getVertexBuffer() {
return this.vertexPositionBuffer;
}
// To minimize GPU calls, renderStart and renderStop can (and have to be) used in order to batch-draw a lot of boxes
renderStart(viewer, renderLayer) {
this.gl.useProgram(this.programInfo.program);
this.gl.uniformMatrix4fv(this.programInfo.uniformLocations.projectionMatrix, false, viewer.camera.projMatrix);
this.gl.uniformMatrix4fv(this.programInfo.uniformLocations.viewMatrix, false, viewer.camera.viewMatrix);
this.gl.uniform3fv(this.programInfo.uniformLocations.postProcessingTranslation, renderLayer.postProcessingTranslation);
const aspect = viewer.width / viewer.height;
this.gl.uniform1f(this.programInfo.uniformLocations.aspect, aspect);
if (this.quantize) {
if (this.uniqueModelId) {
// This is necessary for line renderings of reused geometries.
this.gl.uniformMatrix4fv(this.programInfo.uniformLocations.vertexQuantizationMatrix, false, viewer.vertexQuantization.getUntransformedInverseVertexQuantizationMatrixForUniqueModelId(this.uniqueModelId));
} else {
if (this.unquantizationMatrix) {
this.gl.uniformMatrix4fv(this.programInfo.uniformLocations.vertexQuantizationMatrix, false, this.unquantizationMatrix);
}
}
}
this.first = true;
}
renderStop() {
}
render(color, matrix, thickness) {
for (const fn of this.setupFunctions) {
fn(this.programInfo);
}
this.gl.uniform1f(this.programInfo.uniformLocations.thickness, thickness || 0.005);
this.gl.uniformMatrix4fv(this.programInfo.uniformLocations.matrix, false, matrix);
this.gl.uniform4fv(this.programInfo.uniformLocations.inputColor, color);
this.gl.drawElements(this.gl.TRIANGLES, this.nrIndices, this.indexType, 0);
}
}