1 tQuery.registerStatic('createSpritesheet', function(opts){
  2 	return new tQuery.Spritesheet(opts)
  3 });
  4 /**
  5  * Class to handle spritesheet and generate extruded 3d objects with it.
  6  * TODO remove the canvas part, this is useless
  7  * 
  8  * @name tQuery.Spritesheet
  9  * @class
 10  *
 11  * @param {Object?} opts the options to create the spritesheet
 12  * @param {string} opts.url the url of the spritesheet image
 13  * @param {Number} opts.imgW the width of the image
 14  * @param {Number} opts.imgH the height of the image
 15  * @param {Number} opts.spriteW the width of each sprite in the image
 16  * @param {Number} opts.spriteH the height of each sprite in the image
 17 */
 18 tQuery.registerStatic('Spritesheet', function(opts){
 19 	// handle parameters
 20 	this._opts	= tQuery.extend(opts, {
 21 		url	: 'images/items/items.png',
 22 		imgW	: 256,	// TODO would be better if i didnt have to do that
 23 				// in fact there is some weird trick with explosion size
 24 				// sort this out
 25 		imgH	: 256,
 26 		spriteW	: 16,	// TODO is it smart to get 
 27 		spriteH	: 16
 28 	});
 29 	var imgW	= this._opts.imgW;
 30 	var imgH	= this._opts.imgH;
 31 	var spriteW	= this._opts.spriteW;
 32 	var spriteH	= this._opts.spriteH;
 33 	var nSpriteX	= this._opts.imgW / this._opts.spriteW;
 34 	var nSpriteY	= this._opts.imgH / this._opts.spriteH;
 35 	function getMaterial(image, transparent){
 36 		var tex		= new THREE.Texture(image);
 37 		tex.magFilter	= THREE.NearestFilter;
 38 		tex.minFilter	= THREE.NearestFilter;
 39 		tex.format	= transparent ? THREE.RGBAFormat : THREE.RGBFormat;
 40 		tex.needsUpdate	= true;
 41 		var material	= new THREE.MeshBasicMaterial({
 42 			map		: tex,
 43 			transparent	: transparent ? true : false
 44 		});
 45 		return material;
 46 	}
 47 	function uvmap (geometry, face, x, y, w, h, rotateBy){
 48 		rotateBy	= rotateBy !== undefined ? rotateBy : 0;
 49 		var uvs		= geometry.faceVertexUvs[0][face];
 50 		var tileU	= x;
 51 		var tileV	= y;
 52 		
 53 		uvs[ (0 + rotateBy) % 4 ].u = tileU * tileUvW;
 54 		uvs[ (0 + rotateBy) % 4 ].v = tileV * tileUvH;
 55 		uvs[ (1 + rotateBy) % 4 ].u = tileU * tileUvW;
 56 		uvs[ (1 + rotateBy) % 4 ].v = tileV * tileUvH + h * tileUvH;
 57 		uvs[ (2 + rotateBy) % 4 ].u = tileU * tileUvW + w * tileUvW;
 58 		uvs[ (2 + rotateBy) % 4 ].v = tileV * tileUvH + h * tileUvH;
 59 		uvs[ (3 + rotateBy) % 4 ].u = tileU * tileUvW + w * tileUvW;
 60 		uvs[ (3 + rotateBy) % 4 ].v = tileV * tileUvH;
 61 		
 62 		uvs[ (0 + rotateBy) % 4 ].v	= 1 - uvs[ (0 + rotateBy) % 4 ].v;
 63 		uvs[ (1 + rotateBy) % 4 ].v	= 1 - uvs[ (1 + rotateBy) % 4 ].v;
 64 		uvs[ (2 + rotateBy) % 4 ].v	= 1 - uvs[ (2 + rotateBy) % 4 ].v;
 65 		uvs[ (3 + rotateBy) % 4 ].v	= 1 - uvs[ (3 + rotateBy) % 4 ].v;
 66 	};
 67 	/**
 68 	 * Create the geometry of the sprite 'id'. It is cached
 69 	 *
 70 	 * @param {Number} id the id of the sprite. aka (x + y*nSpriteX)
 71 	 * @returns {THREE.Geometry} geometry built
 72 	*/
 73 	function getGeometry( id ){
 74 		if( geometries[id] !== undefined )	return geometries[id];
 75 		
 76 		function getSides(x, y){
 77 			var ix = Math.floor(id % nSpriteX)*spriteW;
 78 			var iy = Math.floor(id / nSpriteX)*spriteH;
 79 			
 80 			var alphaPx	= (x+1) < spriteW	? imd[((x+1)+y*spriteW)*4+3] : 0;
 81 			var alphaNx	= (x-1) >= 0		? imd[((x-1)+y*spriteW)*4+3] : 0;
 82 			var alphaPy	= (y+1) < spriteH	? imd[(x+(y-1)*spriteW)*4+3] : 0;
 83 			var alphaNy	= (y-1) >= 0		? imd[(x+(y+1)*spriteW)*4+3] : 0;
 84 			
 85 			return {
 86 				px: !alphaPx, // Turns zero and undefined to true
 87 				nx: !alphaNx,
 88 				py: !alphaPy,
 89 				ny: !alphaNy,
 90 				pz: true,
 91 				nz: true
 92 			};
 93 		};
 94 
 95 		var imgdata	= context.getImageData(Math.floor(id % nSpriteX)*spriteW, Math.floor(id / nSpriteX)*spriteH, spriteW, spriteH);
 96 		var imd		= imgdata.data;			
 97 		var geometry	= new THREE.Geometry();
 98 		
 99 		for(var x=0; x < spriteW; x++) {
100 			for(var y=0; y < spriteH; y++) {
101 				// is this pixel transparent, skip it
102 				if( imd[(x+y*spriteW)*4+3] === 0)	continue;
103 				
104 				var voxel	= new THREE.CubeGeometry(1, 1, 2, 1, 1, 1, undefined, getSides(x, y));
105 				// TODO why is there a texture here ????
106 				// - this is a single pixel. get the pixel and set the color
107 				// - this may fix the anti alias issue
108 				for(var i=0; i < voxel.faceVertexUvs[0].length; i++) { // Fix color of voxel
109 					uvmap(voxel, i,
110 						Math.floor(id % nSpriteX)*spriteW + x,
111 						Math.floor(id / nSpriteX)*spriteH+y,
112 						1, 1);
113 				}
114 				
115 				// TODO what is this ... it seems a translation but why ?
116 				console.assert(voxel.vertices.length)
117 				for(var i=0; i < voxel.vertices.length; i++) { // Fix voxel's position
118 					voxel.vertices[i].x += +(x-(spriteW-1)/2);	// what is this 7.5 ? why not 8 ? as in 16/2
119 					voxel.vertices[i].y += -(y-(spriteH-1)/2);
120 				}
121 				THREE.GeometryUtils.merge(geometry, voxel);
122 			}
123 		}
124 		// if the geometry is fully transparent, unset it - NOTE: this is set to null, not unsigned
125 		if( geometry.faces.length === 0 )	geometry = null;
126 		// scale it to be Cube(1, 1, 1)
127 		geometry	&& tQuery(geometry).scaleBy(1/Math.max(spriteW, spriteH))
128 		// cache the result
129 		geometries[id]	= geometry;
130 		// return the result
131 		return geometry;
132 	};
133 	/**
134 	 * Create a tQuery.Mesh with the sprite at x,y in the spritesheet
135 	 * @param {Number} x the x position of the sprite in the spritesheet
136 	 * @param {Number} y the y position of the sprite in the spritesheet
137 	 * @returns {tQuery.Mesh} the generate mesh
138 	*/
139 	function createMeshItem(x, y) {
140 		console.assert(typeof(x) === 'number');
141 		console.assert(typeof(y) === 'number');
142 		var id		= x + y * nSpriteX;
143 		var geometry	= getGeometry(id);
144 		if( !geometry )	return null;
145 		var mesh	= new THREE.Mesh( geometry, material );
146 		return tQuery(mesh);
147 	};	
148 
149 	// create the canvas element
150 	var canvas	= document.createElement('canvas');
151 	canvas.width	= imgW;
152 	canvas.height	= imgH;
153 	var context	= canvas.getContext('2d');
154 	var material	= getMaterial(canvas, false);
155 	// init some constants
156 	var tileUvW	= 1/canvas.width;
157 	var tileUvH	= 1/canvas.height;
158 	
159 
160 	var geometries	= [];
161 
162 	// load the image
163 	var items	= new Image();
164 	items.onload	= function(){
165 		// clear the canvas - TODO is this necessary ?
166 		context.clearRect(0, 0, canvas.width, canvas.height);
167 		// draw the loaded image to the canvas
168 		context.drawImage(items, 0, 0, canvas.width, canvas.height);
169 		// trigger the 'load' event
170 		this.trigger("load");
171 	}.bind(this);
172 	items.src = this._opts.url;
173 	
174 	// setup the public function
175 	this.createMesh		= createMeshItem;
176 	this.createGeometry	= getGeometry;
177 	this.imgW		= function(){ return imgW;	};
178 	this.imgH		= function(){ return imgH;	};
179 	this.spriteW		= function(){ return spriteW;	};
180 	this.spriteH		= function(){ return spriteH;	};
181 	this.nSpriteX		= function(){ return nSpriteX;	};
182 	this.nSpriteY		= function(){ return nSpriteY;	};
183 });
184 
185 // make it eventable
186 tQuery.MicroeventMixin(tQuery.Spritesheet.prototype);
187