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