/**
* @module kami
*/
var Class = require('klasse');
//TODO: decouple into VBO + IBO utilities
/**
* A mesh class that wraps VBO and IBO.
*
* @class Mesh
*/
var Mesh = new Class({
/**
* A write-only property which sets both vertices and indices
* flag to dirty or not.
*
* @property dirty
* @type {Boolean}
* @writeOnly
*/
dirty: {
set: function(val) {
this.verticesDirty = val;
this.indicesDirty = val;
}
},
/**
* Creates a new Mesh with the provided parameters.
*
* If numIndices is 0 or falsy, no index buffer will be used
* and indices will be an empty ArrayBuffer and a null indexBuffer.
*
* If isStatic is true, then vertexUsage and indexUsage will
* be set to gl.STATIC_DRAW. Otherwise they will use gl.DYNAMIC_DRAW.
* You may want to adjust these after initialization for further control.
*
* @param {WebGLContext} context the context for management
* @param {Boolean} isStatic a hint as to whether this geometry is static
* @param {[type]} numVerts [description]
* @param {[type]} numIndices [description]
* @param {[type]} vertexAttribs [description]
* @return {[type]} [description]
*/
initialize: function Mesh(context, isStatic, numVerts, numIndices, vertexAttribs) {
if (typeof context !== "object")
throw "GL context not specified to Mesh";
if (!numVerts)
throw "numVerts not specified, must be > 0";
this.context = context;
this.gl = context.gl;
this.numVerts = null;
this.numIndices = null;
this.vertices = null;
this.indices = null;
this.vertexBuffer = null;
this.indexBuffer = null;
this.verticesDirty = true;
this.indicesDirty = true;
this.indexUsage = null;
this.vertexUsage = null;
/**
* @property
* @private
*/
this._vertexAttribs = null;
/**
* The stride for one vertex _in bytes_.
*
* @property {Number} vertexStride
*/
this.vertexStride = null;
this.numVerts = numVerts;
this.numIndices = numIndices || 0;
this.vertexUsage = isStatic ? this.gl.STATIC_DRAW : this.gl.DYNAMIC_DRAW;
this.indexUsage = isStatic ? this.gl.STATIC_DRAW : this.gl.DYNAMIC_DRAW;
this._vertexAttribs = vertexAttribs || [];
this.indicesDirty = true;
this.verticesDirty = true;
//determine the vertex stride based on given attributes
var totalNumComponents = 0;
for (var i=0; i<this._vertexAttribs.length; i++)
totalNumComponents += this._vertexAttribs[i].offsetCount;
this.vertexStride = totalNumComponents * 4; // in bytes
this.vertices = new Float32Array(this.numVerts);
this.indices = new Uint16Array(this.numIndices);
//add this VBO to the managed cache
this.context.addManagedObject(this);
this.create();
},
//recreates the buffers on context loss
create: function() {
this.gl = this.context.gl;
var gl = this.gl;
this.vertexBuffer = gl.createBuffer();
//ignore index buffer if we haven't specified any
this.indexBuffer = this.numIndices > 0
? gl.createBuffer()
: null;
this.dirty = true;
},
destroy: function() {
this.vertices = null;
this.indices = null;
if (this.vertexBuffer && this.gl)
this.gl.deleteBuffer(this.vertexBuffer);
if (this.indexBuffer && this.gl)
this.gl.deleteBuffer(this.indexBuffer);
this.vertexBuffer = null;
this.indexBuffer = null;
if (this.context)
this.context.removeManagedObject(this);
this.gl = null;
this.context = null;
},
_updateBuffers: function(ignoreBind, subDataLength) {
var gl = this.gl;
//bind our index data, if we have any
if (this.numIndices > 0) {
if (!ignoreBind)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
//update the index data
if (this.indicesDirty) {
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, this.indices, this.indexUsage);
this.indicesDirty = false;
}
}
//bind our vertex data
if (!ignoreBind)
gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffer);
//update our vertex data
if (this.verticesDirty) {
if (subDataLength) {
// TODO: When decoupling VBO/IBO be sure to give better subData support..
var view = this.vertices.subarray(0, subDataLength);
gl.bufferSubData(gl.ARRAY_BUFFER, 0, view);
} else {
gl.bufferData(gl.ARRAY_BUFFER, this.vertices, this.vertexUsage);
}
this.verticesDirty = false;
}
},
draw: function(primitiveType, count, offset, subDataLength) {
if (count === 0)
return;
var gl = this.gl;
offset = offset || 0;
//binds and updates our buffers. pass ignoreBind as true
//to avoid binding unnecessarily
this._updateBuffers(true, subDataLength);
if (this.numIndices > 0) {
gl.drawElements(primitiveType, count,
gl.UNSIGNED_SHORT, offset * 2); //* Uint16Array.BYTES_PER_ELEMENT
} else
gl.drawArrays(primitiveType, offset, count);
},
//binds this mesh's vertex attributes for the given shader
bind: function(shader) {
var gl = this.gl;
var offset = 0;
var stride = this.vertexStride;
//bind and update our vertex data before binding attributes
this._updateBuffers();
//for each attribtue
for (var i=0; i<this._vertexAttribs.length; i++) {
var a = this._vertexAttribs[i];
//location of the attribute
var loc = a.location === null
? shader.getAttributeLocation(a.name)
: a.location;
//TODO: We may want to skip unfound attribs
// if (loc!==0 && !loc)
// console.warn("WARN:", a.name, "is not enabled");
//first, enable the vertex array
gl.enableVertexAttribArray(loc);
//then specify our vertex format
gl.vertexAttribPointer(loc, a.numComponents, a.type || gl.FLOAT,
a.normalize, stride, offset);
//and increase the offset...
offset += a.offsetCount * 4; //in bytes
}
},
unbind: function(shader) {
var gl = this.gl;
//for each attribtue
for (var i=0; i<this._vertexAttribs.length; i++) {
var a = this._vertexAttribs[i];
//location of the attribute
var loc = a.location === null
? shader.getAttributeLocation(a.name)
: a.location;
//first, enable the vertex array
gl.disableVertexAttribArray(loc);
}
}
});
Mesh.Attrib = new Class({
name: null,
numComponents: null,
location: null,
type: null,
/**
* Location is optional and for advanced users that
* want vertex arrays to match across shaders. Any non-numerical
* value will be converted to null, and ignored. If a numerical
* value is given, it will override the position of this attribute
* when given to a mesh.
*
* @param {[type]} name [description]
* @param {[type]} numComponents [description]
* @param {[type]} location [description]
* @return {[type]} [description]
*/
initialize: function(name, numComponents, location, type, normalize, offsetCount) {
this.name = name;
this.numComponents = numComponents;
this.location = typeof location === "number" ? location : null;
this.type = type;
this.normalize = Boolean(normalize);
this.offsetCount = typeof offsetCount === "number" ? offsetCount : this.numComponents;
}
})
module.exports = Mesh;