API Docs for:
Show:

File: ../src/mesh.js

/**
* @namespace GL
*/

/**
* Indexer used to reuse vertices among a mesh
* @class Indexer
* @constructor
*/
GL.Indexer = function Indexer() {
  this.unique = [];
  this.indices = [];
  this.map = {};
}
GL.Indexer.prototype = {
	add: function(obj) {
    var key = JSON.stringify(obj);
    if (!(key in this.map)) {
      this.map[key] = this.unique.length;
      this.unique.push(obj);
    }
    return this.map[key];
  }
};

/**
* A data buffer to be stored in the GPU
* @class Buffer
* @constructor
* @param {Number} target gl.ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER
* @param {ArrayBufferView} data the data in typed-array format
* @param {number} spacing number of numbers per component (3 per vertex, 2 per uvs...), default 3
* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW 
*/
GL.Buffer = function Buffer( target, data, spacing, stream_type, gl ) {
	if(GL.debug)
		console.log("GL.Buffer created");

	if(gl !== null)
		gl = gl || global.gl;
	this.gl = gl;

	this.buffer = null; //webgl buffer
	this.target = target; //GL.ARRAY_BUFFER, GL.ELEMENT_ARRAY_BUFFER
	this.attribute = null; //name of the attribute in the shader ("a_vertex","a_normal","a_coord",...)

	//optional
	this.data = data;
	this.spacing = spacing || 3;

	if(this.data && this.gl)
		this.upload(stream_type);
}

/**
* Applies an action to every vertex in this buffer
* @method forEach
* @param {function} callback to be called for every vertex (or whatever is contained in the buffer)
*/
GL.Buffer.prototype.forEach = function(callback)
{
	var d = this.data;
	for (var i = 0, s = this.spacing, l = d.length; i < l; i += s)
	{
		callback(d.subarray(i,i+s),i);
	}
	return this; //to concatenate
}

/**
* Applies a mat4 transform to every triplets in the buffer (assuming they are points)
* No upload is performed (to ensure efficiency in case there are several operations performed)
* @method applyTransform
* @param {mat4} mat
*/
GL.Buffer.prototype.applyTransform = function(mat)
{
	var d = this.data;
	for (var i = 0, s = this.spacing, l = d.length; i < l; i += s)
	{
		var v = d.subarray(i,i+s);
		vec3.transformMat4(v,v,mat);
	}
	return this; //to concatenate
}

/**
* Uploads the buffer data (stored in this.data) to the GPU
* @method upload
* @param {number} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW 
*/
GL.Buffer.prototype.upload = function( stream_type ) { //default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW )
	var spacing = this.spacing || 3; //default spacing	
	var gl = this.gl;
	if(!gl)
		return;

	if(!this.data)
		throw("No data supplied");

	var data = this.data;
	if(!data.buffer)
		throw("Buffers must be typed arrays");

	//I store some stuff inside the WebGL buffer instance, it is supported
	this.buffer = this.buffer || gl.createBuffer();
	if(!this.buffer)
		return; //if the context is lost...

	this.buffer.length = data.length;
	this.buffer.spacing = spacing;

	//store the data format
	switch( data.constructor )
	{
		case Int8Array: this.buffer.gl_type = gl.BYTE; break;
		case Uint8ClampedArray: 
		case Uint8Array: this.buffer.gl_type = gl.UNSIGNED_BYTE; break;
		case Int16Array: this.buffer.gl_type = gl.SHORT; break;
		case Uint16Array: this.buffer.gl_type = gl.UNSIGNED_SHORT; break;
		case Int32Array: this.buffer.gl_type = gl.INT; break;
		case Uint32Array: this.buffer.gl_type = gl.UNSIGNED_INT; break;
		case Float32Array: this.buffer.gl_type = gl.FLOAT; break;
		default: throw("unsupported buffer type");
	}

	gl.bindBuffer(this.target, this.buffer);
	gl.bufferData(this.target, data , stream_type || this.stream_type || gl.STATIC_DRAW);
};
//legacy
GL.Buffer.prototype.compile = GL.Buffer.prototype.upload;


/**
* Assign data to buffer and uploads it (it allows range)
* @method setData
* @param {ArrayBufferView} data in Float32Array format usually
* @param {number} offset offset in bytes
*/
GL.Buffer.prototype.setData = function( data, offset )
{
	if(!data.buffer)
		throw("Data must be typed array");
	offset = offset || 0;

	if(!this.data)
	{
		this.data = data;
		this.upload();
		return;
	}
	else if( this.data.length < data.length )
		throw("buffer is not big enough, you cannot set data to a smaller buffer");

	if(this.data != data)
	{
		if(this.data.length == data.length)
		{
			this.data.set( data );
			this.upload();
			return;
		}

		//upload just part of it
		var new_data_view = new Uint8Array( data.buffer, data.buffer.byteOffset, data.buffer.byteLength );
		var data_view = new Uint8Array( this.data.buffer );
		data_view.set( new_data_view, offset );
		this.uploadRange( offset, new_data_view.length );
	}

};


/**
* Uploads part of the buffer data (stored in this.data) to the GPU
* @method uploadRange
* @param {number} start offset in bytes
* @param {number} size sizes in bytes
*/
GL.Buffer.prototype.uploadRange = function(start, size)
{
	if(!this.data)
		throw("No data stored in this buffer");

	var data = this.data;
	if(!data.buffer)
		throw("Buffers must be typed arrays");

	var view = new Uint8Array( this.data.buffer, start, size );

	var gl = this.gl;
	gl.bindBuffer(this.target, this.buffer);
	gl.bufferSubData(this.target, start, view );
};

/**
* Clones one buffer (it allows to share the same data between both buffers)
* @method clone
* @param {boolean} share if you want that both buffers share the same data (default false)
* return {GL.Buffer} buffer cloned
*/
GL.Buffer.prototype.clone = function(share)
{
	var buffer = new GL.Buffer();
	if(share)
	{
		for(var i in this)
			buffer[i] = this[i];
	}
	else
	{
		if(this.target)
			buffer.target = this.target;
		if(this.gl)
			buffer.gl = this.gl;
		if(this.spacing)
			buffer.spacing = this.spacing;
		if(this.data) //clone data
		{
			buffer.data = new global[ this.data.constructor ]( this.data );
			buffer.upload();
		}
	}
	return buffer;
}


GL.Buffer.prototype.toJSON = function()
{
	if(!this.data)
	{
		console.error("cannot serialize a mesh without data");
		return null;
	}

	return {
		data_type: getClassName(this.data),
		data: this.data.toJSON(),
		target: this.target,
		attribute: this.attribute,
		spacing: this.spacing
	};
}

GL.Buffer.prototype.fromJSON = function(o)
{
	var data_type = global[ o.data_type ] || Float32Array;
	this.data = new data_type( o.data ); //cloned
	this.target = o.target;
	this.spacing = o.spacing || 3;
	this.attribute = o.attribute;
	this.upload( GL.STATIC_DRAW );
}

/**
* Deletes the content from the GPU and destroys the handler
* @method delete
*/
GL.Buffer.prototype.delete = function()
{
	var gl = this.gl;
	gl.deleteBuffer( this.buffer );
	this.buffer = null;
}

/**
* Base class for meshes, it wraps several buffers and some global info like the bounding box
* @class Mesh
* @param {Object} vertexBuffers object with all the vertex streams
* @param {Object} indexBuffers object with all the indices streams
* @param {Object} options
* @param {WebGLContext} gl [Optional] gl context where to create the mesh
* @constructor
*/
global.Mesh = GL.Mesh = function Mesh( vertexbuffers, indexbuffers, options, gl )
{
	if(GL.debug)
		console.log("GL.Mesh created");

	if( gl !== null )
	{
		gl = gl || global.gl;
		this.gl = gl;
	}

	//used to avoid problems with resources moving between different webgl context
	this._context_id = gl.context_id; 

	this.vertexBuffers = {};
	this.indexBuffers = {};

	//here you can store extra info, like groups, which is an array of { name, start, length, material }
	this.info = {
		groups: []
	}; 
	this._bounding = BBox.create(); //here you can store a AABB in BBox format

	if(vertexbuffers || indexbuffers)
		this.addBuffers( vertexbuffers, indexbuffers, options ? options.stream_type : null );

	if(options)
		for(var i in options)
			this[i] = options[i];
};

Mesh.common_buffers = {
	"vertices": { spacing:3, attribute: "a_vertex"},
	"vertices2D": { spacing:2, attribute: "a_vertex2D"},
	"normals": { spacing:3, attribute: "a_normal"},
	"coords": { spacing:2, attribute: "a_coord"},
	"coords1": { spacing:2, attribute: "a_coord1"},
	"coords2": { spacing:2, attribute: "a_coord2"},
	"colors": { spacing:4, attribute: "a_color"}, 
	"tangents": { spacing:3, attribute: "a_tangent"},
	"bone_indices": { spacing:4, attribute: "a_bone_indices", type: Uint8Array },
	"weights": { spacing:4, attribute: "a_weights"},
	"extra": { spacing:1, attribute: "a_extra"},
	"extra2": { spacing:2, attribute: "a_extra2"},
	"extra3": { spacing:3, attribute: "a_extra3"},
	"extra4": { spacing:4, attribute: "a_extra4"}
};

Mesh.default_datatype = Float32Array;

Object.defineProperty( Mesh.prototype, "bounding", {
	set: function(v)
	{
		if(!v)
			return;
		if(v.length < 13)
			throw("Bounding must use the BBox bounding format of 13 floats: center, halfsize, min, max, radius");
		this._bounding.set(v);
	},
	get: function()
	{
		return this._bounding;
	}
});

/**
* Adds buffer to mesh
* @method addBuffer
* @param {string} name
* @param {Buffer} buffer 
*/

Mesh.prototype.addBuffer = function(name, buffer)
{
	if(buffer.target == gl.ARRAY_BUFFER)
		this.vertexBuffers[name] = buffer;
	else
		this.indexBuffers[name] = buffer;

	if(!buffer.attribute)
		buffer.attribute = GL.Mesh.common_buffers[name].attribute;
}


/**
* Adds vertex and indices buffers to a mesh
* @method addBuffers
* @param {Object} vertexBuffers object with all the vertex streams
* @param {Object} indexBuffers object with all the indices streams
* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW )
*/
Mesh.prototype.addBuffers = function( vertexbuffers, indexbuffers, stream_type )
{
	var num_vertices = 0;

	if(this.vertexBuffers["vertices"])
		num_vertices = this.vertexBuffers["vertices"].data.length / 3;

	for(var i in vertexbuffers)
	{
		var data = vertexbuffers[i];
		if(!data) 
			continue;
		
		if( data.constructor == GL.Buffer )
		{
			data = data.data;
		}
		else if( typeof(data[0]) != "number") //linearize: (transform Arrays in typed arrays)
		{
			var newdata = [];
			for (var j = 0, chunk = 10000; j < data.length; j += chunk) {
			  newdata = Array.prototype.concat.apply(newdata, data.slice(j, j + chunk));
			}
			data = newdata;
		}

		var stream_info = GL.Mesh.common_buffers[i];

		//cast to typed float32 if no type is specified
		if(data.constructor === Array)
		{
			var datatype = GL.Mesh.default_datatype;
			if(stream_info && stream_info.type)
				datatype = stream_info.type;
			data = new datatype( data );
		}

		//compute spacing
		if(i == "vertices")
			num_vertices = data.length / 3;
		var spacing = data.length / num_vertices;
		if(stream_info && stream_info.spacing)
			spacing = stream_info.spacing;

		//add and upload
		var attribute = "a_" + i;
		if(stream_info && stream_info.attribute)
			attribute = stream_info.attribute;
	
		if( this.vertexBuffers[i] )
			this.updateVertexBuffer( i, attribute, spacing, data, stream_type );
		else
			this.createVertexBuffer( i, attribute, spacing, data, stream_type );
	}

	if(indexbuffers)
		for(var i in indexbuffers)
		{
			var data = indexbuffers[i];
			if(!data) continue;

			if( data.constructor == GL.Buffer )
			{
				data = data.data;
			}
			if( typeof(data[0]) != "number") //linearize
			{
				newdata = [];
				for (var i = 0, chunk = 10000; i < data.length; i += chunk) {
				  newdata = Array.prototype.concat.apply(newdata, data.slice(i, i + chunk));
				}
				data = newdata;
			}

			//cast to typed
			if(data.constructor === Array)
			{
				var datatype = Uint16Array;
				if(num_vertices > 256*256)
					datatype = Uint32Array;
				data = new datatype( data );
			}

			this.createIndexBuffer( i, data );
		}
}

/**
* Creates a new empty buffer and attachs it to this mesh
* @method createVertexBuffer
* @param {String} name "vertices","normals"...
* @param {String} attribute name of the stream in the shader "a_vertex","a_normal",... [optional, if omitted is used the common_buffers]
* @param {number} spacing components per vertex [optional, if ommited is used the common_buffers, if not found then uses 3 ]
* @param {ArrayBufferView} buffer_data the data in typed array format [optional, if ommited it created an empty array of getNumVertices() * spacing]
* @param {enum} stream_type [optional, default = gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW ) ]
*/

Mesh.prototype.createVertexBuffer = function( name, attribute, buffer_spacing, buffer_data, stream_type ) {

	var common = GL.Mesh.common_buffers[name]; //generic info about a buffer with the same name

	if (!attribute && common)
		attribute = common.attribute;

	if (!attribute)
		throw("Buffer added to mesh without attribute name");

	if (!buffer_spacing && common)
	{
		if(common && common.spacing)
			buffer_spacing = common.spacing;
		else
			buffer_spacing = 3;
	}

	if(!buffer_data)
	{
		var num = this.getNumVertices();
		if(!num)
			throw("Cannot create an empty buffer in a mesh without vertices (vertices are needed to know the size)");
		buffer_data = new (GL.Mesh.default_datatype)(num * buffer_spacing);
	}

	if(!buffer_data.buffer)
		throw("Buffer data MUST be typed array");

	//used to ensure the buffers are held in the same gl context as the mesh
	var buffer = this.vertexBuffers[name] = new GL.Buffer( gl.ARRAY_BUFFER, buffer_data, buffer_spacing, stream_type, this.gl );
	buffer.name = name;
	buffer.attribute = attribute;

	return buffer;
}

/**
* Updates a vertex buffer 
* @method updateVertexBuffer
* @param {String} name the name of the buffer
* @param {String} attribute the name of the attribute in the shader
* @param {number} spacing number of numbers per component (3 per vertex, 2 per uvs...), default 3
* @param {*} data the array with all the data
* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW 
*/
Mesh.prototype.updateVertexBuffer = function( name, attribute, buffer_spacing, buffer_data, stream_type ) {
	var buffer = this.vertexBuffers[name];
	if(!buffer)
	{
		console.log("buffer not found: ",name);
		return;
	}

	if(!buffer_data.length)
		return;

	buffer.attribute = attribute;
	buffer.spacing = buffer_spacing;
	buffer.data = buffer_data;
	buffer.upload( stream_type );
}


/**
* Removes a vertex buffer from the mesh
* @method removeVertexBuffer
* @param {String} name "vertices","normals"...
* @param {Boolean} free if you want to remove the data from the GPU
*/
Mesh.prototype.removeVertexBuffer = function(name, free) {
	var buffer = this.vertexBuffers[name];
	if(!buffer)
		return;
	if(free)
		buffer.delete();
	delete this.vertexBuffers[name];
}

/**
* Returns a vertex buffer
* @method getVertexBuffer
* @param {String} name of vertex buffer
* @return {Buffer} the buffer
*/
Mesh.prototype.getVertexBuffer = function(name)
{
	return this.vertexBuffers[name];
}


/**
* Creates a new empty index buffer and attachs it to this mesh
* @method createIndexBuffer
* @param {String} name 
* @param {Typed array} data 
* @param {enum} stream_type gl.STATIC_DRAW, gl.DYNAMIC_DRAW, gl.STREAM_DRAW
*/
Mesh.prototype.createIndexBuffer = function(name, buffer_data, stream_type) {
	//(target, data, spacing, stream_type, gl)

	//cast to typed
	if(buffer_data.constructor === Array)
	{
		var datatype = Uint16Array;
		var vertices = this.vertexBuffers["vertices"];
		if(vertices)
		{
			var num_vertices = vertices.data.length / 3;
			if(num_vertices > 256*256)
				datatype = Uint32Array;
			buffer_data = new datatype( buffer_data );
		}
	}

	var buffer = this.indexBuffers[name] = new GL.Buffer(gl.ELEMENT_ARRAY_BUFFER, buffer_data, 0, stream_type, this.gl );
	return buffer;
}

/**
* Returns a vertex buffer
* @method getBuffer
* @param {String} name of vertex buffer
* @return {Buffer} the buffer
*/
Mesh.prototype.getBuffer = function(name)
{
	return this.vertexBuffers[name];
}

/**
* Returns a index buffer
* @method getIndexBuffer
* @param {String} name of index buffer
* @return {Buffer} the buffer
*/
Mesh.prototype.getIndexBuffer = function(name)
{
	return this.indexBuffers[name];
}

/**
* Removes an index buffer from the mesh
* @method removeIndexBuffer
* @param {String} name "vertices","normals"...
* @param {Boolean} free if you want to remove the data from the GPU
*/
Mesh.prototype.removeIndexBuffer = function(name, free) {
	var buffer = this.indexBuffers[name];
	if(!buffer)
		return;
	if(free)
		buffer.delete();
	delete this.indexBuffers[name];
}


/**
* Uploads data inside buffers to VRAM.
* @method upload
* @param {number} buffer_type gl.STATIC_DRAW, gl.DYNAMIC_DRAW, gl.STREAM_DRAW
*/
Mesh.prototype.upload = function(buffer_type) {
	for (var attribute in this.vertexBuffers) {
		var buffer = this.vertexBuffers[attribute];
		//buffer.data = this[buffer.name];
		buffer.upload(buffer_type);
	}

	for (var name in this.indexBuffers) {
		var buffer = this.indexBuffers[name];
		//buffer.data = this[name];
		buffer.upload();
	}
}

//LEGACY, plz remove
Mesh.prototype.compile = Mesh.prototype.upload;


Mesh.prototype.deleteBuffers = function()
{
	for(var i in this.vertexBuffers)
	{
		var buffer = this.vertexBuffers[i];
		buffer.delete();
	}
	this.vertexBuffers = {};

	for(var i in this.indexBuffers)
	{
		var buffer = this.indexBuffers[i];
		buffer.delete();
	}
	this.indexBuffers = {};
}

Mesh.prototype.delete = Mesh.prototype.deleteBuffers;

Mesh.prototype.bindBuffers = function( shader )
{
	// enable attributes as necessary.
	for (var name in this.vertexBuffers)
	{
		var buffer = this.vertexBuffers[ name ];
		var attribute = buffer.attribute || name;
		var location = shader.attributes[ attribute ];
		if (location == null || !buffer.buffer) 
			continue; 
		gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
		gl.enableVertexAttribArray(location);
		gl.vertexAttribPointer(location, buffer.buffer.spacing, buffer.buffer.gl_type, false, 0, 0);
	}
}

Mesh.prototype.unbindBuffers = function( shader )
{
	// disable attributes
	for (var name in this.vertexBuffers)
	{
		var buffer = this.vertexBuffers[ name ];
		var attribute = buffer.attribute || name;
		var location = shader.attributes[ attribute ];
		if (location == null || !buffer.buffer)
			continue; //ignore this buffer
		gl.disableVertexAttribArray( shader.attributes[attribute] );
	}
}

/**
* Creates a clone of the mesh, the datarrays are cloned too
* @method clone
*/
Mesh.prototype.clone = function( gl )
{
	var gl = gl || global.gl;
	var vbs = {};
	var ibs = {};

	for(var i in this.vertexBuffers)
	{
		var b = this.vertexBuffers[i];
		vbs[i] = new b.data.constructor( b.data ); //clone
	}
	for(var i in this.indexBuffers)
	{
		var b = this.indexBuffers[i];
		ibs[i] = new b.data.constructor( b.data ); //clone
	}

	return new GL.Mesh( vbs, ibs, undefined, gl );
}

/**
* Creates a clone of the mesh, but the data-arrays are shared between both meshes (useful for sharing a mesh between contexts)
* @method clone
*/
Mesh.prototype.cloneShared = function( gl )
{
	var gl = gl || global.gl;
	return new GL.Mesh( this.vertexBuffers, this.indexBuffers, undefined, gl );
}


/**
* Creates an object with the info of the mesh (useful to transfer to workers)
* @method toObject
*/
Mesh.prototype.toObject = function()
{
	var vbs = {};
	var ibs = {};

	for(var i in this.vertexBuffers)
	{
		var b = this.vertexBuffers[i];
		vbs[i] = { 
			spacing: b.spacing,
			data: new b.data.constructor( b.data ) //clone
		}; 
	}
	for(var i in this.indexBuffers)
	{
		var b = this.indexBuffers[i];
		ibs[i] = { 
			data: new b.data.constructor( b.data ) //clone
		}
	}

	return { 
		vertexBuffers: vbs, 
		indexBuffers: ibs,
		info: this.info ? cloneObject( this.info ) : null,
		bounding: this._bounding.toJSON()
	};
}


Mesh.prototype.toJSON = function()
{
	var r = {
		vertexBuffers: {},
		indexBuffers: {},
		info: this.info ? cloneObject( this.info ) : null,
		bounding: this._bounding.toJSON() 
	};

	for(var i in this.vertexBuffers)
		r.vertexBuffers[i] = this.vertexBuffers[i].toJSON();

	for(var i in this.indexBuffers)
		r.indexBuffers[i] = this.indexBuffers[i].toJSON();

	return r;
}

Mesh.prototype.fromJSON = function(o)
{
	this.vertexBuffers = {};
	this.indexBuffers = {};

	for(var i in o.vertexBuffers)
	{
		if(!o.vertexBuffers[i])
			continue;
		var buffer = new GL.Buffer();
		buffer.fromJSON( o.vertexBuffers[i] );
		if(!buffer.attribute && GL.Mesh.common_buffers[i])
			buffer.attribute = GL.Mesh.common_buffers[i].attribute;
		this.vertexBuffers[i] = buffer;
	}

	for(var i in o.indexBuffers)
	{
		if(!o.indexBuffers[i])
			continue;
		var buffer = new GL.Buffer();
		buffer.fromJSON( o.indexBuffers[i] );
		this.indexBuffers[i] = buffer;
	}

	if(o.info)
		this.info = cloneObject( o.info );
	if(o.bounding)
		this.bounding = o.bounding; //setter does the job
}


/**
* Computes some data about the mesh
* @method generateMetadata
*/
Mesh.prototype.generateMetadata = function()
{
	var metadata = {};

	var vertices = this.vertexBuffers["vertices"].data;
	var triangles = this.indexBuffers["triangles"].data;

	metadata.vertices = vertices.length / 3;
	if(triangles)
		metadata.faces = triangles.length / 3;
	else
		metadata.faces = vertices.length / 9;

	metadata.indexed = !!this.metadata.faces;
	this.metadata = metadata;
}

//never tested
/*
Mesh.prototype.draw = function(shader, mode, range_start, range_length)
{
	if(range_length == 0) return;

	// Create and enable attribute pointers as necessary.
	var length = 0;
	for (var attribute in this.vertexBuffers) {
	  var buffer = this.vertexBuffers[attribute];
	  var location = shader.attributes[attribute] ||
		gl.getAttribLocation(shader.program, attribute);
	  if (location == -1 || !buffer.buffer) continue;
	  shader.attributes[attribute] = location;
	  gl.bindBuffer(gl.ARRAY_BUFFER, buffer.buffer);
	  gl.enableVertexAttribArray(location);
	  gl.vertexAttribPointer(location, buffer.buffer.spacing, gl.FLOAT, false, 0, 0);
	  length = buffer.buffer.length / buffer.buffer.spacing;
	}

	//range rendering
	var offset = 0;
	if(arguments.length > 3) //render a polygon range
		offset = range_start * (this.indexBuffer ? this.indexBuffer.constructor.BYTES_PER_ELEMENT : 1); //in bytes (Uint16 == 2 bytes)

	if(arguments.length > 4)
		length = range_length;
	else if (this.indexBuffer)
		length = this.indexBuffer.buffer.length - offset;

	// Disable unused attribute pointers.
	for (var attribute in shader.attributes) {
	  if (!(attribute in this.vertexBuffers)) {
		gl.disableVertexAttribArray(shader.attributes[attribute]);
	  }
	}

	// Draw the geometry.
	if (length && (!this.indexBuffer || indexBuffer.buffer)) {
	  if (this.indexBuffer) {
		gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer.buffer);
		gl.drawElements(mode, length, gl.UNSIGNED_SHORT, offset);
	  } else {
		gl.drawArrays(mode, offset, length);
	  }
	}

	return this;
}
*/

/**
* Creates a new index stream with wireframe 
* @method computeWireframe
*/
Mesh.prototype.computeWireframe = function() {
	var index_buffer = this.indexBuffers["triangles"];

	var vertices = this.vertexBuffers["vertices"].data;
	var num_vertices = (vertices.length/3);

	if(!index_buffer) //unindexed
	{
		var num_triangles = num_vertices / 3;
		var buffer = num_vertices > 256*256 ? new Uint32Array( num_triangles * 6 ) : new Uint16Array( num_triangles * 6 );
		for(var i = 0; i < num_vertices; i += 3)
		{
			buffer[i*2] = i;
			buffer[i*2+1] = i+1;
			buffer[i*2+2] = i+1;
			buffer[i*2+3] = i+2;
			buffer[i*2+4] = i+2;
			buffer[i*2+5] = i;
		}

	}
	else //indexed
	{
		var data = index_buffer.data;

		var indexer = new GL.Indexer();
		for (var i = 0; i < data.length; i+=3) {
		  var t = data.subarray(i,i+3);
		  for (var j = 0; j < t.length; j++) {
			var a = t[j], b = t[(j + 1) % t.length];
			indexer.add([Math.min(a, b), Math.max(a, b)]);
		  }
		}

		//linearize
		var unique = indexer.unique;
		var buffer = num_vertices > 256*256 ? new Uint32Array( unique.length * 2 ) : new Uint16Array( unique.length * 2 );
		for(var i = 0, l = unique.length; i < l; ++i)
			buffer.set(unique[i],i*2);
	}

	//create stream
	this.createIndexBuffer('wireframe', buffer);
	return this;
}


/**
* Multiplies every normal by -1 and uploads it
* @method flipNormals
* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW)
*/
Mesh.prototype.flipNormals = function( stream_type  ) {
	var normals_buffer = this.vertexBuffers["normals"];
	if(!normals_buffer)
		return;
	var data = normals_buffer.data;
	var l = data.length;
	for(var i = 0; i < l; ++i)
		data[i] *= -1;
	normals_buffer.upload( stream_type );

	//reverse indices too
	if( !this.indexBuffers["triangles"] )
		this.computeIndices(); //create indices

	var triangles_buffer = this.indexBuffers["triangles"];
	var data = triangles_buffer.data;
	var l = data.length;
	for(var i = 0; i < l; i += 3)
	{
		var tmp = data[i];
		data[i] = data[i+1];
		data[i+1] = tmp;
		//the [i+2] stays the same
	}
	triangles_buffer.upload( stream_type );
}


/**
* Compute indices for a mesh where vertices are shared
* @method computeIndices
*/
Mesh.prototype.computeIndices = function() {

	//cluster by distance
	var new_vertices = [];
	var new_normals = [];
	var new_coords = [];

	var indices = [];

	var old_vertices_buffer = this.vertexBuffers["vertices"];
	var old_normals_buffer = this.vertexBuffers["normals"];
	var old_coords_buffer = this.vertexBuffers["coords"];

	var old_vertices_data = old_vertices_buffer.data;

	var old_normals_data = null;
	if( old_normals_buffer )
		old_normals_data = old_normals_buffer.data;

	var old_coords_data = null;
	if( old_coords_buffer )
		old_coords_data = old_coords_buffer.data;


	var indexer = {};

	var l = old_vertices_data.length / 3;
	for(var i = 0; i < l; ++i)
	{
		var v = old_vertices_data.subarray( i*3,(i+1)*3 );
		var key = (v[0] * 1000)|0;

		//search in new_vertices
		var j = 0;
		var candidates = indexer[key];
		if(candidates)
		{
			var l2 = candidates.length;
			for(; j < l2; j++)
			{
				var v2 = new_vertices[ candidates[j] ];
				//same vertex
				if( vec3.sqrDist( v, v2 ) < 0.01 )
				{
					indices.push(j);
					break;
				}
			}
		}

		/*
		var l2 = new_vertices.length;
		for(var j = 0; j < l2; j++)
		{
			//same vertex
			if( vec3.sqrDist( v, new_vertices[j] ) < 0.001 )
			{
				indices.push(j);
				break;
			}
		}
		*/

		if(candidates && j != l2)
			continue;

		var index = j;
		new_vertices.push(v);
		if( indexer[ key ] )
			indexer[ key ].push( index );
		else
			indexer[ key ] = [ index ];

		if(old_normals_data)
			new_normals.push( old_normals_data.subarray(i*3, (i+1)*3) );
		if(old_coords_data)
			new_coords.push( old_coords_data.subarray(i*2, (i+1)*2) );
		indices.push(index);
	}

	this.vertexBuffers = {}; //erase all

	//new buffers
	this.createVertexBuffer( 'vertices', GL.Mesh.common_buffers["vertices"].attribute, 3, linearizeArray( new_vertices ) );	
	if(old_normals_data)
		this.createVertexBuffer( 'normals', GL.Mesh.common_buffers["normals"].attribute, 3, linearizeArray( new_normals ) );	
	if(old_coords_data)
		this.createVertexBuffer( 'coords', GL.Mesh.common_buffers["coords"].attribute, 2, linearizeArray( new_coords ) );	

	this.createIndexBuffer( "triangles", indices );
}

/**
* Breaks the indices
* @method explodeIndices
*/
Mesh.prototype.explodeIndices = function( buffer_name ) {

	buffer_name = buffer_name || "triangles";

	var indices_buffer = this.getIndexBuffer( buffer_name );
	if(!indices_buffer)
		return;

	var indices = indices_buffer.data;

	//cluster by distance
	var new_vertices = new Float32Array(indices.length * 3);
	var new_normals = null;
	var new_coords = null;

	var old_vertices_buffer = this.vertexBuffers["vertices"];
	var old_vertices = old_vertices_buffer.data;

	var old_normals_buffer = this.vertexBuffers["normals"];
	var old_normals = null;
	if(old_normals_buffer)
	{
		old_normals = old_normals_buffer.data;
		new_normals = new Float32Array(indices.length * 3);
	}

	var old_coords_buffer = this.vertexBuffers["coords"];
	var old_coords = null;
	if( old_coords_buffer )
	{
		old_coords = old_coords_buffer.data;
		new_coords = new Float32Array(indices.length * 2);
	}

	for(var i = 0, l = indices.length; i < l; ++i)
	{
		var index = indices[i];
		new_vertices.set( old_vertices.subarray( index*3, index*3 + 3 ), i*3 );
		if(old_normals)
			new_normals.set( old_normals.subarray( index*3, index*3 + 3 ), i*3 );
		if(old_coords)
			new_coords.set( old_coords.subarray( index*2, index*2 + 2 ), i*2 );
	}

	//erase all
	this.vertexBuffers = {}; 

	//new buffers
	this.createVertexBuffer( 'vertices', GL.Mesh.common_buffers["vertices"].attribute, 3, new_vertices );	
	if(new_normals)
		this.createVertexBuffer( 'normals', GL.Mesh.common_buffers["normals"].attribute, 3, new_normals );	
	if(new_coords)
		this.createVertexBuffer( 'coords', GL.Mesh.common_buffers["coords"].attribute, 2, new_coords );	

	delete this.indexBuffers[ buffer_name ];
}



/**
* Creates a stream with the normals
* @method computeNormals
* @param {enum} stream_type default gl.STATIC_DRAW (other: gl.DYNAMIC_DRAW, gl.STREAM_DRAW)
*/
Mesh.prototype.computeNormals = function( stream_type  ) {
	var vertices_buffer = this.vertexBuffers["vertices"];
	if(!vertices_buffer)
		return console.error("Cannot compute normals of a mesh without vertices");

	var vertices = this.vertexBuffers["vertices"].data;
	var num_vertices = vertices.length / 3;

	//create because it is faster than filling it with zeros
	var normals = new Float32Array( vertices.length );

	var triangles = null;
	if(this.indexBuffers["triangles"])
		triangles = this.indexBuffers["triangles"].data;

	var temp = GL.temp_vec3;
	var temp2 = GL.temp2_vec3;

	var i1,i2,i3,v1,v2,v3,n1,n2,n3;

	//compute the plane normal
	var l = triangles ? triangles.length : vertices.length;
	for (var a = 0; a < l; a+=3)
	{
		if(triangles)
		{
			i1 = triangles[a];
			i2 = triangles[a+1];
			i3 = triangles[a+2];

			v1 = vertices.subarray(i1*3,i1*3+3);
			v2 = vertices.subarray(i2*3,i2*3+3);
			v3 = vertices.subarray(i3*3,i3*3+3);

			n1 = normals.subarray(i1*3,i1*3+3);
			n2 = normals.subarray(i2*3,i2*3+3);
			n3 = normals.subarray(i3*3,i3*3+3);
		}
		else
		{
			v1 = vertices.subarray(a*3,a*3+3);
			v2 = vertices.subarray(a*3+3,a*3+6);
			v3 = vertices.subarray(a*3+6,a*3+9);

			n1 = normals.subarray(a*3,a*3+3);
			n2 = normals.subarray(a*3+3,a*3+6);
			n3 = normals.subarray(a*3+6,a*3+9);
		}

		vec3.sub( temp, v2, v1 );
		vec3.sub( temp2, v3, v1 );
		vec3.cross( temp, temp, temp2 );
		vec3.normalize(temp,temp);

		//save
		vec3.add( n1, n1, temp );
		vec3.add( n2, n2, temp );
		vec3.add( n3, n3, temp );
	}

	//normalize if vertices are shared
	if(triangles)
	for (var a = 0, l = normals.length; a < l; a+=3)
	{
		var n = normals.subarray(a,a+3);
		vec3.normalize(n,n);
	}

	var normals_buffer = this.vertexBuffers["normals"];

	if(normals_buffer)
	{
		normals_buffer.data = normals;
		normals_buffer.upload( stream_type );
	}
	else
		return this.createVertexBuffer('normals', GL.Mesh.common_buffers["normals"].attribute, 3, normals );
	return normals_buffer;
}


/**
* Creates a new stream with the tangents
* @method computeTangents
*/
Mesh.prototype.computeTangents = function()
{
	var vertices_buffer = this.vertexBuffers["vertices"];
	if(!vertices_buffer)
		return console.error("Cannot compute tangents of a mesh without vertices");

	var normals_buffer = this.vertexBuffers["normals"];
	if(!normals_buffer)
		return console.error("Cannot compute tangents of a mesh without normals");

	var uvs_buffer = this.vertexBuffers["coords"];
	if(!uvs_buffer)
		return console.error("Cannot compute tangents of a mesh without uvs");

	var triangles_buffer = this.indexBuffers["triangles"];
	if(!triangles_buffer)
		return console.error("Cannot compute tangents of a mesh without indices");

	var vertices = vertices_buffer.data;
	var normals = normals_buffer.data;
	var uvs = uvs_buffer.data;
	var triangles = triangles_buffer.data;

	if(!vertices || !normals || !uvs) return;

	var num_vertices = vertices.length / 3;

	var tangents = new Float32Array(num_vertices * 4);
	
	//temporary (shared)
	var tan1 = new Float32Array(num_vertices*3*2);
	var tan2 = tan1.subarray(num_vertices*3);

	var a,l;
	var sdir = vec3.create();
	var tdir = vec3.create();
	var temp = vec3.create();
	var temp2 = vec3.create();

	for (a = 0, l = triangles.length; a < l; a+=3)
	{
		var i1 = triangles[a];
		var i2 = triangles[a+1];
		var i3 = triangles[a+2];

		var v1 = vertices.subarray(i1*3,i1*3+3);
		var v2 = vertices.subarray(i2*3,i2*3+3);
		var v3 = vertices.subarray(i3*3,i3*3+3);

		var w1 = uvs.subarray(i1*2,i1*2+2);
		var w2 = uvs.subarray(i2*2,i2*2+2);
		var w3 = uvs.subarray(i3*2,i3*2+2);

		var x1 = v2[0] - v1[0];
		var x2 = v3[0] - v1[0];
		var y1 = v2[1] - v1[1];
		var y2 = v3[1] - v1[1];
		var z1 = v2[2] - v1[2];
		var z2 = v3[2] - v1[2];

		var s1 = w2[0] - w1[0];
		var s2 = w3[0] - w1[0];
		var t1 = w2[1] - w1[1];
		var t2 = w3[1] - w1[1];

		var r;
		var den = (s1 * t2 - s2 * t1);
		if ( Math.abs(den) < 0.000000001 )
		  r = 0.0;
		else
		  r = 1.0 / den;

		vec3.copy(sdir, [(t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r] );
		vec3.copy(tdir, [(s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r] );

		vec3.add( tan1.subarray( i1*3, i1*3+3), tan1.subarray( i1*3, i1*3+3), sdir);
		vec3.add( tan1.subarray( i2*3, i2*3+3), tan1.subarray( i2*3, i2*3+3), sdir);
		vec3.add( tan1.subarray( i3*3, i3*3+3), tan1.subarray( i3*3, i3*3+3), sdir);

		vec3.add( tan2.subarray( i1*3, i1*3+3), tan2.subarray( i1*3, i1*3+3), tdir);
		vec3.add( tan2.subarray( i2*3, i2*3+3), tan2.subarray( i2*3, i2*3+3), tdir);
		vec3.add( tan2.subarray( i3*3, i3*3+3), tan2.subarray( i3*3, i3*3+3), tdir);
	}

	for (a = 0, l = vertices.length; a < l; a+=3)
	{
		var n = normals.subarray(a,a+3);
		var t = tan1.subarray(a,a+3);

		// Gram-Schmidt orthogonalize
		vec3.subtract(temp, t, vec3.scale(temp, n, vec3.dot(n, t) ) );
		vec3.normalize(temp,temp);

		// Calculate handedness
		var w = ( vec3.dot( vec3.cross(temp2, n, t), tan2.subarray(a,a+3) ) < 0.0) ? -1.0 : 1.0;
		tangents.set([temp[0], temp[1], temp[2], w],(a/3)*4);
	}

	this.createVertexBuffer('tangents', Mesh.common_buffers["tangents"].attribute, 4, tangents );
}

/**
* Creates texture coordinates using a triplanar aproximation
* @method computeTextureCoordinates
*/
Mesh.prototype.computeTextureCoordinates = function( stream_type )
{
	var vertices_buffer = this.vertexBuffers["vertices"];
	if(!vertices_buffer)
		return console.error("Cannot compute uvs of a mesh without vertices");

	this.explodeIndices( "triangles" );

	var vertices = vertices_buffer.data;
	var num_vertices = vertices.length / 3;

	var uvs_buffer = this.vertexBuffers["coords"];
	var uvs = new Float32Array( num_vertices * 2 );

	var triangles_buffer = this.indexBuffers["triangles"];
	var triangles = null;
	if( triangles_buffer )
		triangles = triangles_buffer.data;

	var plane_normal = vec3.create();
	var side1 = vec3.create();
	var side2 = vec3.create();

	var bbox = this.getBoundingBox();
	var bboxcenter = BBox.getCenter( bbox );
	var bboxhs = vec3.create();
	bboxhs.set( BBox.getHalfsize( bbox ) ); //careful, this is a reference
	vec3.scale( bboxhs, bboxhs, 2 );

	var num = triangles ? triangles.length : vertices.length/3;

	for (var a = 0; a < num; a+=3)
	{
		if(triangles)
		{
			var i1 = triangles[a];
			var i2 = triangles[a+1];
			var i3 = triangles[a+2];

			var v1 = vertices.subarray(i1*3,i1*3+3);
			var v2 = vertices.subarray(i2*3,i2*3+3);
			var v3 = vertices.subarray(i3*3,i3*3+3);

			var uv1 = uvs.subarray(i1*2,i1*2+2);
			var uv2 = uvs.subarray(i2*2,i2*2+2);
			var uv3 = uvs.subarray(i3*2,i3*2+2);
		}
		else
		{
			var v1 = vertices.subarray((a)*3,(a)*3+3);
			var v2 = vertices.subarray((a+1)*3,(a+1)*3+3);
			var v3 = vertices.subarray((a+2)*3,(a+2)*3+3);

			var uv1 = uvs.subarray((a)*2,(a)*2+2);
			var uv2 = uvs.subarray((a+1)*2,(a+1)*2+2);
			var uv3 = uvs.subarray((a+2)*2,(a+2)*2+2);
		}

		vec3.sub(side1, v1, v2 );
		vec3.sub(side2, v1, v3 );
		vec3.cross( plane_normal, side1, side2 );
		//vec3.normalize( plane_normal, plane_normal ); //not necessary

		plane_normal[0] = Math.abs( plane_normal[0] );
		plane_normal[1] = Math.abs( plane_normal[1] );
		plane_normal[2] = Math.abs( plane_normal[2] );

		if( plane_normal[0] > plane_normal[1] && plane_normal[0] > plane_normal[2])
		{
			//X
			uv1[0] = (v1[2] - bboxcenter[2]) / bboxhs[2];
			uv1[1] = (v1[1] - bboxcenter[1]) / bboxhs[1];
			uv2[0] = (v2[2] - bboxcenter[2]) / bboxhs[2];
			uv2[1] = (v2[1] - bboxcenter[1]) / bboxhs[1];
			uv3[0] = (v3[2] - bboxcenter[2]) / bboxhs[2];
			uv3[1] = (v3[1] - bboxcenter[1]) / bboxhs[1];
		}
		else if ( plane_normal[1] > plane_normal[2])
		{
			//Y
			uv1[0] = (v1[0] - bboxcenter[0]) / bboxhs[0];
			uv1[1] = (v1[2] - bboxcenter[2]) / bboxhs[2];
			uv2[0] = (v2[0] - bboxcenter[0]) / bboxhs[0];
			uv2[1] = (v2[2] - bboxcenter[2]) / bboxhs[2];
			uv3[0] = (v3[0] - bboxcenter[0]) / bboxhs[0];
			uv3[1] = (v3[2] - bboxcenter[2]) / bboxhs[2];
		}
		else
		{
			//Z
			uv1[0] = (v1[0] - bboxcenter[0]) / bboxhs[0];
			uv1[1] = (v1[1] - bboxcenter[1]) / bboxhs[1];
			uv2[0] = (v2[0] - bboxcenter[0]) / bboxhs[0];
			uv2[1] = (v2[1] - bboxcenter[1]) / bboxhs[1];
			uv3[0] = (v3[0] - bboxcenter[0]) / bboxhs[0];
			uv3[1] = (v3[1] - bboxcenter[1]) / bboxhs[1];
		}
	}

	if(uvs_buffer)
	{
		uvs_buffer.data = uvs;
		uvs_buffer.upload( stream_type );
	}
	else
		this.createVertexBuffer('coords', Mesh.common_buffers["coords"].attribute, 2, uvs );
}


/**
* Computes the number of vertices
* @method getVertexNumber
* @param {typed Array} vertices array containing all the vertices
*/
Mesh.prototype.getNumVertices = function() {
	var b = this.vertexBuffers["vertices"];
	if(!b) return 0;
	return b.data.length / b.spacing;
}


/**
* Computes bounding information
* @method Mesh.computeBoundingBox
* @param {typed Array} vertices array containing all the vertices
* @param {BBox} bb where to store the bounding box
* @param {Array} mask [optional] to specify which vertices must be considered when creating the bbox, used to create BBox of a submesh
*/
Mesh.computeBoundingBox = function( vertices, bb, mask ) {

	if(!vertices)
		return;

	var start = 0;

	if(mask)
	{
		for(var i = 0; i < mask.length; ++i)
			if( mask[i] )
			{
				start = i;
				break;
			}
		if(start == mask.length)
		{
			console.warn("mask contains only zeros, no vertices marked");
			return;
		}
	}

	var min = vec3.clone( vertices.subarray( start*3, start*3 + 3) );
	var max = vec3.clone( vertices.subarray( start*3, start*3 + 3) );
	var v;

	for(var i = start*3; i < vertices.length; i+=3)
	{
		if( mask && !mask[i/3] )
			continue;
		v = vertices.subarray(i,i+3);
		vec3.min( min,v, min);
		vec3.max( max,v, max);
	}

	if( isNaN(min[0]) || isNaN(min[1]) || isNaN(min[2]) ||
		isNaN(max[0]) || isNaN(max[1]) || isNaN(max[2]) )
	{
		min[0] = min[1] = min[2] = 0;
		max[0] = max[1] = max[2] = 0;
		console.warn("Warning: GL.Mesh has NaN values in vertices");
	}

	var center = vec3.add( vec3.create(), min,max );
	vec3.scale( center, center, 0.5);
	var half_size = vec3.subtract( vec3.create(), max, center );

	return BBox.setCenterHalfsize( bb || BBox.create(), center, half_size );
}

/**
* returns the bounding box, if it is not computed, then computes it
* @method getBoundingBox
* @return {BBox} bounding box
*/
Mesh.prototype.getBoundingBox = function()
{
	if(this._bounding)
		return this._bounding;

	this.updateBoundingBox();
	return this._bounding;
}

/**
* Update bounding information of this mesh
* @method updateBoundingBox
*/
Mesh.prototype.updateBoundingBox = function() {
	var vertices = this.vertexBuffers["vertices"];
	if(!vertices)
		return;
	GL.Mesh.computeBoundingBox( vertices.data, this._bounding );
	if(this.info && this.info.groups && this.info.groups.length)
		this.computeGroupsBoundingBoxes();
}

/**
* Update bounding information for every group submesh
* @method computeGroupsBoundingBoxes
*/
Mesh.prototype.computeGroupsBoundingBoxes = function()
{
	var indices = null;
	var indices_buffer = this.getIndexBuffer("triangles");
	if( indices_buffer )
		indices = indices_buffer.data;

	var vertices_buffer = this.getVertexBuffer("vertices");
	if(!vertices_buffer)
		return false;
	var vertices = vertices_buffer.data;
	if(!vertices.length)
		return false;

	var groups = this.info.groups;
	for(var i = 0; i < groups.length; ++i)
	{
		var group = groups[i];
		group.bounding = group.bounding || BBox.create();
		var submesh_vertices = null;
		if( indices )
		{
			var mask = new Uint8Array( vertices.length / 3 );
			var s = group.start;
			for( var j = 0, l = group.length; j < l; j += 3 )
			{
				mask[ indices[s+j] ] = 1;
				mask[ indices[s+j+1] ] = 1;
				mask[ indices[s+j+2] ] = 1;
			}
			GL.Mesh.computeBoundingBox( vertices, group.bounding, mask );
		}
		else
		{
			submesh_vertices = vertices.subarray( group.start * 3, ( group.start + group.length) * 3 );
			GL.Mesh.computeBoundingBox( submesh_vertices, group.bounding );
		}
	}
	return true;
}



/**
* forces a bounding box to be set
* @method setBoundingBox
* @param {vec3} center center of the bounding box
* @param {vec3} half_size vector from the center to positive corner
*/
Mesh.prototype.setBoundingBox = function( center, half_size ) {
	BBox.setCenterHalfsize( this._bounding, center, half_size );	
}


/**
* Remove all local memory from the streams (leaving it only in the VRAM) to save RAM
* @method freeData
*/
Mesh.prototype.freeData = function()
{
	for (var attribute in this.vertexBuffers)
	{
		this.vertexBuffers[attribute].data = null;
		delete this[ this.vertexBuffers[attribute].name ]; //delete from the mesh itself
	}
	for (var name in this.indexBuffers)
	{
		this.indexBuffers[name].data = null;
		delete this[ this.indexBuffers[name].name ]; //delete from the mesh itself
	}
}

Mesh.prototype.configure = function( o, options )
{
	var vertex_buffers = {};
	var index_buffers = {};
	options = options || {};

	for(var j in o)
	{
		if(!o[j])
			continue;

		if(j == "vertexBuffers" || j == "vertex_buffers") //HACK: legacy code
		{
			for(i in o[j])
				vertex_buffers[i] = o[j][i];
			continue;
		}
		
		if(j == "indexBuffers" || j == "index_buffers")
		{
			for(i in o[j])
				index_buffers[i] = o[j][i];
			continue;
		}

		if(j == "indices" || j == "lines" ||  j == "wireframe" || j == "triangles")
			index_buffers[j] = o[j];
		else if( GL.Mesh.common_buffers[j])
			vertex_buffers[j] = o[j];
		else //global data like bounding, info of groups, etc
		{
			options[j] = o[j];
		}
	}

	this.addBuffers( vertex_buffers, index_buffers, options.stream_type );

	for(var i in options)
		this[i] = options[i];		

	if(!options.bounding)
		this.updateBoundingBox();
}

/**
* Returns the amount of memory used by this mesh in bytes (sum of all buffers)
* @method getMemory
* @return {number} bytes
*/
Mesh.prototype.totalMemory = function()
{
	var num = 0|0;

	for (var name in this.vertexBuffers)
		num += this.vertexBuffers[name].data.buffer.byteLength;
	for (var name in this.indexBuffers)
		num += this.indexBuffers[name].data.buffer.byteLength;

	return num;
}

/**
* returns a low poly version of the mesh that takes much less memory (but breaks tiling of uvs and smoothing groups)
* @method simplify
* @return {Mesh} simplified mesh
*/
Mesh.prototype.simplify = function()
{
	//compute bounding box
	var bb = this.getBoundingBox();
	var min = BBox.getMin( bb );
	var halfsize = BBox.getHalfsize( bb );
	var range = vec3.scale( vec3.create(), halfsize, 2 );

	var newmesh = new GL.Mesh();
	var temp = vec3.create();

	for(var i in this.vertexBuffers)
	{
		//take every vertex and normalize it to the bounding box
		var buffer = this.vertexBuffers[i];
		var data = buffer.data;

		var new_data = new Float32Array( data.length );

		if(i == "vertices")
		{
			for(var j = 0, l = data.length; j < l; j+=3 )
			{
				var v = data.subarray(j,j+3);
				vec3.sub( temp, v, min );
				vec3.div( temp, temp, range );
				temp[0] = Math.round(temp[0] * 256) / 256;
				temp[1] = Math.round(temp[1] * 256) / 256;
				temp[2] = Math.round(temp[2] * 256) / 256;
				vec3.mul( temp, temp, range );
				vec3.add( temp, temp, min );
				new_data.set( temp, j );
			}
		}
		else
		{
		}

		newmesh.addBuffer();
	}

	//search for repeated vertices
		//compute the average normal and coord
	//reindex the triangles
	//return simplified mesh	
}

/**
* Static method for the class Mesh to create a mesh from a list of common streams
* @method Mesh.load
* @param {Object} buffers object will all the buffers
* @param {Object} options [optional]
* @param {Mesh} output_mesh [optional] mesh to store the mesh, otherwise is created
* @param {WebGLContext} gl [optional] if omitted, the global.gl is used
*/
Mesh.load = function( buffers, options, output_mesh, gl ) {
	options = options || {};
	if(options.no_gl)
		gl = null;
	var mesh = output_mesh || new GL.Mesh(null,null,null,gl);
	mesh.configure( buffers, options );
	return mesh;
}

/**
* Returns a mesh with all the meshes merged (you can apply transforms individually to every buffer)
* @method Mesh.mergeMeshes
* @param {Array} meshes array containing object like { mesh:, matrix:, texture_matrix: }
* @param {Object} options { only_data: to get the mesh data without uploading it }
* @return {GL.Mesh|Object} the mesh in GL.Mesh format or Object format (if options.only_data is true)
*/
Mesh.mergeMeshes = function( meshes, options )
{
	options = options || {};

	var vertex_buffers = {};
	var index_buffers = {};
	var offsets = {}; //tells how many positions indices must be offseted
	var vertex_offsets = [];
	var current_vertex_offset = 0;
	var groups = [];

	//vertex buffers
	//compute size
	for(var i = 0; i < meshes.length; ++i)
	{
		var mesh_info = meshes[i];
		var mesh = mesh_info.mesh;
		var offset = current_vertex_offset;
		vertex_offsets.push( offset );
		var length = mesh.vertexBuffers["vertices"].data.length / 3;
		current_vertex_offset += length;

		for(var j in mesh.vertexBuffers)
		{
			if(!vertex_buffers[j])
				vertex_buffers[j] = mesh.vertexBuffers[j].data.length;
			else
				vertex_buffers[j] += mesh.vertexBuffers[j].data.length;
		}

		for(var j in mesh.indexBuffers)
		{
			if(!index_buffers[j])
				index_buffers[j] = mesh.indexBuffers[j].data.length;
			else
				index_buffers[j] += mesh.indexBuffers[j].data.length;
		}

		//groups
		var group = {
			name: "mesh_" + i,
			start: offset,
			length: length,
			material: ""
		};

		groups.push( group );
	}

	//allocate
	for(var j in vertex_buffers)
	{
		var datatype = options[j];
		if(datatype === null)
		{
			delete vertex_buffers[j];
			continue;
		}

		if(!datatype)
			datatype = Float32Array;

		vertex_buffers[j] = new datatype( vertex_buffers[j] );
		offsets[j] = 0;
	}

	for(var j in index_buffers)
	{
		index_buffers[j] = new Uint32Array( index_buffers[j] );
		offsets[j] = 0;
	}

	//store
	for(var i = 0; i < meshes.length; ++i)
	{
		var mesh_info = meshes[i];
		var mesh = mesh_info.mesh;
		var offset = 0;
		var length = 0;

		for(var j in mesh.vertexBuffers)
		{
			if(!vertex_buffers[j])
				continue;

			if(j == "vertices")
				length = mesh.vertexBuffers[j].data.length / 3;

			vertex_buffers[j].set( mesh.vertexBuffers[j].data, offsets[j] );

			//apply transform
			if(mesh_info[ j + "_matrix"] )
			{
				var matrix = mesh_info[ j + "_matrix" ];
				if(matrix.length == 16)
					apply_transform( vertex_buffers[j], offsets[j], mesh.vertexBuffers[j].data.length, matrix )
				else if(matrix.length == 9)
					apply_transform2D( vertex_buffers[j], offsets[j], mesh.vertexBuffers[j].data.length, matrix )
			}

			offsets[j] += mesh.vertexBuffers[j].data.length;
		}

		for(var j in mesh.indexBuffers)
		{
			index_buffers[j].set( mesh.indexBuffers[j].data, offsets[j] );
			apply_offset( index_buffers[j], offsets[j], mesh.indexBuffers[j].data.length, vertex_offsets[i] );
			offsets[j] += mesh.indexBuffers[j].data.length;
		}
	}

	//useful functions
	function apply_transform( array, start, length, matrix )
	{
		var l = start + length;
		for(var i = start; i < l; i+=3)
		{
			var v = array.subarray(i,i+3);
			vec3.transformMat4( v, v, matrix );
		}
	}

	function apply_transform2D( array, start, length, matrix )
	{
		var l = start + length;
		for(var i = start; i < l; i+=2)
		{
			var v = array.subarray(i,i+2);
			vec2.transformMat3( v, v, matrix );
		}
	}

	function apply_offset( array, start, length, offset )
	{
		var l = start + length;
		for(var i = start; i < l; ++i)
			array[i] += offset;
	}

	var extra = { info: { groups: groups } };

	//return
	if( typeof(gl) != "undefined" || options.only_data )
		return new GL.Mesh( vertex_buffers,index_buffers, extra );
	return { 
		vertexBuffers: vertex_buffers, 
		indexBuffers: index_buffers, 
		info: { groups: groups } 
	};
}



//Here we store all basic mesh parsers (OBJ, STL) and encoders
Mesh.parsers = {};
Mesh.encoders = {};
Mesh.binary_file_formats = {}; //extensions that must be downloaded in binary
Mesh.compressors = {}; //used to compress binary meshes
Mesh.decompressors = {}; //used to decompress binary meshes

/**
* Returns am empty mesh and loads a mesh and parses it using the Mesh.parsers, by default only OBJ is supported
* @method Mesh.fromOBJ
* @param {Array} meshes array containing all the meshes
*/
Mesh.fromURL = function(url, on_complete, gl, options)
{
	options = options || {};
	gl = gl || global.gl;
	
	var mesh = new GL.Mesh(undefined,undefined,undefined,gl);
	mesh.ready = false;

	var pos = url.lastIndexOf(".");
	var extension = url.substr(pos+1).toLowerCase();
	options.binary = Mesh.binary_file_formats[ extension ];

	HttpRequest( url, null, function(data) {
		mesh.parse( data, extension );
		delete mesh["ready"];
		if(on_complete)
			on_complete.call(mesh,mesh, url);
	}, function(err){
		if(on_complete)
			on_complete(null);
	}, options );
	return mesh;
}

/**
* given some data an information about the format, it search for a parser in Mesh.parsers and tries to extract the mesh information
* Only obj supported now
* @method parse
* @param {*} data could be string or ArrayBuffer
* @param {String} format parser file format name (p.e. "obj")
* @return {?} depending on the parser
*/
Mesh.prototype.parse = function( data, format )
{
	format = format.toLowerCase();
	var parser = GL.Mesh.parsers[ format ];
	if(parser)
		return parser.call(null, data, {mesh: this});
	throw("GL.Mesh.parse: no parser found for format " + format );
}

/**
* It returns the mesh data encoded in the format specified
* Only obj supported now
* @method encode
* @param {String} format to encode the data to (p.e. "obj")
* @return {?} String with the info
*/
Mesh.prototype.encode = function( format, options )
{
	format = format.toLowerCase();
	var encoder = GL.Mesh.encoders[ format ];
	if(encoder)
		return encoder.call(null, this, options );
	throw("GL.Mesh.encode: no encoder found for format " + format );
}

/**
* Returns a shared mesh containing a quad to be used when rendering to the screen
* Reusing the same quad helps not filling the memory
* @method getScreenQuad
* @return {GL.Mesh} the screen quad
*/
Mesh.getScreenQuad = function(gl)
{
	gl = gl || global.gl;
	var mesh = gl.meshes[":screen_quad"];
	if(mesh)
		return mesh;

	var vertices = new Float32Array([0,0,0, 1,1,0, 0,1,0,  0,0,0, 1,0,0, 1,1,0 ]);
	var coords = new Float32Array([0,0, 1,1, 0,1,  0,0, 1,0, 1,1 ]);
	mesh = new GL.Mesh({ vertices: vertices, coords: coords}, undefined, undefined, gl);
	return gl.meshes[":screen_quad"] = mesh;
}

function linearizeArray( array, typed_array_class )
{
	if(array.constructor === typed_array_class)
		return array;
	if(array.constructor !== Array)
	{
		typed_array_class = typed_array_class || Float32Array;
		return new typed_array_class(array);
	}

	typed_array_class = typed_array_class || Float32Array;
	var components = array[0].length;
	var size = array.length * components;
	var buffer = new typed_array_class(size);

	for (var i=0; i < array.length;++i)
		for(var j=0; j < components; ++j)
			buffer[i*components + j] = array[i][j];
	return buffer;
}

/* BINARY MESHES */
//Add some functions to the classes in LiteGL to allow store in binary
GL.Mesh.EXTENSION = "wbin";
GL.Mesh.enable_wbin_compression = true;