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 })()