webgl/meshColored.js

/**
 * Create a new instance of meshColored.
 *
 * @class
 * @alias geo.webgl.meshColored
 * @param {geo.meshColored.spec} arg
 * @returns {geo.webgl.meshColored}
 */
var webgl_meshColored = function (arg) {
  'use strict';

  arg = arg || {};

  var vgl = require('../vgl');
  var transform = require('../transform');
  var util = require('../util');
  var fragmentShader = require('./meshColored.frag');
  var vertexShader = require('./meshColored.vert');

  var object = require('./object');
  object.call(this);

  /**
   * @private
   */
  var m_this = this,
      s_exit = this._exit,
      m_textureUnit = 7,
      m_actor = null,
      m_mapper = null,
      m_material = null,
      m_texture = null,
      m_minColorUniform = null,
      m_maxColorUniform = null,
      m_stepsUniform = null,
      m_steppedUniform = null,
      m_dynamicDraw = arg.dynamicDraw === undefined ? false : arg.dynamicDraw,
      m_origin,
      m_modelViewUniform,
      s_init = this._init,
      s_update = this._update;

  function createVertexShader() {
    var shader = new vgl.shader(vgl.GL.VERTEX_SHADER);
    shader.setShaderSource(vertexShader);
    return shader;
  }

  function createFragmentShader() {
    var shader = new vgl.shader(vgl.GL.FRAGMENT_SHADER);
    shader.setShaderSource(fragmentShader);
    return shader;
  }

  /**
   * Create the colored mesh.  The generated geometry is loaded into the
   * various gl uniforms and buffers.
   *
   * @param {geo.meshFeature.meshColoredInfo} mesh The mesh to draw.
   */
  this.createGLMeshColored = function (mesh) {
    var numPts = mesh.elements.length,
        colorTable = [],
        i, i3, j, j3, e,
        posBuf, opacityBuf, valueBuf, indicesBuf,
        geom = m_mapper.geometryData();

    m_minColorUniform.set([
      mesh.minColor.r,
      mesh.minColor.g,
      mesh.minColor.b,
      mesh.minColor.a]);
    m_maxColorUniform.set([
      mesh.maxColor.r,
      mesh.maxColor.g,
      mesh.maxColor.b,
      mesh.maxColor.a]);
    m_stepsUniform.set(mesh.colorMap.length);
    m_steppedUniform.set(mesh.stepped);
    // pad the colortable by repeating the end colors an extra time to ensure
    // interpolation never goes off of the colormap.
    for (i = -1; i < mesh.colorMap.length + 1; i += 1) {
      j = Math.max(0, Math.min(mesh.colorMap.length - 1, i));
      colorTable.push(mesh.colorMap[j].r * 255);
      colorTable.push(mesh.colorMap[j].g * 255);
      colorTable.push(mesh.colorMap[j].b * 255);
      colorTable.push(mesh.colorMap[j].a * 255);
    }
    m_texture.setColorTable(colorTable);
    mesh.pos = transform.transformCoordinates(
      m_this.gcs(), m_this.layer().map().gcs(), mesh.pos, 3);
    m_origin = new Float32Array(m_this.style.get('origin')(mesh.pos));
    if (m_origin[0] || m_origin[1] || m_origin[2]) {
      for (i = 0; i < mesh.pos.length; i += 3) {
        mesh.pos[i] -= m_origin[0];
        mesh.pos[i + 1] -= m_origin[1];
        mesh.pos[i + 2] -= m_origin[2];
      }
    }
    m_modelViewUniform.setOrigin(m_origin);

    posBuf = util.getGeomBuffer(geom, 'pos', numPts * 3);
    opacityBuf = util.getGeomBuffer(geom, 'opacity', numPts);
    valueBuf = util.getGeomBuffer(geom, 'value', numPts);
    for (i = i3 = 0; i < numPts; i += 1, i3 += 3) {
      j = mesh.elements[i];
      j3 = j * 3;
      posBuf[i3] = mesh.pos[j3];
      posBuf[i3 + 1] = mesh.pos[j3 + 1];
      posBuf[i3 + 2] = mesh.pos[j3 + 2];
      e = mesh.elementValues ? Math.floor(i / 3) : j;
      opacityBuf[i] = mesh.opacity[e];
      valueBuf[i] = mesh.value[e];
    }
    indicesBuf = geom.primitive(0).indices();
    if (!(indicesBuf instanceof Uint16Array) || indicesBuf.length !== numPts) {
      indicesBuf = new Uint16Array(numPts);
      geom.primitive(0).setIndices(indicesBuf);
    }
    geom.boundsDirty(true);
    m_mapper.modified();
    m_mapper.boundsDirtyTimestamp().modified();
  };

  /**
   * Initialize.
   *
   * @param {geo.meshColored.spec} arg The contour feature specification.
   */
  this._init = function (arg) {
    var blend = vgl.blend(),
        prog = vgl.shaderProgram(),
        mat = vgl.material(),
        tex = vgl.lookupTable(),
        geom = vgl.geometryData(),
        projectionUniform = new vgl.projectionUniform('projectionMatrix'),
        samplerUniform = new vgl.uniform(vgl.GL.INT, 'sampler2d'),
        vertexShader = createVertexShader(),
        fragmentShader = createFragmentShader(),
        posAttr = vgl.vertexAttribute('pos'),
        valueAttr = vgl.vertexAttribute('value'),
        opacityAttr = vgl.vertexAttribute('opacity'),
        sourcePositions = vgl.sourceDataP3fv({name: 'pos'}),
        sourceValues = vgl.sourceDataAnyfv(
          1, vgl.vertexAttributeKeysIndexed.One, {name: 'value'}),
        sourceOpacity = vgl.sourceDataAnyfv(
          1, vgl.vertexAttributeKeysIndexed.Two, {name: 'opacity'}),
        primitive = new vgl.triangles();
    m_modelViewUniform = new vgl.modelViewOriginUniform('modelViewMatrix');

    s_init.call(m_this, arg);
    m_mapper = vgl.mapper({dynamicDraw: m_dynamicDraw});

    prog.addVertexAttribute(posAttr, vgl.vertexAttributeKeys.Position);
    prog.addVertexAttribute(valueAttr, vgl.vertexAttributeKeysIndexed.One);
    prog.addVertexAttribute(opacityAttr, vgl.vertexAttributeKeysIndexed.Two);

    prog.addUniform(m_modelViewUniform);
    prog.addUniform(projectionUniform);
    m_minColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'minColor');
    prog.addUniform(m_minColorUniform);
    m_maxColorUniform = new vgl.uniform(vgl.GL.FLOAT_VEC4, 'maxColor');
    prog.addUniform(m_maxColorUniform);
    /* steps is always an integer, but it is more efficient if we use a float
     */
    m_stepsUniform = new vgl.uniform(vgl.GL.FLOAT, 'steps');
    prog.addUniform(m_stepsUniform);
    m_steppedUniform = new vgl.uniform(vgl.GL.BOOL, 'stepped');
    prog.addUniform(m_steppedUniform);

    prog.addShader(fragmentShader);
    prog.addShader(vertexShader);

    prog.addUniform(samplerUniform);
    tex.setTextureUnit(m_textureUnit);
    samplerUniform.set(m_textureUnit);

    m_material = mat;
    m_material.addAttribute(prog);
    m_material.addAttribute(blend);
    m_texture = tex;
    m_material.addAttribute(m_texture);

    m_actor = vgl.actor();
    m_actor.setMaterial(m_material);
    m_actor.setMapper(m_mapper);

    geom.addSource(sourcePositions);
    geom.addSource(sourceValues);
    geom.addSource(sourceOpacity);
    geom.addPrimitive(primitive);
    /* We don't need vgl to compute bounds, so make the geo.computeBounds just
     * set them to 0. */
    geom.computeBounds = function () {
      geom.setBounds(0, 0, 0, 0, 0, 0);
    };
    m_mapper.setGeometryData(geom);
  };

  /**
   * List vgl actors.
   *
   * @returns {vgl.actor[]} The list of actors.
   */
  this.actors = function () {
    return m_actor ? [m_actor] : [];
  };

  /**
   * Update.
   */
  this._update = function () {
    s_update.call(m_this);

    if (m_this.dataTime().timestamp() >= m_this.buildTime().timestamp() ||
        m_this.updateTime().timestamp() <= m_this.timestamp()) {
      m_this._build();
    }

    m_actor.setVisible(m_this.visible());
    m_actor.material().setBinNumber(m_this.bin());
    m_this.updateTime().modified();
  };

  /**
   * Destroy.
   */
  this._exit = function () {
    m_this.renderer().contextRenderer().removeActor(m_actor);
    s_exit();
  };

  return this;
};

module.exports = webgl_meshColored;