//@ts-check
/**
* this class is responsible for the WebGL operations of the randar renderer
* @class
*/
export class WebGLRadarRenderer {
/**
* Create a new WebGLRadarRenderer
* @param {WebGL2RenderingContext} gl
* @param {string} vertexShaderSource
* @param {string} fragmentShaderSource
*/
constructor(gl, vertexShaderSource, fragmentShaderSource){
this.gl = gl;
this.vertexShaderSource = vertexShaderSource;
this.fragmentShaderSource = fragmentShaderSource;
/**
* @private
* @type {WebGLShader} */
this.vertexShader = null;
/**
* @private
* @type {WebGLShader} */
this.fragmentShader = null;
/**
* @private
* @type {WebGLProgram} */
this.program = null;
/**
* @private
* @type {Object<string, GLint>} */
this.attributes = {};
/**
* @private
* @type {Object<string, WebGLUniformLocation>} */
this.uniforms = {};
/**
* @private
* @type {{[key: string]: WebGLBuffer}} */
this.buffers = {};
/**
* @private
* @type {WebGLVertexArrayObject} */
this.vao = null;
/**
* @private
* @type {Object} */
this.attributeCache = {};
/**
* check if the attributes are dirty and need to be updated
* @type {boolean} */
this.dirty = false;
this.allocatedVertexCount = 0;
this.createShaderProgram();
}
/**
* Create the shaders the program and the buffer
*/
createShaderProgram(){
const { gl } = this;
this.vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(this.vertexShader, this.vertexShaderSource);
gl.compileShader(this.vertexShader);
if(!gl.getShaderParameter(this.vertexShader, gl.COMPILE_STATUS)){
console.error(gl.getShaderInfoLog(this.vertexShader));
}
this.fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(this.fragmentShader, this.fragmentShaderSource);
gl.compileShader(this.fragmentShader);
if(!gl.getShaderParameter(this.fragmentShader, gl.COMPILE_STATUS)){
console.error(gl.getShaderInfoLog(this.fragmentShader));
}
this.program = gl.createProgram();
gl.attachShader(this.program, this.vertexShader);
gl.attachShader(this.program, this.fragmentShader);
gl.linkProgram(this.program);
if(!gl.getProgramParameter(this.program, gl.LINK_STATUS)){
console.error(gl.getProgramInfoLog(this.program));
}
this.uniforms.colormap = gl.getUniformLocation(this.program, 'colormap');
this.uniforms.minimum = gl.getUniformLocation(this.program, 'minimum');
this.uniforms.maximum = gl.getUniformLocation(this.program, 'maximum');
this.uniforms.opacity = gl.getUniformLocation(this.program, 'opacity');
this.uniforms.colormap_length = gl.getUniformLocation(this.program, 'colormap_length');
}
/**
* Set the attributes of the shader
* @param {Object} attributes
* @param {boolean} updateImmediately
*/
setAttributes(attributes, updateImmediately = false){
if(!updateImmediately){
this.attributeCache = attributes;
this.dirty = true;
return;
}
const { gl } = this;
gl.useProgram(this.program);
if(!this.vao){
this.vao = gl.createVertexArray();
}
gl.bindVertexArray(this.vao);
for(const key in attributes){
if(!this.attributes[key]){
this.attributes[key] = gl.getAttribLocation(this.program, key);
}
let attribute = this.attributes[key];
let value = attributes[key];
if(!this.buffers[key] || this.allocatedVertexCount < value.data.length/value.size){
this.buffers[key] = gl.createBuffer();
const buffer = this.buffers[key];
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, value.data, gl.DYNAMIC_DRAW);
this.allocatedVertexCount = value.data.length/value.size;
}else{
gl.bindBuffer(gl.ARRAY_BUFFER, this.buffers[key]);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, value.data);
}
gl.vertexAttribPointer(attribute, value.size, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(attribute);
}
}
/**
* pass the minimum and maximum values to the shader
* @param {number} min
* @param {number} max
*/
setMinMax(min, max){
const { gl } = this;
gl.useProgram(this.program);
gl.uniform1f(this.uniforms.minimum, min);
gl.uniform1f(this.uniforms.maximum, max);
}
/**
* pass the colormap as a uniform to the shader
* @param {number[][]} colormap
*/
setColormap(colormap) {
const { gl } = this;
const colormapUniform = gl.getUniformLocation(this.program, 'colormap');
gl.useProgram(this.program);
gl.uniform4fv(colormapUniform, new Float32Array(colormap.flat()));
gl.uniform1f(this.uniforms.colormap_length, colormap.length);
}
/**
* set the opacity of the radar renderer
* @param {number} opacity
*/
setOpacity(opacity){
const { gl } = this;
gl.useProgram(this.program);
gl.uniform1f(this.uniforms.opacity, opacity);
}
/**
* execute the WebGL draw operation
* @param {Object} uniforms
*/
render(uniforms){
const { gl } = this;
if(this.dirty){
this.setAttributes(this.attributeCache, true);
this.dirty = false;
}
gl.useProgram(this.program);
gl.bindVertexArray(this.vao);
this.setUniforms(uniforms);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLES, 0, this.allocatedVertexCount);
}
/**
* Set the uniforms of the shader
* @param {Object} uniforms
*/
setUniforms(uniforms){
const { gl } = this;
gl.useProgram(this.program);
for(const key in uniforms){
if(!this.uniforms[key]){
this.uniforms[key] = gl.getUniformLocation(this.program, key);
}
let uniform = this.uniforms[key];
let value = uniforms[key];
if(value instanceof Float32Array || value instanceof Array){
if(value.length === 16){
gl.uniformMatrix4fv(uniform, false, value);
}else if(value.length === 9){
gl.uniformMatrix3fv(uniform, false, value);
}else{
gl['uniform' + value.length + 'fv'](uniform, value);
}
}else if(typeof value === 'number'){
gl.uniform1f(uniform, value);
}
}
}
/**
* Clear the buffer and reset the allocated vertex count
*/
clear(){
const { gl } = this;
for (const key in this.buffers) {
gl.deleteBuffer(this.buffers[key]);
}
this.buffers = {};
this.allocatedVertexCount = 0;
}
/**
* Destroy the shaders, the program, the buffer and the allocated vertex count
*/
destroy(){
const { gl } = this;
gl.deleteShader(this.vertexShader);
gl.deleteShader(this.fragmentShader);
gl.deleteProgram(this.program);
for (const key in this.buffers) {
gl.deleteBuffer(this.buffers[key]);
}
this.vertexShader = null;
this.fragmentShader = null;
this.program = null;
this.buffers = {};
this.allocatedVertexCount = 0;
this.attributes = {};
this.uniforms = {};
this.gl = null;
}
}