vgl/geomData.js

var vgl = require('./vgl');
var inherit = require('../inherit');
var timestamp = require('../timestamp');

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

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

  var m_primitiveType = 0,
      m_indicesValueType = 0,
      m_indices = null;

  /**
   * Get indices of the primitive.
   *
   * @returns {Uint16Array}
   */
  this.indices = function () {
    return m_indices;
  };

  /**
   * Return the number of indices.
   *
   * @returns {number} The number of indices.
   */
  this.numberOfIndices = function () {
    return m_indices.length;
  };

  /*
   * Return primitive type.
   *
   * @returns {number}
   */
  this.primitiveType = function () {
    return m_primitiveType;
  };

  /**
   * Set primitive type.
   *
   * @param {number} type The new type.
   */
  this.setPrimitiveType = function (type) {
    m_primitiveType = type;
  };

  /**
   * Return indices value type.
   *
   * @returns {number}
   */
  this.indicesValueType = function () {
    return m_indicesValueType;
  };

  /**
   * Set indices value type.
   *
   * @param {number} type
   */
  this.setIndicesValueType = function (type) {
    m_indicesValueType = type;
  };

  /**
   * Set indices from a array.
   *
   * @param {Array} indicesArray The array of new indices.
   */
  this.setIndices = function (indicesArray) {
    // TODO Check for the type
    m_indices = new Uint16Array(indicesArray);
  };

  return this;
};

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

  if (!(this instanceof vgl.triangles)) {
    return new vgl.triangles();
  }
  vgl.primitive.call(this);

  this.setPrimitiveType(vgl.GL.TRIANGLES);
  this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);

  return this;
};

inherit(vgl.triangles, vgl.primitive);

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

  if (!(this instanceof vgl.points)) {
    return new vgl.points();
  }
  vgl.primitive.call(this);

  this.setPrimitiveType(vgl.GL.POINTS);
  this.setIndicesValueType(vgl.GL.UNSIGNED_SHORT);

  return this;
};

inherit(vgl.points, vgl.primitive);

/**
 * Create a new instance of class sourceData.
 *
 * @class
 * @alias vgl.sourceData
 * @param {object} arg
 * @param {string?} arg.name Name of the source
 * @returns {vgl.sourceData}
 */
vgl.sourceData = function (arg) {
  'use strict';

  if (!(this instanceof vgl.sourceData)) {
    return new vgl.sourceData(arg);
  }

  arg = arg || {};
  var m_attributesMap = {},
      m_data = [],
      m_name = arg.name || 'Source ' + new Date().toISOString(),

      /* Attribute data for the source */
      vglAttributeData = function () {
        // Number of components per group
        // Type of data type (GL_FLOAT etc)
        this.m_numberOfComponents = 0;
        // Size of data type
        this.m_dataType = 0;
        this.m_dataTypeSize = 0;
        // Specifies whether fixed-point data values should be normalized
        // (true) or converted directly as fixed-point values (false)
        // when they are accessed.
        this.m_normalized = false;
        // Strides for each attribute.
        this.m_stride = 0;
        // Offset
        this.m_offset = 0;
      };

  /**
   * Return raw data for this source.
   *
   * @returns {Array|Float32Array}
   */
  this.data = function () {
    return m_data;
  };

  /**
   * Return raw data for this source.
   *
   * @returns {Array|Float32Array}
   */
  this.getData = function () {
    return this.data();
  };

  /**
   * If the raw data is not a Float32Array, convert it to one.  Then, return
   * raw data for this source.
   *
   * @returns {Float32Array}
   */
  this.dataToFloat32Array = function () {
    if (!(m_data instanceof Float32Array)) {
      m_data = new Float32Array(m_data);
    }
    return m_data;
  };

  /**
   * Set data for this source.
   *
   * @param {Array|Float32Array} data
   */
  this.setData = function (data) {
    if (!(data instanceof Array) && !(data instanceof Float32Array)) {
      console.log('[error] Requires array');
      return;
    }
    if (data instanceof Float32Array) {
      m_data = data;
    } else {
      m_data = data.slice(0);
    }
  };

  /**
   * Add new attribute data to the source.
   *
   * @param {string} key Attribute key.
   * @param {number} dataType
   * @param {number} sizeOfDataType
   * @param {number} offset
   * @param {number} stride
   * @param {number} noOfComponents
   * @param {boolean} normalized
   */
  this.addAttribute = function (key, dataType, sizeOfDataType, offset, stride,
    noOfComponents, normalized) {

    if (!m_attributesMap.hasOwnProperty(key)) {
      var newAttr = new vglAttributeData();
      newAttr.m_dataType = dataType;
      newAttr.m_dataTypeSize = sizeOfDataType;
      newAttr.m_offset = offset;
      newAttr.m_stride = stride;
      newAttr.m_numberOfComponents = noOfComponents;
      newAttr.m_normalized = normalized;
      m_attributesMap[key] = newAttr;
    }
  };

  /**
   * Check if there is attribute exists of a given key type.
   *
   * @param {string} key Attribute key.
   * @returns {boolean}
   */
  this.hasKey = function (key) {
    return m_attributesMap.hasOwnProperty(key);
  };

  /**
   * Return keys of all attributes.
   *
   * @returns {string[]}
   */
  this.keys = function () {
    return Object.keys(m_attributesMap);
  };

  /**
   * Return number of components of the attribute data.
   *
   * @param {string} key Attribute key.
   * @returns {number}
   */
  this.attributeNumberOfComponents = function (key) {
    if (m_attributesMap.hasOwnProperty(key)) {
      return m_attributesMap[key].m_numberOfComponents;
    }

    return 0;
  };

  /**
   * Return if the attribute data is normalized.
   *
   * @param {string} key Attribute key.
   * @returns {boolean}
   */
  this.normalized = function (key) {
    if (m_attributesMap.hasOwnProperty(key)) {
      return m_attributesMap[key].m_normalized;
    }

    return false;
  };

  /**
   * Return attribute data type.
   *
   * @param {string} key Attribute key.
   * @returns {number}
   */
  this.attributeDataType = function (key) {
    if (m_attributesMap.hasOwnProperty(key)) {
      return m_attributesMap[key].m_dataType;
    }

    return undefined;
  };

  /**
   * Return attribute offset.
   *
   * @param {string} key Attribute key.
   * @returns {number}
   */
  this.attributeOffset = function (key) {
    if (m_attributesMap.hasOwnProperty(key)) {
      return m_attributesMap[key].m_offset;
    }

    return 0;
  };

  /**
   * Return attribute stride.
   *
   * @param {string} key Attribute key.
   * @returns {number}
   */
  this.attributeStride = function (key) {
    if (m_attributesMap.hasOwnProperty(key)) {
      return m_attributesMap[key].m_stride;
    }

    return 0;
  };

  /**
   * Virtual function to insert new vertex data at the end.
   *
   * @param {number|Array} vertexData
   */
  this.pushBack = function (vertexData) {
    // Should be implemented by the base class
  };

  /**
   * Insert new data block to the raw data.
   *
   * @param {number[]|Float32Array} data
   */
  this.insert = function (data) {
    var i;

    /* If we will are given a Float32Array and don't have any other data, use
     * it directly. */
    if (!m_data.length && data.length && data instanceof Float32Array) {
      m_data = data;
      return;
    }
    /* If our internal array is immutable and we will need to change it, create
     * a regular mutable array from it. */
    if (!m_data.slice && (m_data.length || !data.slice)) {
      m_data = Array.prototype.slice.call(m_data);
    }
    if (!data.length) {
      /* data is a singular value, so append it to our array */
      m_data[m_data.length] = data;
    } else {
      /* We don't have any data currently, so it is faster to copy the data
       * using slice. */
      if (!m_data.length && data.slice) {
        m_data = data.slice(0);
      } else {
        for (i = 0; i < data.length; i += 1) {
          m_data[m_data.length] = data[i];
        }
      }
    }
  };

  /**
   * Return name of the source data.
   *
   * @returns {string}
   */
  this.name = function () {
    return m_name;
  };

  return this;
};

/**
 * Create a new instance of class sourceDataP3fv.
 *
 * @class
 * @alias vgl.sourceDataAnyfv
 * @param {number} size Number of sets of 4 floats.
 * @param {string} key Attribute key.
 * @param {object} arg Argument to pass to parent class.
 * @returns {vgl.sourceDataAnyfv}
 */
vgl.sourceDataAnyfv = function (size, key, arg) {
  'use strict';
  if (!(this instanceof vgl.sourceDataAnyfv)) {
    return new vgl.sourceDataAnyfv(size, key, arg);
  }

  vgl.sourceData.call(this, arg);
  this.addAttribute(key, vgl.GL.FLOAT,
                    4, 0, size * 4, size, false);

  this.pushBack = function (value) {
    this.insert(value);
  };

  return this;
};

inherit(vgl.sourceDataAnyfv, vgl.sourceData);

/**
 * Create a new instance of class sourceDataP3fv.
 *
 * @class
 * @alias vgl.sourceDataP3fv
 * @param {object} arg Object to pass to parent class.
 * @returns {vgl.sourceDataP3fv}
 */
vgl.sourceDataP3fv = function (arg) {
  'use strict';

  if (!(this instanceof vgl.sourceDataP3fv)) {
    return new vgl.sourceDataP3fv(arg);
  }

  vgl.sourceData.call(this, arg);

  this.addAttribute(vgl.vertexAttributeKeys.Position, vgl.GL.FLOAT, 4, 0, 3 * 4, 3,
                    false);

  this.pushBack = function (value) {
    this.insert(value);
  };

  return this;
};

inherit(vgl.sourceDataP3fv, vgl.sourceData);

/**
 * Create a new instance of class sourceDataT2fv.
 *
 * @class
 * @alias vgl.sourceDataT2fv
 * @param {object} arg Object to pass to parent class.
 * @returns {vgl.sourceDataT2fv}
 */
vgl.sourceDataT2fv = function (arg) {
  'use strict';

  if (!(this instanceof vgl.sourceDataT2fv)) {
    return new vgl.sourceDataT2fv(arg);
  }

  vgl.sourceData.call(this, arg);

  this.addAttribute(vgl.vertexAttributeKeys.TextureCoordinate, vgl.GL.FLOAT, 4, 0,
                    2 * 4, 2, false);

  this.pushBack = function (value) {
    this.insert(value);
  };

  return this;
};

inherit(vgl.sourceDataT2fv, vgl.sourceData);

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

  if (!(this instanceof vgl.geometryData)) {
    return new vgl.geometryData();
  }
  vgl.data.call(this);

  var m_name = '',
      m_primitives = [],
      m_sources = [],
      m_bounds = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
      m_computeBoundsTimestamp = timestamp(),
      m_boundsDirtyTimestamp = timestamp();

  /**
   * Return type.
   *
   * @returns {number}
   */
  this.type = function () {
    return vgl.data.geometry;
  };

  /**
   * Return ID of the geometry data.
   *
   * @returns {string}
   */
  this.name = function () {
    return m_name;
  };

  /**
   * Add new source.
   *
   * @param {vgl.sourceData} source
   * @returns {boolean} True is the source was added.
   */
  this.addSource = function (source) {
    if (m_sources.indexOf(source) === -1) {
      m_sources.push(source);

      if (source.hasKey(vgl.vertexAttributeKeys.Position)) {
        m_boundsDirtyTimestamp.modified();
      }
      return true;
    }

    return false;
  };

  /**
   * Return source for a given index. Returns 0 if not found.
   *
   * @param {number} index
   * @returns {vgl.sourceData|number}
   */
  this.source = function (index) {
    if (index < m_sources.length) {
      return m_sources[index];
    }

    return 0;
  };

  /**
   * Return source with a specified name.  Returns 0 if not found.
   *
   * @param {string} sourceName
   * @returns {vgl.sourceData|number}
   */
  this.sourceByName = function (sourceName) {
    for (var i = 0; i < m_sources.length; i += 1) {
      if (m_sources[i].name() === sourceName) {
        return m_sources[i];
      }
    }
    return 0;
  };

  /**
   * Return number of sources.
   *
   * @returns {number}
   */
  this.numberOfSources = function () {
    return m_sources.length;
  };

  /**
   * Return source data given a key.
   *
   * @param {string} key
   * @returns {vgl.sourceData|null}
   */
  this.sourceData = function (key) {
    var i;

    for (i = 0; i < m_sources.length; i += 1) {
      if (m_sources[i].hasKey(key)) {
        return m_sources[i];
      }
    }

    return null;
  };

  /**
   * Add new primitive.
   *
   * @param {vgl.primitive} primitive
   * @returns {boolean}
   */
  this.addPrimitive = function (primitive) {
    m_primitives.push(primitive);
    return true;
  };

  /**
   * Return primitive for a given index. Returns null if not found.
   *
   * @param {number} index
   * @returns {vgl.primitive|null}
   */
  this.primitive = function (index) {
    if (index < m_primitives.length) {
      return m_primitives[index];
    }

    return null;
  };

  /**
   * Return number of primitives.
   *
   * @returns {number}
   */
  this.numberOfPrimitives = function () {
    return m_primitives.length;
  };

  /**
   * Return bounds.
   *
   * @returns {number[]} Array of minX, maxX, minY, maxY, minZ, maxZ.
   */
  this.bounds = function () {
    if (m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime()) {
      this.computeBounds();
    }
    return m_bounds;
  };

  /**
   * Check if bounds are dirty or mark them as such.
   *
   * @param {boolean} dirty true to set bounds as dirty.
   * @returns {boolean} true if bounds are dirty.
   */
  this.boundsDirty = function (dirty) {
    if (dirty) {
      m_boundsDirtyTimestamp.modified();
    }
    return m_boundsDirtyTimestamp.getMTime() > m_computeBoundsTimestamp.getMTime();
  };

  /**
   * Set bounds.
   *
   * @param {number} minX
   * @param {number} maxX
   * @param {number} minY
   * @param {number} maxY
   * @param {number} minZ
   * @param {number} maxZ
   * @returns {boolean} True if set.
   */
  this.setBounds = function (minX, maxX, minY, maxY, minZ, maxZ) {
    m_bounds[0] = minX;
    m_bounds[1] = maxX;
    m_bounds[2] = minY;
    m_bounds[3] = maxY;
    m_bounds[4] = minZ;
    m_bounds[5] = maxZ;

    m_computeBoundsTimestamp.modified();

    return true;
  };

  return this;
};

inherit(vgl.geometryData, vgl.data);