vgl/uniform.js

var vgl = require('./vgl');
var inherit = require('../inherit');
var mat4 = require('gl-mat4');

/**
 * Create a new instance of class uniform.
 *
 * @class
 * @alias vgl.uniform
 * @param {number} type The GL type, such as FLOAT or INT.
 * @param {string} name The name of the uniform.
 * @returns {vgl.uniform} OpenGL uniform encapsulation
 */
vgl.uniform = function (type, name) {
  'use strict';

  if (!(this instanceof vgl.uniform)) {
    return new vgl.uniform(type, name);
  }

  this.getTypeNumberOfComponents = function (type) {
    switch (type) {
      case vgl.GL.FLOAT:
      case vgl.GL.INT:
      case vgl.GL.BOOL:
        return 1;

      case vgl.GL.FLOAT_VEC2:
      case vgl.GL.INT_VEC2:
      case vgl.GL.BOOL_VEC2:
        return 2;

      case vgl.GL.FLOAT_VEC3:
      case vgl.GL.INT_VEC3:
      case vgl.GL.BOOL_VEC3:
        return 3;

      case vgl.GL.FLOAT_VEC4:
      case vgl.GL.INT_VEC4:
      case vgl.GL.BOOL_VEC4:
        return 4;

      case vgl.GL.FLOAT_MAT3:
        return 9;

      case vgl.GL.FLOAT_MAT4:
        return 16;

      default:
        return 0;
    }
  };

  var m_type = type,
      m_name = name,
      m_dataArray = new Array(this.getTypeNumberOfComponents(m_type)).fill(0);

  /**
   * Get name of the uniform.
   *
   * @returns {string}
   */
  this.name = function () {
    return m_name;
  };

  /**
   * Set value of the uniform.
   *
   * @param {Array|number} value
   */
  this.set = function (value) {
    var i = 0, lendata = m_dataArray.length;
    if (lendata !== 1) {
      for (i = 0; i < lendata; i += 1) {
        m_dataArray[i] = value[i];
      }
    } else {
      m_dataArray[0] = value;
    }
  };

  /**
   * Call GL and pass updated values to the current shader.
   *
   * @param {vgl.renderState} renderState The current render state with the
   *    current context.
   * @param {number} location The context location.
   */
  this.callGL = function (renderState, location) {
    switch (m_type) {
      case vgl.GL.BOOL:
      case vgl.GL.INT:
        renderState.m_context.uniform1iv(location, m_dataArray);
        break;
      case vgl.GL.FLOAT:
        renderState.m_context.uniform1fv(location, m_dataArray);
        break;
      case vgl.GL.BOOL_VEC2:
      case vgl.GL.INT_VEC2:
        renderState.m_context.uniform2iv(location, m_dataArray);
        break;
      case vgl.GL.FLOAT_VEC2:
        renderState.m_context.uniform2fv(location, m_dataArray);
        break;
      case vgl.GL.BOOL_VEC3:
      case vgl.GL.INT_VEC3:
        renderState.m_context.uniform3iv(location, m_dataArray);
        break;
      case vgl.GL.FLOAT_VEC3:
        renderState.m_context.uniform3fv(location, m_dataArray);
        break;
      case vgl.GL.BOOL_VEC4:
      case vgl.GL.INT_VEC4:
        renderState.m_context.uniform4iv(location, m_dataArray);
        break;
      case vgl.GL.FLOAT_VEC4:
        renderState.m_context.uniform4fv(location, m_dataArray);
        break;
      case vgl.GL.FLOAT_MAT3:
        renderState.m_context.uniformMatrix3fv(location, vgl.GL.FALSE, m_dataArray);
        break;
      case vgl.GL.FLOAT_MAT4:
        renderState.m_context.uniformMatrix4fv(location, vgl.GL.FALSE, m_dataArray);
        break;
      default:
        break;
    }
  };

  /**
   * Virtual method to update the uniform.
   *
   * Should be implemented by the derived class.
   *
   * @param {vgl.renderState} renderState
   * @param {vgl.shaderProgram} program
   */
  this.update = function (renderState, program) {
    // Should be implemented by the derived class
  };

  return this;
};

/**
 * Create new instance of class modelViewOriginUniform.
 *
 * @class
 * @alias vgl.modelViewUniform
 * @param {string} name
 * @param {number[]} origin a triplet of floats.
 * @returns {vgl.modelViewUniform}
 */
vgl.modelViewOriginUniform = function (name, origin) {
  'use strict';

  if (!(this instanceof vgl.modelViewOriginUniform)) {
    return new vgl.modelViewOriginUniform(name, origin);
  }

  if (!name) {
    name = 'modelViewMatrix';
  }
  origin = origin || [0, 0, 0];

  var m_origin = [origin[0], origin[1], origin[2] || 0];

  vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);

  this.set(mat4.create());

  /**
   * Change the origin used by the uniform view matrix.
   *
   * @param {number[]} origin a triplet of floats.
   */
  this.setOrigin = function (origin) {
    origin = origin || [0, 0, 0];
    m_origin = [origin[0], origin[1], origin[2] || 0];
  };

  /**
   * Update the uniform given a render state and shader program.  This offsets
   * the modelViewMatrix by the origin, and, if the model view should be
   * aligned, aligns it appropriately.  The alignment must be done after the
   * origin offset to maintain precision.
   *
   * @param {vgl.renderState} renderState
   * @param {vgl.shaderProgram} program
   */
  this.update = function (renderState, program) {
    var view = renderState.m_modelViewMatrix;
    if (renderState.m_modelViewAlignment) {
      /* adjust alignment before origin.  Otherwise, a changing origin can
       * affect the rounding choice and result in a 1 pixe jitter. */
      var align = renderState.m_modelViewAlignment;
      /* Don't modify the original matrix.  If we are in an environment where
       * you can't slice an Float32Array, switch to a regular array */
      view = view.slice ? view.slice() : Array.prototype.slice.call(view);
      /* view[12] and view[13] are the x and y offsets.  align.round is the
       * units-per-pixel, and align.dx and .dy are either 0 or half the size of
       * a unit-per-pixel.  The alignment guarantees that the texels are
       * aligned with screen pixels. */
      view[12] = Math.round(view[12] / align.roundx) * align.roundx + align.dx;
      view[13] = Math.round(view[13] / align.roundy) * align.roundy + align.dy;
    }
    view = mat4.translate(mat4.create(), view, m_origin);
    this.set(view);
  };

  return this;
};

inherit(vgl.modelViewOriginUniform, vgl.uniform);

/**
 * Create a new instance of class projectionUniform.
 *
 * @class
 * @alias vgl.projectionUniform
 * @param {string} name
 * @returns {vgl.projectionUniform}
 */
vgl.projectionUniform = function (name) {
  'use strict';

  if (!(this instanceof vgl.projectionUniform)) {
    return new vgl.projectionUniform(name);
  }

  if (!name) {
    name = 'projectionMatrix';
  }

  vgl.uniform.call(this, vgl.GL.FLOAT_MAT4, name);

  this.set(mat4.create());

  /**
   * Update the uniform given a render state and shader program.
   *
   * @param {vgl.renderState} renderState
   * @param {vgl.shaderProgram} program
   */
  this.update = function (renderState, program) {
    this.set(renderState.m_projectionMatrix);
  };

  return this;
};

inherit(vgl.projectionUniform, vgl.uniform);

/**
 * Create a new instance of class floatUniform.
 *
 * @class
 * @alias vgl.floatUniform
 * @param {string} name
 * @param {number} value
 * @returns {vgl.floatUniform}
 */
vgl.floatUniform = function (name, value) {
  'use strict';

  if (!(this instanceof vgl.floatUniform)) {
    return new vgl.floatUniform(name, value);
  }

  if (!name) {
    name = 'floatUniform';
  }

  value = value === undefined ? 1.0 : value;

  vgl.uniform.call(this, vgl.GL.FLOAT, name);

  this.set(value);
};

inherit(vgl.floatUniform, vgl.uniform);