Webgl.js

import { COLOR_BUFFER_BIT, DEPTH_BUFFER_BIT } from '../const/webglConst';
import _fallback from '../utils/fallback';
import Debug from '../utils/Debug';
import { mat4, mat3 } from 'gl-matrix';
import Texture from './Texture';
import TextureCube from './TextureCube';
import Ext from './Extension';
import Utils from '../utils/Utils';
function camelize(str) {
  return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function(match, index) {
    if (+match === 0) return ""; // or if (/\s+/.test(match)) for white spaces
    return index == 0 ? match.toLowerCase() : match.toUpperCase();
  });
}


let modelViewMatrix = mat4.create();
let inverseModelViewMatrix = mat4.create();
let normalMatrix4 = mat4.create();
let normalMatrix3 = mat3.create();


class Webgl {
/**
  * Constructs a new WebglRenderer
  * @param {object} [params={}]  context type
  * @param {string} [params.type=webgl]  context type
  * @param {object} [params.canvas=null]  canvas to attach the renderer
  * @param {number} [params.width=window.innerWidth]  width of the renderer
  * @param {number} [params.height=window.innerHeight]  height of the renderer
  * @param {object} [params.contextOptions={}]  options to pass to the webgl context
  * @param {function} [params.fallback=null]  function called when webgl is not possible
  */
  constructor({
    type = 'webgl',
    canvas = null,
    width = window.innerWidth,
    height = window.innerHeight,
    contextOptions = {},
    fallback = null,
  } = {}) {
    Debug.print(`Vanilla GL`);
    Debug.print(`https://github.com/JordanMachado/webgl-tools`);

    this.canvas = canvas ? canvas : document.createElement('canvas');
    this.canvas.style.width = `${width}px`;
    this.canvas.style.height = `${height}px`;
    this.width = width * window.devicePixelRatio;
    this.height = height * window.devicePixelRatio;
    window.gl = this.context = this.gl = this.canvas.getContext(type, contextOptions);
    Ext.setGl(gl);
    Ext.active('OES_vertex_array_object');
    Ext.active('ANGLE_instanced_arrays');
    Ext.active('OES_standard_derivatives');


    // no webgl2
    if(!!!this.context) {
      console.warn('No Webgl 2');

      if (fallback) {
        if (typeof fallback === 'function') {
          fallback();
        } else {
          console.warn('Fallback is not a function');
        }
      } else {
        _fallback();
      }
    } else {
      this.canvas.width = width * window.devicePixelRatio;
      this.canvas.height = height * window.devicePixelRatio;
      this.context.viewport(0, 0, this.canvas.width, this.canvas.height);
      this.aspect = this.canvas.width / this.canvas.height;


      Debug.info(`${type} created`);

    }
  }
  /**
  * @func  append
  * @description  Append the renderer into an htmlElement
  * @param {object} [container=document.body]  html element to add the canvas
  * @memberof Webgl.prototype
  */
  append(container) {
    if (container) {
      container.appendChild(this.canvas);
    } else {
      document.body.appendChild(this.canvas);
    }
  }
  /**
  * @func  clearColor
  * @description  Set clearColor of the renderer
  * @param {number} r  red channel
  * @param {number} v  green channel
  * @param {number} b  blue channel
  * @param {number} a  alpha channel
  * @memberof Webgl.prototype
  */
  clearColor(r, g, b, a) {
    if(arguments.length === 2) {
       const color = Utils.hexToRgb(r);
      this.gl.clearColor(color[0], color[1], color[2], g);
    } else if(arguments.length === 4){
      this.gl.clearColor(r, g, b, a);
    } else {
      this.gl.clearColor(r[0],r[1],r[2],1);

    }

  }
  /**
  * @func  clear
  * @description  Clear the color buffer & depth buffer
  * @memberof Webgl.prototype
  */
  clear() {
    this.gl.clear(COLOR_BUFFER_BIT | DEPTH_BUFFER_BIT);
  }
  /**
  * @func  useProgram
  * @description  Set the current program
  * @param {object} program instance of a Shader
  * @memberof Webgl.prototype
  */
  useProgram(program) {
    if (this.shader === shader) return;
    this.shader = shader;
    this.gl.useProgram(shader.program);
  }
  /**
  * @func  setDefaultUniforms
  * @description  Set default uniforms to a mesh
  * @param {object} mesh instance of a Mesh
  * @param {object} camera instance of a Camera
  * @memberof Webgl.prototype
  */
  setDefaultUniforms(mesh, camera) {
    // if container
    if(mesh.parent && !mesh.parent.shader && mesh.parent._needUpdate == true) {
      mesh.parent._updateMatrix()
    }
    mesh.shader.program.uniforms.projectionMatrix = camera.projection;
    mesh.shader.program.uniforms.viewMatrix = camera.view;
    mesh.shader.program.uniforms.worldMatrix = mesh.matrixWorld;
    mesh.shader.program.uniforms.worldMatrix = mesh.matrixWorld;

    if (mesh.geometry.normals) {
      mat4.identity(modelViewMatrix);
      mat4.multiply(modelViewMatrix, camera.view, mesh.matrixWorld);
      mat4.invert(inverseModelViewMatrix, modelViewMatrix);
      mat4.transpose(normalMatrix4, inverseModelViewMatrix);
      mesh.shader.program.uniforms.normalMatrix = mat3.fromMat4(normalMatrix3,normalMatrix4);
    }

  }
  /**
  * @func  setUniforms
  * @description  Set all the uniforms of a mesh
  * @param {object} mesh instance of a Mesh
  * @memberof Webgl.prototype
  */
  setUniforms(mesh) {
    let count = 0;
    for (const key in mesh.shader.uniforms) {
      if(mesh.shader.program.uniforms.hasOwnProperty(key)) {
        if(mesh.shader.uniforms[key] instanceof Texture || mesh.shader.uniforms[key] instanceof TextureCube) {
          mesh.shader.uniforms[key].bindIndex(count)
          mesh.shader.uniforms[key].bind();
          count++
        }
        mesh.shader.program.uniforms[key] = mesh.shader.uniforms[key];
        // console.log(mesh.shader.program.uniforms[key]);

      }
    }
  }
  /**
  * @func  bindBuffer
  * @description  Bind all the buffer of a mesh
  * @param {object} mesh instance of a Mesh
  * @memberof Webgl.prototype
  */
  bindBuffer(mesh) {
    // TODO
    // if(gl.createVertexArray && !mesh.vao && true) {
    //   mesh.vao = gl.createVertexArray();
    //   gl.bindVertexArray(mesh.vao);
    //   this.attr(mesh);
    //   gl.bindVertexArray(null);
    // } else {
    //   if(mesh.vao) {
    //     gl.bindVertexArray(mesh.vao);
    //   } else {
        this.attr(mesh);
      // }
    // }
 }
 /**
 * @func  unbindBuffer
 * @description  Unbind all the buffer of a mesh
 * @param {object} mesh instance of a Mesh
 * @memberof Webgl.prototype
 */
 unbindBuffer(mesh) {
   for (const key in mesh.geometry.attributes) {
     let str = `a ${key}`;

     let aKey = camelize(str.substring(0, str.length - 1));
     if(mesh.geometry.attributes[key].instanced) {
       if(mesh.shader.program.attributes[aKey])

       mesh.geometry.attributes[key].attribPointerInstanced(mesh.shader.program.attributes[aKey], 0);
     }
   }
 }
 /**
 * @func  attr
 * @description  Add all the attributes of a mesh
 * @param {object} mesh instance of a Mesh
 * @memberof Webgl.prototype
 */
 attr(mesh) {
   for (const key in mesh.geometry.attributes) {
     let str = `a ${key}`;
     // console.log(key);

     let aKey = camelize(str.substring(0, str.length - 1));

     if(mesh.geometry.attributes[key].instanced) {
       if(mesh.shader.program.attributes[aKey])
        mesh.geometry.attributes[key].attribPointerInstanced(mesh.shader.program.attributes[aKey], mesh.geometry.attributes[key].divisor);
       else {
         Debug.error(`Attribute ${aKey} not used`);
       }
     } else {

      if(mesh.shader.program.attributes[aKey])
       mesh.geometry.attributes[key].attribPointer(mesh.shader.program.attributes[aKey]);
     }
   }
 }
 /**
 * @func  render
 * @description  Render to the canvas
 * @param {object|array} mesh instance of a Mesh or array of Mesh
 * @param {object} camera instance of a Camera
 * @memberof Webgl.prototype
 */
 render(mesh, camera) {
   if(mesh.shader) {

     mesh.shader.program.bind();
     if(camera)
      this.setDefaultUniforms(mesh, camera);
     this.setUniforms(mesh);
     this.bindBuffer(mesh);
     if (mesh.geometry.indices) {
       mesh.geometry.indices.bind()
       if(mesh.geometry.instanced) {
         mesh.geometry.indices.drawInstance(mesh.drawType,   mesh.geometry.count);
       } else {
         mesh.geometry.indices.draw(mesh.drawType);
       }
       mesh.geometry.indices.unbind()

     } else {
       mesh.geometry.positions.draw(mesh.drawType);
     }
     this.unbindBuffer(mesh);
   }

    if(mesh.children.length > 0) {
      for (var i = 0; i < mesh.children.length; i++) {
        this.render(mesh.children[i], camera);
      }
    }
  }
  /**
  * @func  resize
  * @description  Resize the canvas
  * @memberof Webgl.prototype
  */
  resize() {

    this.canvas.style.width = `${window.innerWidth}px`;
    this.canvas.style.height = `${window.innerHeight}px`;
    this.canvas.width = window.innerWidth * window.devicePixelRatio;
    this.canvas.height = window.innerHeight * window.devicePixelRatio;
    this.aspect = this.canvas.width/this.canvas.height;
    this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
  }
}

export default Webgl;