Source: extras/GeometryTools.js

/**
 *  @namespace GeometryTools
 *  @memberof SQR
 *
 *  @description Tools to work with geometries/buffers.
 */
SQR.GeometryTools = (function() {

	var geoTools = {};
	var MAX_BUFFER_SIZE = 65535;

	/**
	 *	@method batch
	 *	@memberof SQR.GeometryTools
	 *
	 *	@description Combines all the geometries in a tree into one geometry. It assumes (but does not check) 
	 *	that all the children can be rendered using the same shader and that all their buffers have the same
	 *	layout. 
	 *
	 *	@param {SQR.Transform} container - the root of the tree to combine. 
	 *	This object and all it's children will be combined. 
	 *
	 *	@returns {SQR.Transform} the same object as passed in the argument. It will have no children 
	 *	and will have a buffer containing all the combine geometries. If the container shader was not set
	 *	it will inherit the shader from the first child that had one.
	 */
	geoTools.batch = function(container) {
		var batchObjects = [], size = 0;

		var updateTransform = function(t) {

			t.transformWorld();

			if (t.numChildren > 0) {
	            for (var i = 0; i < t.numChildren; i++) {
	                updateTransform(t.children[i]);
	            }
	        }

	        if(t.buffer) {
	        	if(t.buffer.isIndexed()) {
	        		console.warn("> SQR.GeometryTools.batch - indexed buffers can't be batched (yet)");
	        	} else {
	        		batchObjects.push(t);
	        		size += t.buffer.size;
	        	}
	        }

	        if(t.shader && !container.shader) {
	        	container.shader = t.shader;
	        }
		}

		updateTransform(container);

		var cb = SQR.Buffer().layout(batchObjects[0].buffer.layout, size);

		var offset = 0, base = 0, tmp = new SQR.V3(), tmpMat = new SQR.Matrix33(), c = 0;
		

		for(var i = 0; i < batchObjects.length; i++) {
				var bo = batchObjects[i];
				var b = batchObjects[i].buffer;
				var tb = new Float32Array(b.size * b.strideSize);
				tb.set(b.getDataArray());

				b.iterate('aPosition', function(i, d, c) {
					tmp.set(d[i+0], d[i+1], d[i+2], 1);
					bo.globalMatrix.transformVector(tmp);
					tb[i+0] = tmp.x;
					tb[i+1] = tmp.y;
					tb[i+2] = tmp.z;
				});

				b.iterate('aNormal', function(i, d, c) {
					tmp.set(d[i+0], d[i+1], d[i+2]);
					bo.globalMatrix.inverseMat3(tmpMat);
					tmpMat.transformVector(tmp);
					tb[i+0] = tmp.x;
					tb[i+1] = tmp.y;
					tb[i+2] = tmp.z;
				});

				cb.setRawData(tb, offset - base);

				offset += b.size * b.strideSize;

				c++;
		}

		// console.log('batched ' + container.numChildren + ' geometries, ' + size + ' vertices, ' + offset + ' elements in array');

		container.removeAll();
		container.buffer = cb.update();

		return container;
	}

	return geoTools;

})();