1 (function(){ 2 /** 3 * Creates an instance of a Cube animation 4 * @class 5 * @name Cube 6 * @param x {Number} width of cube 7 * @param y {Number} depth of cube 8 * @param z {Number} height of cube 9 */ 10 function Cube(x, y, z){ 11 if(Math.max(x, y, z) > 32){ 12 throw new Error('Maximum cube dimension is 32'); 13 } 14 15 this.size = {x:x, y:y, z:z}; 16 this.frames = [ new cubeFrame(this) ]; 17 this.frame = 0; 18 } 19 20 /** @private */ 21 function getFrame(ix, cloneFrom){ 22 ix = 0|(ix < 0 ? 0 : ix); 23 24 this.frame = ix; 25 return (cloneFrom 26 ? this.frames[ix] = new cubeFrame(cloneFrom) 27 : this.frames[ix] || (this.frames[ix] = new cubeFrame(this)) 28 ); 29 } 30 31 Cube.prototype = { 32 /** 33 * Sets the cube to a specific frame number 34 * @param {Number} frameNumber 35 */ 36 getFrame : function(ix){ 37 return getFrame.call(this, ix); 38 }, 39 40 /** 41 * Sets the cube to the next frame of animation. 42 * Will automatically create new frames as necessary. 43 * @param {Boolean} [duplicateCurrent=false] Duplicate the current frame when creating the next one. If false, creates empty frames when necessary. 44 */ 45 nextFrame : function(duplicateCurrent){ return getFrame.call(this, this.frame + 1, duplicateCurrent && this.frames[this.frame]); }, 46 47 /** 48 * Sets the cube to the previous frame of animation. 49 * Will automatically create new frames as necessary. 50 * @param {Boolean} [duplicateCurrent=false] Duplicate the current frame when creating a previous one. If false, creates empty frames when necessary. 51 */ 52 prevFrame : function(duplicateCurrent){ return getFrame.call(this, this.frame - 1, duplicateCurrent && this.frames[this.frame]); } 53 } 54 55 56 /** @private */ 57 function cubeFrame(cloneFrom /* or cubeObject */){ 58 this.cube = []; 59 60 if(cloneFrom instanceof cubeFrame){ 61 this.cubeObject = cloneFrom.cubeObject; 62 this.size = this.cubeObject.size; 63 64 for(var z=0; z<this.size.z; z++){ 65 this.cube[z] = []; 66 for(var y=0; y<this.size.y; y++){ 67 this.cube[z][y] = cloneFrom.cube[z][y]; 68 } 69 } 70 }else{ 71 this.cubeObject = cloneFrom; 72 this.size = this.cubeObject.size; 73 this.fill(0); 74 } 75 } 76 77 /** @private */ 78 function writePlaneZ(z, val){ 79 if(z<0 || z>=this.size.z){ return; } 80 81 for(var y=0; y<this.size.y; y++){ 82 this.cube[z][y] = val ? 0xFFFFFFFF : 0; 83 } 84 } 85 86 /** @private */ 87 function writePlaneY(y, val){ 88 if(y<0 || y>=this.size.y){ return; } 89 90 for(var z=0; z<this.size.z; z++){ 91 this.cube[z][y] = val ? 0xFFFFFFFF : 0; 92 } 93 } 94 95 /** @private */ 96 function writePlaneX(x, val){ 97 if(x<0 || x>=this.size.x){ return; } 98 99 for (var z=0; z<this.size.z; z++){ 100 for (var y=0; y<this.size.y; y++){ 101 val 102 ? this.cube[z][y] |= (1 << x) 103 : this.cube[z][y] &= ~(1 << x); 104 } 105 } 106 } 107 108 cubeFrame.prototype = 109 110 /** @lends Cube.prototype */ 111 { 112 /** @private */ 113 inRange : function(x, y, z){ 114 return !(x<0 || x>=this.size.x || y<0 || y>=this.size.y || z<0 || z>=this.size.z); 115 }, 116 117 /** 118 * Sets a specific LED to ON state 119 * @param {Number} x 120 * @param {Number} y 121 * @param {Number} z 122 */ 123 setVoxel : function(x, y, z){ 124 if(this.inRange(x, y, z)){ 125 this.cube[z][y] |= 1 << x; 126 } 127 128 return this; 129 }, 130 131 /** 132 * Sets a specific LED to OFF state 133 * @param {Number} x 134 * @param {Number} y 135 * @param {Number} z 136 */ 137 clearVoxel : function(x, y, z){ 138 if(this.inRange(x, y, z)){ 139 this.cube[z][y] &= ~(1 << x); 140 } 141 142 return this; 143 }, 144 145 /** 146 * Toggles a specific LED (on-->off, off-->on) 147 * @param {Number} x 148 * @param {Number} y 149 * @param {Number} z 150 */ 151 flipVoxel : function(x, y, z){ 152 if(this.inRange(x, y, z)){ 153 this.cube[z][y] ^= (1 << x); 154 } 155 }, 156 157 /** 158 * Gets the state of a specific LED 159 * @param {Number} x 160 * @param {Number} y 161 * @param {Number} z 162 * @returns {Bool} on/off 163 */ 164 getVoxel : function(x, y, z){ 165 if(!this.inRange(x, y, z)){ return 0; } 166 167 return (this.cube[z][y] & (1 << x) ? 1 : 0); 168 }, 169 170 /** 171 * Combines the functionality of setVoxel and clearVoxel for when an alternate syntax is needed 172 * @param {Number} x 173 * @param {Number} y 174 * @param {Number} z 175 * @param {Boolean} state 176 */ 177 alterVoxel : function(x, y, z, val){ 178 return this[val ? 'setVoxel' : 'clearVoxel'](x, y, z); 179 }, 180 181 /** 182 * Turns an entire Z-Plane on 183 * @param {Number} Z-plane 184 */ 185 setPlaneZ : function(z){ 186 writePlaneZ.call(this, z, 1); 187 188 return this; 189 }, 190 191 /** 192 * Turns an entire Z-Plane off 193 * @param {Number} Z-plane 194 */ 195 clearPlaneZ : function(z){ 196 writePlaneZ.call(this, z, 0); 197 198 return this; 199 }, 200 201 /** 202 * Turns an entire X-Plane on 203 * @param {Number} X-plane 204 */ 205 setPlaneX : function(x){ 206 writePlaneX.call(this, x, 1); 207 208 return this; 209 }, 210 211 /** 212 * Turns an entire X-Plane off 213 * @param {Number} X-plane 214 */ 215 clearPlaneX : function(x){ 216 writePlaneX.call(this, x, 0); 217 218 return this; 219 }, 220 221 /** 222 * Turns an entire Y-Plane on 223 * @param {Number} Y-plane 224 */ 225 setPlaneY : function(y){ 226 writePlaneY.call(this, y, 1); 227 228 return this; 229 }, 230 231 /** 232 * Turns an entire Y-Plane off 233 * @param {Number} Y-plane 234 */ 235 clearPlaneY : function(y){ 236 writePlaneY.call(this, y, 0); 237 238 return this; 239 }, 240 241 /** 242 * Turns a plane on in any dimension 243 * @param {String} (x|y|z) axis 244 * @param {Number} plane 245 */ 246 setPlane : function(axis, plane){ 247 switch(axis){ 248 case 'x' : writePlaneX.call(this, num, 1); break; 249 case 'y' : writePlaneY.call(this, num, 1); break; 250 case 'z' : writePlaneZ.call(this, num, 1); break; 251 } 252 253 return this; 254 }, 255 256 /** 257 * Turns a plane off in any dimension 258 * @param {String} (x|y|z) axis 259 * @param {Number} plane 260 */ 261 clearPlane : function(axis, plane){ 262 switch(axis){ 263 case 'x' : clearPlaneX.call(this, num, 0); break; 264 case 'y' : clearPlaneY.call(this, num, 0); break; 265 case 'z' : clearPlaneZ.call(this, num, 0); break; 266 } 267 268 return this; 269 }, 270 271 /** 272 * Fills each [z][y] line of a cube with a given pattern. 273 * @param {Bytes} [pattern=0x00] 274 */ 275 fill : function(pattern){ 276 pattern || (pattern = 0); 277 278 for(var z=0; z<this.size.z; z++){ 279 this.cube[z] || (this.cube[z] = []); 280 for(var y=0; y<this.size.y; y++){ 281 this.cube[z][y] = pattern; 282 } 283 } 284 285 return this; 286 }, 287 288 /** 289 * Draws a line through the cube between any two points in 3D space 290 * @param {Number} x1 291 * @param {Number} y1 292 * @param {Number} z1 293 * @param {Number} x2 294 * @param {Number} y2 295 * @param {Number} z2 296 */ 297 drawLine : function(x1, y1, z1, x2, y2, z2){ 298 var xy, xz, x, y, z, tmp; 299 300 // We always want to draw the line from x=0 to x=..31. 301 // If x1 is bigget than x2, we need to flip all the values. 302 if(x1 > x2){ 303 tmp = x1; x1 = x2; x2 = tmp; 304 tmp = y1; y1 = y2; y2 = tmp; 305 tmp = z1; z1 = z2; z2 = tmp; 306 } 307 308 xy = (y1 > y2) ? (y1-y2)/(x2-x1) : (y2-y1)/(x2-x1); 309 xz = (z1 > z2) ? (z1-z2)/(x2-x1) : (z2-z1)/(x2-x1); 310 311 // For each step of x, y increments by: 312 for (x = x1; x<=x2; x++){ 313 y = (xy*(x-x1))+y1; 314 z = (xz*(x-x1))+z1; 315 316 this.setVoxel(0|x, 0|y, 0|z); 317 } 318 319 return this; 320 }, 321 322 /** 323 * Shifts the contents of a cube along an axis. Useful for effects like rain or bringing text/etc from one side of the cube to another 324 * @param {String} (x|y|z) 325 * @param {Number} amount positive or negative. Usually 1 or -1. 326 */ 327 shift : function(axis, amt){ 328 var order = { 329 'x' : ['y', 'z', 'x'], 330 'y' : ['x', 'z', 'y'], 331 'z' : ['x', 'y', 'z'] 332 }[axis], 333 l1 = this.size[order[0]], 334 l2 = this.size[order[1]], 335 l3 = this.size[order[2]], 336 337 dir = amt < 0 ? -1 : 1, 338 339 a, b, c, i, ii, iii, state; 340 341 for(a=0; a<l3; a++){ 342 ii = dir == -1 ? a : l3 - a + amt; 343 iii = ii - amt; 344 345 for(b=0; b<l1; b++){ 346 for(c=0; c<l2; c++){ 347 switch(axis){ 348 case 'x': state = this.getVoxel(iii, c, b); this.alterVoxel(ii, c, b, state); break; 349 case 'y': state = this.getVoxel(b, iii, c); this.alterVoxel(b, ii, c, state); break; 350 case 'z': state = this.getVoxel(b, c, iii); this.alterVoxel(b, c, ii, state); break; 351 } 352 } 353 } 354 } 355 356 i = dir == -1 ? l3 : 0; 357 358 for(var a=0, ii=Math.abs(dir); a<ii; a++){ 359 iii = i+a*dir; 360 for(b=0; b<l1; b++){ 361 for(c=0; c<l2; c++){ 362 switch(axis){ 363 case 'x': this.clearVoxel(b, c, iii); break; 364 case 'y': this.clearVoxel(b, iii, c); break; 365 case 'z': this.clearVoxel(iii, c, b); break; 366 } 367 } 368 } 369 } 370 371 return this; 372 } 373 }; 374 375 var proto = cubeFrame.prototype; 376 for(var func in proto){ 377 Cube.prototype[func] = (function(func){ 378 return function(){ 379 var frame = this.frames[this.frame], 380 ret = this.frames[this.frame][func].apply(frame, arguments); 381 382 return ret===frame ? this : ret; 383 }; 384 })(func); 385 } 386 387 388 window.Cube = Cube; 389 })()