public enum Animation
{
	RESTORESTEADYSTATE,	// get back to a typical sized/rotated block 
	
	// Block Animations
	
	PICKUP,				// when a user places a finger on an interactable block
	DROP,				// when a user lifts a finger on a picked up block
	SWAPTO,				// used to swap the position of two blocks (when user drags finger)
	SETTLETO,			// used to implement gravity on the board
	DROPSPAWN,			// when a block spawns onto the board from the top
	GROWSPAWN,			// when a block spawns onto the board from the bottom
	GROWFLIPSPAWN,		// when a block spawns onto the board from the bottom while turning over and up (corkscrew)
	FIREWORKSPAWN,		// NOT USED
	ROWONDECKRELOCATE,	// used upon flipping of the board
	ROWONDECKSPAWN,		// used for spawning ROD
	MATCH,				// used when a match is found on the board
	MELTMEMBERS,		// used to inflate group members upon a matching next to a group
	MELTWRAPPER,		// used to deflate the wrapper containing a group
	WOBBLE,				// when blocks hit the top of the board
	DESTROY,
	
	// Bonus Animations
	
	BONUS_BOMB_SPIN_DESTROY,
	BONUS_WAVE_EPICENTER,
	BONUS_WAVE_EPICENTER_DESTROY,
	
	// 3D Text
	
	FLIPUPSPAWN,		// when 3d text flips into place (sit up out from the grave)
	FLIPOVERSPAWN,		// when 3d text flips into place (page flip)
	SQUISHROCKFLIPOVERSPAWN,	// same as flipoverspawn, but with spring
	SQUISHROCKFLIPOVERCENTERSPAWN,
	ROCKFLIPOUT,		// same as flipoverspawn, but with falling back as an outro (and no spring)
	SPINDEFORM,
	TILTFLOATUP,
	TILTBUBBLEUP,
	GAMEOVERFOLLOWBOARDROTATION,
	TEXTWOBBLE,
	SQUISHROCKFLIPOUT,
	SQUISHROCKFLIPTHREW,
	SQUISHROCKFLIPIN,
	;			// end of game / match blocks

	
	
	
	// array keys
	private static final int POS	= 0;
	private static final int ROT	= 1;
	private static final int SIZ	= 2;
	private static final int X		= 0;
	private static final int Y		= 1;
	private static final int Z		= 2;

	
	
	public static TransformState[] queueRestoreSteadyState( float[][] currentPoints )
	{
	
		
		
		return new TransformState[] 
		{
			new TransformState(
				TransformType.ROTATE, 												// Translate
				new Boolean[] {true, true, true}, 									// Z Dir only
				TimeDomain.FINITE, 													// Timed Animation
				new MotionEquation[] {
						MotionEquation.SPRING_UNDAMP_ALT_K4_5, 
						MotionEquation.SPRING_UNDAMP_ALT_K4_5, 
						MotionEquation.SPRING_UNDAMP_ALT_K4_5
				},
				currentPoints[ROT].clone(),								// start point
				new float[] {
						0,0,0
							}, 
				new int[] {100,200,300}, 									// duration: 400 ms
				new int[] {0,0,0}											// no trigger delay
			) // end TransformState 
	
			,
				
			 new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {true, true, true}, 						// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						MotionEquation.SPRING_UNDAMP_ALT_K4_5, 
						MotionEquation.SPRING_UNDAMP_ALT_K4_5, 
						MotionEquation.SPRING_UNDAMP_ALT_K4_5
						}, 	
				currentPoints[SIZ].clone(),							// start point: current position
				new float[] {
						1.0f,					 						// end X 
						1.0f, 											// end Y 
						1.0f											// end Z 
							}, 
				new int[] {100,200,300}, 								// duration: 700 ms
				new int[] {0,0,0}										// no trigger delay
			) // end TransformState 

		};
		
		
		
		
	}
	
	
	
	
	public static TransformState[] queuePickup( float[][] currentPoints )
	{

		return new TransformState[] 
		{
	
			new TransformState(
				TransformType.TRANSLATE, 												// Translate
				new Boolean[] {false, false, true}, 									// Z Dir only
				TimeDomain.FINITE, 														// Timed Animation
				new MotionEquation[] {
						null, 
						null,
						MotionEquation.SPRING_UNDAMP_ALT_K9_5
						//MotionEquation.SIN
				},
				currentPoints[POS].clone(),								// start point: current position
				new float[] {
						currentPoints[POS][X], 							// end X = start X
						currentPoints[POS][Y], 							// end Y = start Y
						0 + World.mDepthUnit*2		// default board depth + 2 depth units
							}, 
				new int[] {1,1,400}, 										// duration: 400 ms
				new int[] {0,0,0}											// no trigger delay
			) // end TransformState 

			,
			
			new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {true, true, false}, 						// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						MotionEquation.SPRING_UNDAMP_ALT_K9_5,
						MotionEquation.SPRING_UNDAMP_ALT_K9_5,
						MotionEquation.SPRING_UNDAMP_ALT_K9_5
						//MotionEquation.SIN, 
						//MotionEquation.SIN, 
						//MotionEquation.SIN, 
						}, 	
				currentPoints[SIZ].clone(),							// start point: current position
				new float[] {
						2f,					 						// end X 
						2f, 										// end Y 
						0.5f										// end Z 
							}, 
				new int[] {400,400,400},								// duration: 400 ms
				new int[] {0,0,0}										// no trigger delay
			) // end TransformState 
			
			
			,
			
			new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {false, true, false}, 					// direction
					TimeDomain.CONTINUOUS, 								// Timed Animation
					new MotionEquation[] {
							MotionEquation.SIN, 			
							MotionEquation.SIN,
							MotionEquation.SIN
							}, 	
					currentPoints[ROT].clone(),							// start point: current position
					new double[] {
							20, 			// rate X 
							20, 			// rate Y 
							20				// rate Z
								}, 
					new double[] {
							400, 			// unit X 
							400, 			// unit Y 
							400				// unit Z
								}, 
					new int[] {0, 0, 0}		// use given delay
				) // end TransformState 
			
		};
			 
	}


	public static TransformState[] queueDrop( float[][] currentPoints  )
	{
		
		return new TransformState[] 
		{
			new TransformState(
				TransformType.TRANSLATE, 												// Translate
				new Boolean[] {false, false, true}, 									// Z Dir only
				TimeDomain.FINITE, 														// Timed Animation
				new MotionEquation[] {
						null, 
						null, 
						/*MotionEquation.SIN_OVERSHOOT_SLOW,*/ 			// Z Bounce EQ
						//MotionEquation.LINEAR
						MotionEquation.SPRING_UNDAMP_ALT_K9_5
				},
				currentPoints[POS].clone(),								// start point: current position
				new float[] {
						currentPoints[POS][X], 							// end X = start X
						currentPoints[POS][Y], 							// end Y = start Y
						//mCurrentPoints[POS][Z] - World.mDepthUnit*3	// end Z = start Z - 3 depth unit
						0												// default board depth
							}, 
				new int[] {1,1,400}, 										// duration: 400 ms
				new int[] {0,0,0}											// no trigger delay
			) // end TransformState 
	
			,
				
			 new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {true, true, true}, 						// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						MotionEquation.SPRING_UNDAMP_P4, 
						MotionEquation.SPRING_UNDAMP_P4, 
						MotionEquation.SPRING_UNDAMP_P4
						}, 	
				currentPoints[SIZ].clone(),							// start point: current position
				new float[] {
						1.0f,					 						// end X 
						1.0f, 											// end Y 
						1.0f											// end Z 
							}, 
				new int[] {300,700,900}, 								// duration: 700 ms
				new int[] {0,0,0}										// no trigger delay
			) // end TransformState 

		};


	}
	
	
	

	public static TransformState[] queueSwapTo(float[][] currentPoints, Coord<Integer> fromRowCol, Coord<Integer> toRowCol, float[] startPos, float[] endPos, Integer duration, Orientation orient)
	{

		
		// determine how to move to the new coordinate
		int defaultDuration;
		if (duration == null)
			defaultDuration = 400;
		else
			defaultDuration = duration;
		
		int xDur = defaultDuration;		// total animation duration in x direction
		int yDur = defaultDuration;		// total animation duration in y direction
		int xDelay = 0;					// ms to wait before starting animation in x direction
		int yDelay = 0;					// ms to wait before starting animation in y direction
		
		int deltaRowCount = Math.abs(fromRowCol.getRow() - toRowCol.getRow());
		int deltaColCount = 1;	// always swapping on single column change
		
		MotionEquation yEq = MotionEquation.SPRING_UNDAMP_ALT_K4_5;
		MotionEquation xEq = MotionEquation.SPRING_UNDAMP_ALT_K4_5;
		
		// cross-row swap; swap around a corner
		if (fromRowCol.getRow() != toRowCol.getRow())
		{
			
			xDur	= (defaultDuration/(deltaRowCount + deltaColCount))*deltaColCount;
			yDur 	= (defaultDuration/(deltaRowCount + deltaColCount))*deltaRowCount; 
			
			xEq = MotionEquation.LOGISTIC;
			
			if (
					fromRowCol.getRow() > toRowCol.getRow() && orient == Orientation.NORMAL  ||
					fromRowCol.getRow() < toRowCol.getRow() && orient == Orientation.REVERSE  
				)
			{
				// move up some rows first, then over (overlap by a half block)
				xDelay	= (int) ((defaultDuration/(deltaRowCount + deltaColCount))*(deltaRowCount-0.5));
				yEq = MotionEquation.SIN;
			}
			else
			{
				// move over first, then move down some rows  (overlap by a half block)
				yDelay	= (int) ((defaultDuration/(deltaRowCount + deltaColCount))*(deltaColCount-0.5));
				yEq = MotionEquation.SPRING_UNDAMP_ALT_K4_5;
			}
			
		} // end else
		
		
		
		return new TransformState[] 
		{ 
			new TransformState(
				TransformType.TRANSLATE, 							// Translate
				new Boolean[] {true, true, false}, 					// X & Y dir
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						xEq, 			// X & Y Bounce EQ
						yEq, 			// X & Y Bounce EQ 
						//MotionEquation.LINEAR,	//debug
						//MotionEquation.LINEAR,	//debug
						null
						}, 	
				startPos,								// start point: current position or end of current running animation
				new float[] {
						endPos[X], 						// end X = start X + offset
						endPos[Y], 						// end Y = start Y + offset
						currentPoints[POS][Z]			// end Z = start Z
							}, 
				new int[] {xDur		, yDur,		1}, 	// duration (ms)
				new int[] {xDelay	, yDelay,	0}		// no trigger delay
			) // end TransformState 
		};
		
	}
	

	
	
	
	

	
	public static TransformState[] queueSettleTo(float[][] currentPoints, float[] startPos, float[] endPos, int delay)
	{
		
		return new TransformState[] 
		{
			new TransformState(
				TransformType.TRANSLATE, 							// Translate
				new Boolean[] {false, true, false}, 				// Y dir
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						null, 			
						MotionEquation.SPRING_UNDAMP_ALT_K4_5,
						//MotionEquation.LINEAR,
						null
						}, 	
				startPos.clone(),						// start point: current position or end of current running animation
				new float[] {
						endPos[X], 						// end X = start X + offset
						endPos[Y], 						// end Y = start Y + offset
						currentPoints[POS][Z]			// end Z = start Z
							},
				
							// 600
				new int[] {1, 600, 1}, 				// duration (ms)
				new int[] {0, delay,    0}			// use given delay
				
				/*
				new int[] {1, 500, 1}, 				// duration (ms)
				new int[] {0, 0,    0}			// use given delay
				*/
			) // end TransformState 
		};
		
	}

	

	
	

	// drops from top of board to given position
	
	public static TransformState[] queueDropSpawn( float[] startPos, float[] endPos, int duration, int delay )
	{			
		
		return new TransformState[] 
		{
			new TransformState(
				TransformType.TRANSLATE, 							// Translate
				new Boolean[] {false, true, false}, 				// Y dir
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						null, 			
						MotionEquation.LINEAR,
						null
						}, 	
				startPos.clone(),						// start point: current position or end of current running animation
				new float[] {
						endPos[X], 						// end X 
						endPos[Y], 						// end Y 
						0								// end Z = board level
							}, 
				new int[] {1, duration,  1}, 	// duration (ms)
				new int[] {0, delay, 0}		// use given delay
			) // end TransformState 
		};
		
		/*

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, true}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.SPRING_UNDAMP_ALT_K4_5, 			
						MotionEquation.SPRING_UNDAMP_ALT_K4_5,
						MotionEquation.SPRING_UNDAMP_ALT_K4_5
						}, 	
				new float[] {
						45, 					// start X 
						45, 					// start Y 
						45						// start Z
							}, 
				new float[] {
						0, 						// end X 
						0, 						// end Y 
						0						// end Z
							}, 
				new int[] {500, 500, 500}, 	// duration (ms)
				new int[] {0, 100, 200}		// use given delay
			) // end TransformState 
		*/

	}
	
	
	
	
	// grows from bottom of board to given position

	public static TransformState[] queueGrowSpawn( float[] startPos, float[] endPos, int delay )
	{	
		return new TransformState[] 
		{
			new TransformState(
				TransformType.TRANSLATE, 							// Translate
				new Boolean[] {false, true, false}, 				// Y dir
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						null, 			
						MotionEquation.LOGISTIC,
						null
						}, 	
				startPos.clone(),						// start point: current position or end of current running animation
				new float[] {
						endPos[X], 						// end X 
						endPos[Y], 						// end Y 
						0								// end Z = board level
							}, 
				new int[] {1, 600,  1}, 	// duration (ms)
				new int[] {0, delay, 0}		// use given delay
			) // end TransformState 
		};
			
		/*
		TransformState spawn_rotate = 
				new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {true, true, true}, 					// direction
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.SPRING_UNDAMP_ALT_K4_5, 			
							MotionEquation.SPRING_UNDAMP_ALT_K4_5,
							MotionEquation.SPRING_UNDAMP_ALT_K4_5
							}, 	
					new float[] {
							45, 					// start X 
							45, 					// start Y 
							45						// start Z
								}, 
					new float[] {
							0, 						// end X 
							0, 						// end Y 
							0						// end Z
								}, 
					new int[] {500, 500, 500}, 	// duration (ms)
					new int[] {0, 100, 200}		// use given delay
				); // end TransformState 
		*/

	}
	
	
	
	
	// grows from bottom of board to given position

	public static TransformState[] queueGrowFlipSpawn( float[] startPos, float[] endPos, int duration, int delay )
	{	
		return new TransformState[] 
		{
			new TransformState(
				TransformType.TRANSLATE, 							// Translate
				new Boolean[] {false, true, false}, 				// Y dir
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						null, 			
						MotionEquation.LOGISTIC,
						null
						}, 	
				startPos.clone(),						// start point: current position or end of current running animation
				new float[] {
						endPos[X], 						// end X 
						endPos[Y], 						// end Y 
						0								// end Z = board level
							}, 
				new int[] {1, duration,  1}, 	// duration (ms)
				new int[] {0, delay, 0}		// use given delay
			) // end TransformState 
			
			,
			
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {true, true, false}, 				// Y dir
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,			
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC,
							}, 	
					new float[] {-90,-90,0}, 		// start point: current position or end of current running animation
					new float[] {0,0,0}, 		// end point
					new int[] {600, 600,  600}, 	// duration (ms)
					new int[] {delay, delay, delay}		// use given delay
				) // end TransformState 
			
		};
			

	}
	
	
	

	
	
	// drops from top of board to given position

	public static TransformState[] queueFireworksSpawn(float[] startPos, float[] middlePos, float[] endPos, int delay)
	{
		
		int duration = 600;
		
		
		return new TransformState[] 
		{
			new TransformState(
				TransformType.TRANSLATE, 							// Translate
				new Boolean[] {false, true, false}, 				// Y dir
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						null, 			
						MotionEquation.LOGISTIC,
						null
						}, 	
				startPos.clone(),						// start point: current position or end of current running animation
				new float[] {
						middlePos[X], 					// end X 
						middlePos[Y], 					// end Y 
						0								// end Z = board level
							}, 
				new int[] {1, duration+delay,   1}, 	// duration (ms)
				new int[] {0, delay, 0}		// use given delay
			) // end TransformState 
	
			,

			new TransformState(
				TransformType.TRANSLATE, 					// Translate
				new Boolean[] {false, true, false}, 		// Y dir
				TimeDomain.FINITE, 							// Timed Animation
				new MotionEquation[] {
						null, 			
						MotionEquation.LOGISTIC,
						null
						}, 	
				new float[] {
						middlePos[X], 					// start X 
						middlePos[Y], 					// start Y 
						0								// start Z = board level
							}, 
				new float[] {
						endPos[X], 						// end X 
						endPos[Y], 						// end Y 
						0								// end Z = board level
							}, 
				new int[] {1, duration,   	  1}, 			// duration (ms)
				new int[] {0, delay+duration, 0}		// use given delay
			) // end TransformState 
		
		};
		
		/*
		TransformState spawn_rotate = 
				new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {true, true, true}, 					// direction
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.SPRING_UNDAMP_ALT_K4_5, 			
							MotionEquation.SPRING_UNDAMP_ALT_K4_5,
							MotionEquation.SPRING_UNDAMP_ALT_K4_5
							}, 	
					new float[] {
							45, 					// start X 
							45, 					// start Y 
							45						// start Z
								}, 
					new float[] {
							0, 						// end X 
							0, 						// end Y 
							0						// end Z
								}, 
					new int[] {500, 500, 500}, 	// duration (ms)
					new int[] {0, 100, 200}		// use given delay
				); // end TransformState 
		*/

	}
	
	
	
	
	// drops from top of board to given position

	public static TransformState[] queueRowOnDeckRelocate(float[] startPos, float[] middlePos1, float[] middlePos2, float[] endPos, int delay)
	{
		
		int duration = 300;
		
		
		return new TransformState[] 
		{
			new TransformState(
				TransformType.TRANSLATE, 				// Translate
				new Boolean[] {false, true, false}, 	// Y dir
				TimeDomain.FINITE, 						// Timed Animation
				new MotionEquation[] {
						null,			
						MotionEquation.LOGISTIC,
						null
						}, 	
				startPos.clone(),						
				middlePos1.clone(),
				new int[] {1, duration,   1}, 	// duration (ms)
				new int[] {0, delay, 0}				// use given delay
			) // end TransformState 
	
			,
			
			new TransformState(
					TransformType.TRANSLATE, 							// Translate
					new Boolean[] {true, true, false}, 				// Y dir
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR,		
							MotionEquation.LINEAR,
							null
							}, 	
					middlePos1.clone(),
					new float[] {
							middlePos1[X]+5, 					// end X 
							middlePos1[Y], 					// end Y 
							0								// end Z = board level
								}, 
					new int[] {1, 1, 0}, 	// duration (ms)
					new int[] {delay+duration+1, delay+duration+1, 0}		// use given delay
				) // end TransformState 

			,

			new TransformState(
					TransformType.TRANSLATE, 							// Translate
					new Boolean[] {true, true, false}, 				// Y dir
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR,			
							MotionEquation.LINEAR,
							null
							}, 	
					new float[] {
							middlePos1[X]+5, 					// start X 
							middlePos1[Y], 					// start Y 
							0								// start Z = board level
								}, 
					new float[] {
							middlePos2[X]+5, 					// end X 
							middlePos2[Y], 					// end Y 
							0								// end Z = board level
								}, 
					new int[] {1, 1, 0}, 	// duration (ms)
					new int[] {delay+duration+2, delay+duration+2, 0}		// use given delay
				) // end TransformState 
			
			,

			new TransformState(
					TransformType.TRANSLATE, 							// Translate
					new Boolean[] {true, true, false}, 				// Y dir
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR,		
							MotionEquation.LINEAR,
							null
							}, 	
					new float[] {
							middlePos2[X]+5, 					// start X 
							middlePos2[Y], 					// start Y 
							0								// start Z = board level
								}, 
					new float[] {
							middlePos2[X], 					// end X 
							middlePos2[Y], 					// end Y 
							0								// end Z = board level
								}, 
					new int[] {1, 1, 0}, 	// duration (ms)
					new int[] {delay+duration+3, delay+duration+3, 0}		// use given delay
				) // end TransformState 
			
			,
			

			new TransformState(
					TransformType.TRANSLATE, 				// Translate
					new Boolean[] {false, true, false}, 	// Y dir
					TimeDomain.FINITE, 						// Timed Animation
					new MotionEquation[] {
							null,			
							MotionEquation.LOGISTIC,
							null
							}, 	
					middlePos2.clone(),						
					endPos.clone(),
					new int[] {1, duration,   1}, 				// duration (ms)
					new int[] {0, delay+duration+4, 0}		// use given delay
				) // end TransformState 
		
		};


	}
	

	
	
	

	public static TransformState[] queueRowOnDeckSpawn(float[] startPos, float[] endPos, int delay)
	{
		
		int duration = 300;
		
		
		return new TransformState[] 
		{

			new TransformState(
				TransformType.TRANSLATE, 				// Translate
				new Boolean[] {true, true, false}, 		// Y dir
				TimeDomain.FINITE, 						// Timed Animation
				new MotionEquation[] {
						MotionEquation.LOGISTIC,			
						MotionEquation.LOGISTIC,
						null
						}, 	
				new float[] {
						startPos[X], 					// start X 
						startPos[Y], 					// start Y 
						0								// start Z = board level
							}, 
				new float[] {
						endPos[X], 						// end X 
						endPos[Y], 						// end Y 
						0								// end Z = board level
							}, 
				new int[] { duration, duration,   	  1}, 			// duration (ms)
				new int[] { delay+duration+4, delay+duration+4, 0}	// use given delay
			) // end TransformState 
		
		};

	}

	


	
	
	
	
	
	
	
	
	
	
	
	
	public static TransformState[] queueMatch(float[][] currentPoints, int delay, int stageDuration)
	{
		//Flashing: during delay
		//Stage 1: pickup (over all stages)
		//Stage 2: spin left/up 90 deg, crunch X 
		//Stage 3: spin left/up 90 deg, crunch y
		//Stage 4: crunch all, spin all (disappear)
		
		// AL IN ALL: 6 STAGE LENGTH
		
		int flashDelay = 400;
		int numFlashes = 2;
		
		return new TransformState[] 
		{

			
			// STAGE 1
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {false, false, true}, 			// Y dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					currentPoints[POS].clone(),						// start point: current position or end of current running animation
					new float[] {
							currentPoints[POS][X], 							// end X = start X
							currentPoints[POS][Y], 							// end Y = start Y
							//0 + World.mDepthUnit*6		// default board depth + 2 depth units
							0 + World.mDepthUnit*20
							}, 
					new int[] {1, 1, stageDuration*4}, 	// duration (ms)
					new int[] {flashDelay+delay/4, flashDelay+delay/4, flashDelay+delay/4}		// use given delay
				) // end TransformState 
			,
			
			// STAGE 2
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, false, false}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							null,
							null
							}, 	
					currentPoints[SIZ].clone(),							// start point: current position
					new float[] {
							0.2f,					 						// end X 
							1f, 											// end Y 
							1f												// end Z 
								}, 
					new int[] {stageDuration,1,1},	 								// duration: 400 ms
					new int[] {flashDelay+delay,flashDelay+delay,flashDelay+delay}										// no trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, false}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						null
						}, 	
				currentPoints[ROT].clone(),	
				new float[] {
						90, 						// end X 
						90, 						// end Y 
						0						// end Z
							}, 
				new int[] {stageDuration, stageDuration, 1}, 	// duration (ms)
				new int[] {flashDelay+delay,flashDelay+delay,flashDelay+delay}		// use given delay
			) // end TransformState 
			
			,
			
			// STAGE 3
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, true, false}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							null
							}, 	
					new float[] {
							0.2f,					 						// start X 
							1f, 											// start Y 
							1f												// start Z 
								}, 
					new float[] {
							0.2f,					 						// end X 
							0.2f, 											// end Y 
							1f												// end Z 
								}, 
					new int[] {stageDuration,stageDuration,1},	 			// duration (ms)
					new int[] {flashDelay+delay+stageDuration,flashDelay+delay+stageDuration,flashDelay+delay}				// trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, false}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						null
						}, 	
				new float[] {
						90, 					// end X 
						90, 					// end Y 
						0						// end Z
							}, 
				new float[] {
						180, 					// end X 
						180, 					// end Y 
						0						// end Z
							}, 
				new int[] {stageDuration, stageDuration, 1}, 	// duration (ms)
				new int[] {flashDelay+delay+stageDuration, flashDelay+delay+stageDuration, flashDelay+delay}		// use given delay
			) // end TransformState 
			
			,
			
			// STAGE 4
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, true, true}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							}, 	
					new float[] {
							0.2f,					 						// start X 
							0.2f, 											// start Y 
							1f												// start Z 
								}, 
					new float[] {
							0f,					 						// end X 
							0f, 											// end Y 
							0f												// end Z 
								}, 
					new int[] {stageDuration,stageDuration, stageDuration},	 		// duration (ms)
					new int[] { flashDelay+delay+stageDuration*2, flashDelay+delay+stageDuration*2 ,flashDelay+delay+stageDuration*2}	// trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, true}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						MotionEquation.LINEAR, 
						}, 	
				new float[] {
						180, 					// end X 
						180, 					// end Y 
						0						// end Z
							}, 
				new float[] {
						270, 					// end X 
						270, 					// end Y 
						90						// end Z
							}, 
				new int[] {stageDuration, stageDuration, stageDuration}, 	// duration (ms)
				new int[] {flashDelay+delay+stageDuration*2, flashDelay+delay+stageDuration*2, flashDelay+delay+stageDuration*2}		// use given delay
			) // end TransformState 
			
			,
			
			// RESET ROTATION (incase of redestroy?)
			new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {true, true, true}, 					// direction
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 			
							MotionEquation.LINEAR,
							MotionEquation.LINEAR, 
							}, 	
					new float[] {
							0, 					// end X 
							0, 					// end Y 
							0						// end Z
								}, 
					new float[] {
							0, 					// end X 
							0, 					// end Y 
							0						// end Z
								}, 
					new int[] {1, 1, 1}, 	// duration (ms)
					new int[] { flashDelay+delay+stageDuration*3,  flashDelay+delay+stageDuration*3,  flashDelay+delay+stageDuration*3}		// use given delay
				) // end TransformState 
			
		};
	
	}
	
	
	
	
	
	
	
	
	

	
	
	
	
	public static TransformState[] queueMeltWapper( float[][] currentPoints, int duration, int delay  )
	{
		
		return new TransformState[] 
		{
		
			 new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {false, true, false}, 					// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						null,
						MotionEquation.LINEAR, 
						null
						}, 	
				currentPoints[SIZ].clone(),							// start point: current position
				new float[] {
						1.0f,					 						// end X 
						0f, 											// end Y 
						1.0f											// end Z 
							}, 
				new int[] {1,duration,1}, 								// duration: 700 ms
				new int[] {delay, delay, delay}							// no trigger delay
			) // end TransformState 

			
			 ,
			 
			 
			 new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {true, false, true}, 						// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 
						null, 
						MotionEquation.LINEAR
						}, 	
				new float[] {
						1.0f,					 					// start X 
						0f, 										// start Y 
						1.0f										// start Z 
							}, 
				new float[] {
						0f,					 						// end X 
						0f, 										// end Y 
						0f											// end Z 
							}, 
				new int[] {duration/2,duration/2,duration/2}, 								// duration: 700 ms
				new int[] {(int) (delay + duration), (int) (delay + duration), (int) (delay + duration)}		// trigger delay
			) // end TransformState 
			 
		};


	}
	
	
	
	
	
	
	
	
	public static TransformState[] queueMeltMembers( float[][] currentPoints,  int duration, int delay  )
	{
		
		return new TransformState[] 
		{
			// new
			new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {true, true, true}, 					// direction
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC, 			
							MotionEquation.LOGISTIC,
							MotionEquation.SPRING_UNDAMP_ALT_K9_3,
							}, 	
					new float[] {
							-180, 					// end X 
							180, 					// end Y 
							180						// end Z
								}, 
					new float[] {
							0, 					// end X 
							0, 					// end Y 
							0						// end Z
								}, 
					new int[] {(int) (duration*0.6*0.6),(int) (duration*0.8*0.6),(int) (duration*0.6)}, 								// duration: ms
					new int[] {delay, delay, delay}							// no trigger delay
				) // end TransformState 
			,
				
			 new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {true, true, true}, 						// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						MotionEquation.SPRING_UNDAMP_ALT_K9_3, 
						MotionEquation.SPRING_UNDAMP_ALT_K9_3, 
						MotionEquation.SPRING_UNDAMP_ALT_K9_3
						}, 	

				currentPoints[SIZ].clone(),							// start point: current position
				new float[] {
						1.3f,					 						// end X 
						1.3f, 											// end Y 
						1.3f											// end Z 
							}, 
				//new int[] {(int) (duration*0.6),(int) (duration*0.8),(int) (duration)}, 
				new int[] {(int) (duration*0.6*0.6),(int) (duration*0.8*0.6),(int) (duration*0.6)}, 								// duration: ms
				new int[] {delay, delay, delay}							// no trigger delay
			) // end TransformState 
			 
			 
			,
			
			new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {true, true, true}, 						// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						MotionEquation.LOGISTIC, 
						MotionEquation.LOGISTIC, 
						MotionEquation.LOGISTIC
						}, 	
				new float[] {
						1.3f,					 						// end X 
						1.3f, 											// end Y 
						1.3f											// end Z 
							}, 
				new float[] {
						1.0f,					 						// end X 
						1.0f, 											// end Y 
						1.0f											// end Z 
							}, 
				new int[] {(int) (duration*0.6*0.4),(int) (duration*0.8*0.4),(int) (duration*0.4)}, 									// duration: ms
				new int[] {delay+(int) (duration*0.4), delay+(int) (duration*0.4), delay+(int) (duration*0.4)}							// no trigger delay
			) // end TransformState 
			 
		};


	}
	
	
	
	
	
	
	
	public static TransformState[] queueWobble( float[][] currentPoints )
	{

		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {false, true, false}, 					// direction
					TimeDomain.CONTINUOUS, 								// Timed Animation
					new MotionEquation[] {
							null, 			
							MotionEquation.SIN,
							null
							}, 	
					currentPoints[ROT].clone(),							// start point: current position
					new double[] {
							0, 				// rate X 
							5, 			// rate Y 
							0				// rate Z
								}, 
					new double[] {
							0, 				// unit X 
							600, 			// unit Y 
							0				// unit Z
								}, 
					new int[] {0, 0, 0}		// use given delay
				) // end TransformState 
			
			,
			
			new TransformState(
					TransformType.SCALE,	 							// Translate
					new Boolean[] {true, true, false}, 					// direction
					TimeDomain.CONTINUOUS, 								// Timed Animation
					new MotionEquation[] {
							MotionEquation.SIN, 			
							MotionEquation.SIN,
							null
							}, 	
					currentPoints[SIZ].clone(),							// start point: current position
					new double[] {
							-0.1, 				// rate X 
							0.25, 				// rate Y 
							0				// rate Z
								}, 
					new double[] {
							600, 				// unit X 
							600, 			// unit Y 
							0				// unit Z
								}, 
					new int[] {0, 0, 0}		// use given delay
				) // end TransformState 
			
		};
			 
	}
	
	
	
	
	
	
	
	
	
	
	
	
	public static TransformState[] queueDestroy(float[][] currentPoints, int delay, int stageDuration)
	{
		//Wave   : Spans over 4 stages, overlaps with 2
		//Stage 1: X
		//Stage 2: spin left/up 90 deg, crunch X 
		//Stage 3: spin left/up 90 deg, crunch y
		//Stage 4: crunch all, spin all (disappear)
		
		// AL IN ALL: 6 STAGE LENGTH
		
		int waveTime = stageDuration*4; // up, down, down, up (sin)
		int waveDepth = 3;
		
		return new TransformState[] 
		{
			// Wave: Pickup
			new TransformState(
					TransformType.TRANSLATE, 				// Translate
					new Boolean[] {false, false, true}, 	// Z dir
					TimeDomain.FINITE, 						// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					currentPoints[POS].clone(),			// start point: current position or end of current running animation
					new float[] {
							currentPoints[POS][X], 		// end X = start X
							currentPoints[POS][Y], 		// end Y = start Y
							0 + World.mDepthUnit*waveDepth		// default board depth + 5 depth units
							}, 
					new int[] {1, 1, stageDuration}, 	// duration (ms)
					new int[] {delay, delay, delay}		// use given delay
				) // end TransformState 
			,
			
			// Wave: Rotate For
			new TransformState(
					TransformType.ROTATE, 				// Translate
					new Boolean[] {true, true, false}, 	// Z dir
					TimeDomain.FINITE, 						// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC,			
							}, 	
					currentPoints[ROT].clone(),			// start point: current position or end of current running animation
					new float[] {
							90, 		
							45, 		
							0 							
							}, 
					new int[] {stageDuration, stageDuration, stageDuration}, 	// duration (ms)
					new int[] {delay, delay, delay}		// use given delay
				) // end TransformState 
			,
			
			// Wave: Drop 1 & 2
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {false, false, true}, 			// Z dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					new float[] {
							currentPoints[POS][X], 		// start X 
							currentPoints[POS][Y], 		// start Y 
							0 + World.mDepthUnit*waveDepth		//
							}, 
					new float[] {
							currentPoints[POS][X], 		// end X = start X
							currentPoints[POS][Y], 		// end Y = start Y
							0 - World.mDepthUnit*waveDepth		// default board depth + 5 depth units
							}, 
					new int[] {1, 1, stageDuration*2}, 	// duration (ms)
					new int[] {delay, delay, delay + stageDuration}		// use given delay
				) // end TransformState 
			
			,
			// Wave: Rotate Back
			new TransformState(
					TransformType.ROTATE, 				// Translate
					new Boolean[] {true, true, false}, 	// Z dir
					TimeDomain.FINITE, 						// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC,			
							}, 	
					new float[] {
							90, 		
							45, 		
							0 							
							}, 
					currentPoints[ROT].clone(),
					new int[] {stageDuration, stageDuration, stageDuration}, 	// duration (ms)
					new int[] {delay+stageDuration, delay+stageDuration, delay+stageDuration}		// use given delay
				) // end TransformState 
			,
			
			// Wave: Settle up
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {false, false, true}, 			// Z dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					new float[] {
							currentPoints[POS][X], 		// start X 
							currentPoints[POS][Y], 		// start Y 
							0 - World.mDepthUnit*waveDepth		// Board Level - 5 depth units
							}, 
					new float[] {
							currentPoints[POS][X], 		// end X = start X
							currentPoints[POS][Y], 		// end Y = start Y
							0 							// Board Level
							}, 
					new int[] {1, 1, stageDuration}, 	// duration (ms)
					new int[] {delay, delay, delay + stageDuration*3}		// use given delay
				) // end TransformState 
			
			,
			
			// STAGE 2
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, false, false}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							null,
							null
							}, 	
					currentPoints[SIZ].clone(),							// start point: current position
					new float[] {
							0.2f,					 						// end X 
							1f, 											// end Y 
							1f												// end Z 
								}, 
					new int[] {stageDuration,1,1},	 								// duration: 400 ms
					new int[] {waveTime/2 + delay,waveTime/2 + delay,waveTime/2 + delay}										// no trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, false}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						null
						}, 	
				currentPoints[ROT].clone(),	
				new float[] {
						90, 						// end X 
						90, 						// end Y 
						0						// end Z
							}, 
				new int[] {stageDuration, stageDuration, 1}, 	// duration (ms)
				new int[] {waveTime/2 + delay,waveTime/2 + delay,waveTime/2 + delay}		// use given delay
			) // end TransformState 
			
			,
			
			// STAGE 3
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, true, false}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							null
							}, 	
					new float[] {
							0.2f,					 						// start X 
							1f, 											// start Y 
							1f												// start Z 
								}, 
					new float[] {
							0.2f,					 						// end X 
							0.2f, 											// end Y 
							1f												// end Z 
								}, 
					new int[] {stageDuration,stageDuration,1},	 			// duration (ms)
					new int[] {waveTime/2 + delay+stageDuration,waveTime/2 + delay+stageDuration,waveTime/2 + delay}				// trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, false}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						null
						}, 	
				new float[] {
						90, 					// end X 
						90, 					// end Y 
						0						// end Z
							}, 
				new float[] {
						180, 					// end X 
						180, 					// end Y 
						0						// end Z
							}, 
				new int[] {stageDuration, stageDuration, 1}, 	// duration (ms)
				new int[] {waveTime/2 + delay+stageDuration, waveTime/2 + delay+stageDuration, waveTime/2 + delay}		// use given delay
			) // end TransformState 
			
			,
			
			// STAGE 4
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, true, true}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							}, 	
					new float[] {
							0.2f,					 						// start X 
							0.2f, 											// start Y 
							1f												// start Z 
								}, 
					new float[] {
							0f,					 						// end X 
							0f, 											// end Y 
							0f												// end Z 
								}, 
					new int[] {stageDuration,stageDuration, stageDuration},	 		// duration (ms)
					new int[] {waveTime/2 + delay+stageDuration*2,waveTime/2 + delay+stageDuration*2 ,waveTime/2 + delay+stageDuration*2}	// trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, true}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						MotionEquation.LINEAR, 
						}, 	
				new float[] {
						180, 					// end X 
						180, 					// end Y 
						0						// end Z
							}, 
				new float[] {
						270, 					// end X 
						270, 					// end Y 
						90						// end Z
							}, 
				new int[] {stageDuration, stageDuration, stageDuration}, 	// duration (ms)
				new int[] {waveTime/2 + delay+stageDuration*2, waveTime/2 + delay+stageDuration*2, waveTime/2 + delay+stageDuration*2}		// use given delay
			) // end TransformState 
			
			,
			
			// RESET ROTATION (incase of redestroy?)
			new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {true, true, true}, 					// direction
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 			
							MotionEquation.LINEAR,
							MotionEquation.LINEAR, 
							}, 	
					new float[] {
							0, 					// end X 
							0, 					// end Y 
							0						// end Z
								}, 
					new float[] {
							0, 					// end X 
							0, 					// end Y 
							0						// end Z
								}, 
					new int[] {1, 1, 1}, 	// duration (ms)
					new int[] {waveTime/2 + delay+stageDuration*3, waveTime/2 + delay+stageDuration*3, waveTime/2 + delay+stageDuration*3}		// use given delay
				) // end TransformState 
			
		};
		
		
	}
	
	
	/*

	public static TransformState[] queueDestroy(float[][] currentPoints, int delay, int stageDuration)
	{
		//Wave   : Spans over 4 stages, overlaps with 2
		//Stage 1: X
		//Stage 2: spin left/up 90 deg, crunch X 
		//Stage 3: spin left/up 90 deg, crunch y
		//Stage 4: crunch all, spin all (disappear)
		
		// AL IN ALL: 6 STAGE LENGTH
		
		int waveTime = stageDuration*4; // up, down, down, up (sin)
		int waveDepth = 5;
		
		return new TransformState[] 
		{
			// Wave: Pickup
			new TransformState(
					TransformType.TRANSLATE, 				// Translate
					new Boolean[] {false, false, true}, 	// Z dir
					TimeDomain.FINITE, 						// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					currentPoints[POS].clone(),			// start point: current position or end of current running animation
					new float[] {
							currentPoints[POS][X], 		// end X = start X
							currentPoints[POS][Y], 		// end Y = start Y
							0 + World.mDepthUnit*waveDepth		// default board depth + 5 depth units
							}, 
					new int[] {1, 1, stageDuration}, 	// duration (ms)
					new int[] {delay, delay, delay}		// use given delay
				) // end TransformState 
			,
			
			// Wave: Drop 1 & 2
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {false, false, true}, 			// Z dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					new float[] {
							currentPoints[POS][X], 		// start X 
							currentPoints[POS][Y], 		// start Y 
							0 + World.mDepthUnit*waveDepth		//
							}, 
					new float[] {
							currentPoints[POS][X], 		// end X = start X
							currentPoints[POS][Y], 		// end Y = start Y
							0 - World.mDepthUnit*waveDepth		// default board depth + 5 depth units
							}, 
					new int[] {1, 1, stageDuration*2}, 	// duration (ms)
					new int[] {delay, delay, delay + stageDuration}		// use given delay
				) // end TransformState 
			
			,
			
			
			// Wave: Settle up
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {false, false, true}, 			// Z dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					new float[] {
							currentPoints[POS][X], 		// start X 
							currentPoints[POS][Y], 		// start Y 
							0 - World.mDepthUnit*waveDepth		// Board Level - 5 depth units
							}, 
					new float[] {
							currentPoints[POS][X], 		// end X = start X
							currentPoints[POS][Y], 		// end Y = start Y
							0 							// Board Level
							}, 
					new int[] {1, 1, stageDuration}, 	// duration (ms)
					new int[] {delay, delay, delay + stageDuration*3}		// use given delay
				) // end TransformState 
			
			,
			
			// STAGE 2
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, false, false}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							null,
							null
							}, 	
					currentPoints[SIZ].clone(),							// start point: current position
					new float[] {
							0.2f,					 						// end X 
							1f, 											// end Y 
							1f												// end Z 
								}, 
					new int[] {stageDuration,1,1},	 								// duration: 400 ms
					new int[] {waveTime/2 + delay,waveTime/2 + delay,waveTime/2 + delay}										// no trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, false}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						null
						}, 	
				currentPoints[ROT].clone(),	
				new float[] {
						90, 						// end X 
						90, 						// end Y 
						0						// end Z
							}, 
				new int[] {stageDuration, stageDuration, 1}, 	// duration (ms)
				new int[] {waveTime/2 + delay,waveTime/2 + delay,waveTime/2 + delay}		// use given delay
			) // end TransformState 
			
			,
			
			// STAGE 3
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, true, false}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							null
							}, 	
					new float[] {
							0.2f,					 						// start X 
							1f, 											// start Y 
							1f												// start Z 
								}, 
					new float[] {
							0.2f,					 						// end X 
							0.2f, 											// end Y 
							1f												// end Z 
								}, 
					new int[] {stageDuration,stageDuration,1},	 			// duration (ms)
					new int[] {waveTime/2 + delay+stageDuration,waveTime/2 + delay+stageDuration,waveTime/2 + delay}				// trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, false}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						null
						}, 	
				new float[] {
						90, 					// end X 
						90, 					// end Y 
						0						// end Z
							}, 
				new float[] {
						180, 					// end X 
						180, 					// end Y 
						0						// end Z
							}, 
				new int[] {stageDuration, stageDuration, 1}, 	// duration (ms)
				new int[] {waveTime/2 + delay+stageDuration, waveTime/2 + delay+stageDuration, waveTime/2 + delay}		// use given delay
			) // end TransformState 
			
			,
			
			// STAGE 4
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, true, true}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							MotionEquation.LINEAR, 
							}, 	
					new float[] {
							0.2f,					 						// start X 
							0.2f, 											// start Y 
							1f												// start Z 
								}, 
					new float[] {
							0f,					 						// end X 
							0f, 											// end Y 
							0f												// end Z 
								}, 
					new int[] {stageDuration,stageDuration, stageDuration},	 		// duration (ms)
					new int[] {waveTime/2 + delay+stageDuration*2,waveTime/2 + delay+stageDuration*2 ,waveTime/2 + delay+stageDuration*2}	// trigger delay
				) // end TransformState 
			
			,
			

			new TransformState(
				TransformType.ROTATE,	 							// Translate
				new Boolean[] {true, true, true}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LINEAR, 			
						MotionEquation.LINEAR,
						MotionEquation.LINEAR, 
						}, 	
				new float[] {
						180, 					// end X 
						180, 					// end Y 
						0						// end Z
							}, 
				new float[] {
						270, 					// end X 
						270, 					// end Y 
						90						// end Z
							}, 
				new int[] {stageDuration, stageDuration, stageDuration}, 	// duration (ms)
				new int[] {waveTime/2 + delay+stageDuration*2, waveTime/2 + delay+stageDuration*2, waveTime/2 + delay+stageDuration*2}		// use given delay
			) // end TransformState 
			
			,
			
			// RESET ROTATION (incase of redestroy?)
			new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {true, true, true}, 					// direction
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LINEAR, 			
							MotionEquation.LINEAR,
							MotionEquation.LINEAR, 
							}, 	
					new float[] {
							0, 					// end X 
							0, 					// end Y 
							0						// end Z
								}, 
					new float[] {
							0, 					// end X 
							0, 					// end Y 
							0						// end Z
								}, 
					new int[] {1, 1, 1}, 	// duration (ms)
					new int[] {waveTime/2 + delay+stageDuration*3, waveTime/2 + delay+stageDuration*3, waveTime/2 + delay+stageDuration*3}		// use given delay
				) // end TransformState 
			
		};
		
		
	}
	 */

	
	
	
	
	
	
	
	
	
	
	// Bonus Blocks
	
	

	
	
	public static TransformState[] queueBonusBombSpinDestroy(  float[][] currentPoints, int duration, int delay )
	{
		return new TransformState[] 
		{
				
		new TransformState(
			TransformType.ROTATE,	 							// Translate
			new Boolean[] {true, true, true}, 					// direction
			TimeDomain.FINITE, 									// Timed Animation
			new MotionEquation[] {
					MotionEquation.LOGISTIC, 			
					MotionEquation.LOGISTIC,
					MotionEquation.LOGISTIC, 
					}, 	
			currentPoints[ROT].clone(),	
			new float[] {
					270, 						// end X 
					180, 						// end Y 
					360						// end Z
						}, 
			new int[] {duration, duration, duration}, 	// duration (ms)
			new int[] {delay, delay, delay}		// use given delay
		) // end TransformState 
		
		,
		
		new TransformState(
				TransformType.SCALE,	 							// Translate
				new Boolean[] {true, true, true}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
						MotionEquation.LOGISTIC, 			
						MotionEquation.LOGISTIC,
						MotionEquation.LOGISTIC, 
						}, 	
				currentPoints[SIZ].clone(),	
				new float[] {
						0, 						// end X 
						0, 						// end Y 
						0						// end Z
							}, 
				new int[] {(int) (duration), (int) (duration), (int) (duration)}, 	// duration (ms)
				new int[] {delay, delay, delay}		// use given delay
			) // end TransformState 
	
		};
	}
	
	public static TransformState[] queueBonusWaveEpicenter(  float[][] currentPoints, int duration, int delay )
	{
		int waveDepth = 5;
		
		return new TransformState[] 
		{
				
		new TransformState(
			TransformType.TRANSLATE,	 							// Translate
			new Boolean[] {false, false, true}, 					// direction
			TimeDomain.FINITE, 									// Timed Animation
			new MotionEquation[] {
				null, 			
				null,
				MotionEquation.LOGISTIC, 
				}, 	
			currentPoints[POS].clone(),	
			new float[] {
				currentPoints[POS][X],
				currentPoints[POS][Y],
				0 + World.mDepthUnit*waveDepth		// default board depth + 5 depth units
						}, 
			new int[] {duration, duration, duration}, 	// duration (ms)
			new int[] {delay, delay, delay}		// use given delay
		) // end TransformState 
		
		,
		
		new TransformState(
				TransformType.TRANSLATE,	 							// Translate
				new Boolean[] {false, false, true}, 					// direction
				TimeDomain.FINITE, 									// Timed Animation
				new MotionEquation[] {
					null, 			
					null,
					MotionEquation.LOGISTIC, 
					}, 	
				new float[] {
					currentPoints[POS][X],
					currentPoints[POS][Y],
					0 + World.mDepthUnit*waveDepth		// default board depth + 5 depth units
							}, 
				currentPoints[POS].clone(),	 
				new int[] {(int) (duration*0.75), (int) (duration*0.75), (int) (duration*0.75)}, 	// duration (ms)
				new int[] {delay + duration, delay + duration, delay + duration}		// use given delay
			) // end TransformState 
		
		};
	}
	
	
	
	public static TransformState[] queueBonusWaveEpicenterDestroy(  float[][] currentPoints, int duration, int delay )
	{
		//Flashing: during delay
		//Stage 1: pickup (over all stages)
		//Stage 2: spin left/up 90 deg, crunch X 
		//Stage 3: spin left/up 90 deg, crunch y
		//Stage 4: crunch all, spin all (disappear)
		
		// AL IN ALL: 6 STAGE LENGTH
		
		int flashDelay = 400;
		int numFlashes = 2;
		int stageDuration = 400;
		
		int waveDepth = 5;
		
		return new TransformState[] 
		{
				
		new TransformState(
			TransformType.TRANSLATE,	 							// Translate
			new Boolean[] {false, false, true}, 					// direction
			TimeDomain.FINITE, 									// Timed Animation
			new MotionEquation[] {
				null, 			
				null,
				MotionEquation.LOGISTIC, 
				}, 	
			currentPoints[POS].clone(),	
			new float[] {
				currentPoints[POS][X],
				currentPoints[POS][Y],
				0 + World.mDepthUnit*waveDepth		// default board depth + 5 depth units
						}, 
			new int[] {duration, duration, duration}, 	// duration (ms)
			new int[] {delay, delay, delay}		// use given delay
		) // end TransformState 
		
		,
			
			// STAGE 1
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {false, false, true}, 			// Y dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC,			
							}, 	
					new float[] {
							currentPoints[POS][X],
							currentPoints[POS][Y],
							0 + World.mDepthUnit*waveDepth		// default board depth + 5 depth units
									}, 
					new float[] {
							currentPoints[POS][X], 							// end X = start X
							currentPoints[POS][Y], 							// end Y = start Y
							//0 + World.mDepthUnit*6		// default board depth + 2 depth units
							0 + World.mDepthUnit*10
							}, 
					new int[] {1, 1, stageDuration*3}, 	// duration (ms)
					new int[] {flashDelay+delay, flashDelay+delay, flashDelay+delay}		// use given delay
				) // end TransformState 
			,
			
			// STAGE 2
			new TransformState(
				TransformType.SCALE, 									// Scale
				new Boolean[] {true, true, true}, 						// X, Y, Z
				TimeDomain.FINITE, 										// Timed Animation
				new MotionEquation[] {
						MotionEquation.SPRING_UNDAMP_ALT_K9_3,
						MotionEquation.SPRING_UNDAMP_ALT_K4_5,
						MotionEquation.SPRING_UNDAMP_ALT_K9_5
						}, 	
				currentPoints[SIZ].clone(),							// start point: current position
				new float[] {
						1.2f,					 						// end X 
						1.2f, 											// end Y 
						1.2f											// end Z 
							}, 
				new int[] {(int) (stageDuration*1.2), (int) (stageDuration*0.8), stageDuration*1}, 	// duration (ms)
				new int[] {flashDelay+delay, flashDelay+delay, flashDelay+delay}		// use given delay
				) // end TransformState 
			
			,
			

			
			// STAGE 3
			new TransformState(
					TransformType.SCALE, 									// Scale
					new Boolean[] {true, true, true}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
					MotionEquation.LOGISTIC, 
					MotionEquation.LOGISTIC, 
					MotionEquation.SPRING_UNDAMP_ALT_K4_5
					}, 	
					new float[] {
							1.2f,					 						// end X 
							1.2f, 											// end Y 
							1.2f											// end Z 
								}, 
					new float[] {
							0,					 						// end X 
							0, 											// end Y 
							0												// end Z 
								}, 
					new int[] {(int) (stageDuration*0.8),(int) (stageDuration*1.2),(int) (stageDuration*1.5)},	 			// duration (ms)
					new int[] {flashDelay+delay+stageDuration,flashDelay+delay+stageDuration,flashDelay+delay+stageDuration}				// trigger delay
				) // end TransformState 
			
		
		
			,
			
			
			
			// STAGE 4
			new TransformState(
					TransformType.ROTATE, 									// Scale
					new Boolean[] {true, true, true}, 						// X, Y, Z
					TimeDomain.FINITE, 										// Timed Animation
					new MotionEquation[] {
							MotionEquation.SPRING_UNDAMP_ALT_K4_5, 
							MotionEquation.LOGISTIC, 
							MotionEquation.SPRING_UNDAMP_ALT_K4_5, 
							}, 	
					new float[] {
							0,					 						// start X 
							0, 											// start Y 
							0												// start Z 
								}, 
					new float[] {
							45f,					 						// end X 
							45f, 											// end Y 
							45f												// end Z 
								}, 
					new int[] {(int) (stageDuration*0.8),(int) (stageDuration*1.2),stageDuration},	 			// duration (ms)
					new int[] {(int) (flashDelay+delay+stageDuration*0.5),flashDelay+delay,flashDelay+delay+stageDuration}				// trigger delay
				) // end TransformState 
			 
			
			
		};
		
	
	}
	
	
	/*
	public static TransformState[] queueBonusWaveEpicenterDestroy(  float[][] currentPoints, float[] endPos, int duration, int delay )
	{
		//Flashing: during delay
		//Stage 1: pickup (over all stages)
		//Stage 2: spin left/up 90 deg, crunch X 
		//Stage 3: spin left/up 90 deg, crunch y
		//Stage 4: crunch all, spin all (disappear)
		
		// AL IN ALL: 6 STAGE LENGTH
		
		int flashDelay = 400;
		int numFlashes = 2;
		int stageDuration = 400;
		
		int waveDepth = 5;
		
		return new TransformState[] 
		{
				
		new TransformState(
			TransformType.TRANSLATE,	 							// Translate
			new Boolean[] {true, true, true}, 					// direction
			TimeDomain.FINITE, 									// Timed Animation
			new MotionEquation[] {
				MotionEquation.LOGISTIC, 			
				MotionEquation.LOGISTIC, 
				MotionEquation.LOGISTIC, 
				}, 	
			currentPoints[POS].clone(),	
			new float[] {
				endPos[X],
				endPos[Y],
				0		// default board depth + 5 depth units
						}, 
			new int[] {duration, duration, duration}, 	// duration (ms)
			new int[] {delay, delay, delay}		// use given delay
		) // end TransformState 
		
		

			
		};
		
	
	}
	*/
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	
	// 3D Text
	
	
	

	public static TransformState[] queueFlipUpSpawn(  int delay, int duration )
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {true, false, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,			
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC,
							},
					//currentPoints[ROT].clone(),
					new float[] {-90,0,0}, 		// start point: current position or end of current running animation
					new float[] {0,0,0}, 		// end point
					new int[] {duration, duration,  duration}, 	// duration (ms)
					new int[] {delay, delay, delay}		// use given delay
				) // end TransformState 
			
		};
			

	}
	
	public static TransformState[] queueFlipOverSpawn( int delay, int duration )
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,			
							MotionEquation.LOGISTIC,
							null,
							},
					//currentPoints[ROT].clone(),
					new float[] {0,-90,0}, 		// start point: current position or end of current running animation
					new float[] {0,0,0}, 		// end point
					new int[] {1, duration,  1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			
		};
			

	}
	

	public static TransformState[] queueSquishRockFlipOverSpawn( int delay, int duration )
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,		
							//MotionEquation.SPRING_UNDAMP_ALT_K4_5,	// bad
							MotionEquation.SPRING_UNDAMP_ALT_K9_3,		// good
							//MotionEquation.SPRING_UNDAMP_ALT_K9_5,	// good
							null,
							},
					//currentPoints[ROT].clone(),
					new float[] {0,-90,0}, 		// start point: current position or end of current running animation
					new float[] {0,0,0}, 		// end point
					new int[] {1, duration,  1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			,
			
			new TransformState(
					TransformType.SCALE, 												// Translate
					new Boolean[] {true, true, true}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.SPRING_UNDAMP_ALT_K9_5, 
							MotionEquation.SPRING_UNDAMP_ALT_K9_5, 
							MotionEquation.SPRING_UNDAMP_ALT_K9_5
					},
					new float[] {0.1f,0.4f,0.6f}, 		// start
					new float[] {1,1,1}, 
					new int[] {(int) (duration*0.9),(int) (duration*0.5),(int) (duration*0.75)}, 									// duration: 400 ms
					new int[] {delay,delay,delay}											// no trigger delay
				) // end TransformState 
		
			
		};
			

	}

	
	


	public static TransformState[] queueSquishRockFlipOverCenterSpawn( float[][] currentPoints, float[] endPoint, int delay, int duration )
	{	
		return new TransformState[] 
		{
			new TransformState(
					TransformType.TRANSLATE, 												// Translate
					new Boolean[] {true, false, false}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,
							null,
							null, 
					},
					currentPoints[POS].clone(),
					new float[] {
						endPoint[X],
						endPoint[Y],
						0
						}, 
					new int[] {duration, 1, 1}, 				
					new int[] {delay, 0, 0}	
				) // end TransformState 
			
				
			,
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,		
							//MotionEquation.SPRING_UNDAMP_ALT_K4_5,	// bad
							MotionEquation.SPRING_UNDAMP_ALT_K9_3,		// good
							//MotionEquation.SPRING_UNDAMP_ALT_K9_5,	// good
							null,
							},
					//currentPoints[ROT].clone(),
					new float[] {0,-90,0}, 		// start point: current position or end of current running animation
					new float[] {0,0,0}, 		// end point
					new int[] {1, duration,  1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			,
			
			new TransformState(
					TransformType.SCALE, 												// Translate
					new Boolean[] {true, true, true}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.SPRING_UNDAMP_ALT_K9_5, 
							MotionEquation.SPRING_UNDAMP_ALT_K9_5, 
							MotionEquation.SPRING_UNDAMP_ALT_K9_5
					},
					new float[] {0.1f,0.4f,0.6f}, 		// start
					new float[] {1,1,1}, 
					new int[] {(int) (duration*0.9),(int) (duration*0.5),(int) (duration*0.75)}, 									// duration: 400 ms
					new int[] {delay,delay,delay}											// no trigger delay
				) // end TransformState 
		
			
		};
			

	}
	
	
	
	public static TransformState[] queueRockFlipOut( float[][] currentPoints, int delay, int duration)
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,
							MotionEquation.LOGISTIC,
							//MotionEquation.SPRING_UNDAMP_ALT_K4_5,	// bad
							//MotionEquation.SPRING_UNDAMP_ALT_K9_3,		// good
							//MotionEquation.SPRING_UNDAMP_ALT_K9_5,	// good
							null,
							}, 	
					currentPoints[ROT].clone(),
					//new float[] {0,0,0}, 		// start point: current position or end of current running animation
					new float[] {0,90,0}, 		// end point
					new int[] {1, duration,  1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			
		};
			

	}
	
	
	public static TransformState[] queueSpinDeform( float[][] currentPoints, int delay, int duration)
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,
							MotionEquation.LOGISTIC,		
							null
							}, 	
					currentPoints[ROT].clone(),
					//new float[] {0,0,0}, 		// start point: current position or end of current running animation
					new float[] {0,360*3,0}, 		// end point
					new int[] {1, duration, 1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			
			,
			
			new TransformState(
					TransformType.SCALE, 												// Translate
					new Boolean[] {true, true, false}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC, 
							null
					},
					currentPoints[SIZ].clone(),
					//new float[] {0.3f,0.5f,0.7f}, 		
					new float[] {1.3f,0.5f,1}, 
					new int[] {duration/2,duration/2,1}, 				
					new int[] {delay,delay,0}	
				) // end TransformState 
			,
			new TransformState(
					TransformType.SCALE, 												// Translate
					new Boolean[] {true, true, false}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC, 
							null
					},
					new float[] {1.3f,0.5f,1}, 
					new float[] {1,1,1}, 
					new int[] {duration/2,duration/2,1}, 				
					new int[] {delay + duration/2,delay + duration/2,0}	
				) // end TransformState 
			,
			
		};
			

	}
	
	
	public static TransformState[] queueTiltFloatup( float[][] currentPoints, float z, int delay, int duration)
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {true, false, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,	
							null,
							null
							}, 	
					currentPoints[ROT].clone(),
					//new float[] {0,0,0}, 		// start point: current position or end of current running animation
					new float[] {-45,0,0}, 		// end point
					new int[] {(int) (duration*0.75), 1, 1}, 	// duration (ms)
					new int[] {delay, 0, 0}		// use given delay
				) // end TransformState 
			
			,
			
			new TransformState(
					TransformType.SCALE, 												// Translate
					new Boolean[] {true, true, false}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,
							MotionEquation.LOGISTIC, 
							null
					},
					currentPoints[SIZ].clone(),
					//new float[] {0.3f,0.5f,0.7f}, 		
					new float[] {0,0,0}, 
					new int[] {duration,duration,1}, 				
					new int[] {delay,delay,0}	
				) // end TransformState 
			
			,
			
			new TransformState(
					TransformType.TRANSLATE, 												// Translate
					new Boolean[] {false, false, true}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC, 
					},
					currentPoints[POS].clone(),
					new float[] {
						currentPoints[POS][X],
						currentPoints[POS][Y],
						z
						}, 
					new int[] {1,1,duration}, 				
					new int[] {0,0,delay}	
				) // end TransformState 
			
			
		};
			

	}
	
	
	
	
	public static TransformState[] queueTiltBubbleup( float[][] currentPoints, float z, int delay, int duration)
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {true, false, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,	
							null,
							null
							}, 	
					currentPoints[ROT].clone(),
					//new float[] {0,0,0}, 		// start point: current position or end of current running animation
					new float[] {-45,0,0}, 		// end point
					new int[] {(int) (duration*0.75), 1, 1}, 	// duration (ms)
					new int[] {delay, 0, 0}		// use given delay
				) // end TransformState 
			
			,
			
				new TransformState(
						TransformType.SCALE,	 							// Translate
						new Boolean[] {true, true, false}, 					// direction
						TimeDomain.CONTINUOUS, 								// Timed Animation
						new MotionEquation[] {
								MotionEquation.SIN, 			
								MotionEquation.SIN,
								null
								}, 	
						currentPoints[SIZ].clone(),							// start point: current position
						new double[] {
								-0.07, 				// rate X 
								0.05, 				// rate Y 
								0				// rate Z
									}, 
						new double[] {
								444, 				// unit X 
								444, 			// unit Y 
								0				// unit Z
									}, 
						new int[] {delay, 0, 0}		// use given delay
					) // end TransformState 
				
		
			
			,
			
			new TransformState(
					TransformType.TRANSLATE, 												// Translate
					new Boolean[] {false, false, true}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							null,
							null,
							MotionEquation.LOGISTIC, 
					},
					currentPoints[POS].clone(),
					new float[] {
						0,
						0,
						z
						}, 
					new int[] {1,1,duration}, 				
					new int[] {0,0,delay}	
				) // end TransformState 
			
			,
			
			new TransformState(
					TransformType.TRANSLATE, 												// Translate
					new Boolean[] {true, true, false}, 									// Z Dir only
					TimeDomain.CONTINUOUS, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.SIN,
							MotionEquation.SIN,
							null, 
					},
					currentPoints[POS].clone(),
					new double[] {
						0.01, 				// rate X 
						0.01, 				// rate Y 
						0				// rate Z
							}, 
					new double[] {
						700, 				// unit X 
						700, 			// unit Y 
						0				// unit Z
							}, 				
					new int[] {delay,delay,0}	
				) // end TransformState 
			
			
		};
			

	}
	
	
	
	
	public static TransformState[] queueGameOverFollowBoardRotation( float[][] currentPoints, int delay, int duration)
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {true, false, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,	
							null,
							null
							}, 	
					currentPoints[ROT].clone(),
					//new float[] {0,0,0}, 		// start point: current position or end of current running animation
					new float[] {90,0,0}, 		// end point
					new int[] {duration,1,1}, 	// duration (ms)
					new int[] {delay, 0, 0}		// use given delay
				) // end TransformState 
			
		};
	}
	
	
	
	public static TransformState[] queueTextWobble( float[][] currentPoints )
	{

		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.ROTATE,	 							// Translate
					new Boolean[] {false, true, false}, 					// direction
					TimeDomain.CONTINUOUS, 								// Timed Animation
					new MotionEquation[] {
							null, 			
							MotionEquation.SIN,
							null
							}, 	
					currentPoints[ROT].clone(),							// start point: current position
					new double[] {
							0, 				// rate X 
							5, 			// rate Y 
							0				// rate Z
								}, 
					new double[] {
							0, 				// unit X 
							1200, 			// unit Y 
							0				// unit Z
								}, 
					new int[] {0, 0, 0}		// use given delay
				) // end TransformState 
			/*
			,
			
			new TransformState(
					TransformType.SCALE,	 							// Translate
					new Boolean[] {true, true, false}, 					// direction
					TimeDomain.CONTINUOUS, 								// Timed Animation
					new MotionEquation[] {
							MotionEquation.SIN, 			
							MotionEquation.SIN,
							null
							}, 	
					currentPoints[SIZ].clone(),							// start point: current position
					new double[] {
							-0.01, 				// rate X 
							0.02, 				// rate Y 
							0				// rate Z
								}, 
					new double[] {
							1200, 				// unit X 
							1200, 			// unit Y 
							0				// unit Z
								}, 
					new int[] {0, 0, 0}		// use given delay
				) // end TransformState 
			*/
		};
			 
	}
	
	public static TransformState[] queueSquishRockFlipIn( float[] startPos, float[] endPos , int delay, int duration )
	{	
		return new TransformState[] 
		{
			
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {true, false, false}, 			// Y dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,			
							//MotionEquation.SPRING_UNDAMP_ALT_K9_3,
							null,
							null,
							}, 	
					new float[] {
							startPos[X], 							// end X = start X
							startPos[Y], 							// end Y = start Y
							0		// default board depth + 2 depth units
							},
					new float[] {
						endPos[X], 							// end X = start X
						endPos[Y], 							// end Y = start Y
						0		// default board depth + 2 depth units
						}, 
					new int[] {duration, 1, 1}, 	// duration (ms)
					new int[] {delay, 1, 1}		// use given delay
				) // end TransformState 
			
			,
			
			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,		
							//MotionEquation.SPRING_UNDAMP_ALT_K4_5,	// bad
							//MotionEquation.SPRING_UNDAMP_ALT_K9_3,		// good
							MotionEquation.LOGISTIC,
							//MotionEquation.SPRING_UNDAMP_ALT_K9_5,	// good
							null,
							},
					//currentPoints[ROT].clone(),
					new float[] {0,-90,0}, 		// start point: current position or end of current running animation
					new float[] {0,0,0}, 		// end point
					new int[] {1, duration,  1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			,
			/*
			new TransformState(
					TransformType.SCALE, 												// Translate
					new Boolean[] {true, true, true}, 									// Z Dir only
					TimeDomain.FINITE, 													// Timed Animation
					new MotionEquation[] {
							MotionEquation.SPRING_UNDAMP_ALT_K9_5, 
							MotionEquation.SPRING_UNDAMP_ALT_K9_5, 
							MotionEquation.SPRING_UNDAMP_ALT_K9_5
					},
					new float[] {0.1f,0.4f,0.6f}, 		// start
					new float[] {1,1,1}, 
					new int[] {(int) (duration*0.9),(int) (duration*0.5),(int) (duration*0.75)}, 									// duration: 400 ms
					new int[] {delay,delay,delay}											// no trigger delay
				) // end TransformState 
			*/
		};
			

	}
	
	
	public static TransformState[] queueSquishRockFlipOut( float[][] currentPoints, float[] endPos , int delay, int duration )
	{	
		return new TransformState[] 
		{

					
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {true, false, false}, 			// Y dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,			
							//MotionEquation.SPRING_UNDAMP_ALT_K9_3,
							null,
							null,
							}, 	
					currentPoints[POS].clone(),						// start point: current position or end of current running animation
					new float[] {
						endPos[X], 							// end X = start X
						endPos[Y], 							// end Y = start Y
						0		
						}, 
					new int[] {duration, 1, 1}, 	// duration (ms)
					new int[] {delay, 1, 1}		// use given delay
				) // end TransformState 
			
			,

			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,		
							//MotionEquation.SPRING_UNDAMP_ALT_K4_5,	// bad
							MotionEquation.LOGISTIC,		// good
							//MotionEquation.SPRING_UNDAMP_ALT_K9_5,	// good
							null,
							},
					//currentPoints[ROT].clone(),
					new float[] {0,0,0}, 		// start point: current position or end of current running animation
					new float[] {0,-90,0}, 		// end point
					new int[] {1, duration,  1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			
		};
			

	}
	
	public static TransformState[] queueSquishRockFlipThrew( float[][] currentPoints, float[] endPos , int delay, int duration )
	{	
		return new TransformState[] 
		{

					
			new TransformState(
					TransformType.TRANSLATE, 						// Translate
					new Boolean[] {true, false, false}, 			// Y dir
					TimeDomain.FINITE, 								// Timed Animation
					new MotionEquation[] {
							MotionEquation.LOGISTIC,			
							//MotionEquation.SPRING_UNDAMP_ALT_K9_3,
							null,
							null,
							}, 	
					currentPoints[POS].clone(),						// start point: current position or end of current running animation
					new float[] {
						endPos[X], 							// end X = start X
						endPos[Y], 							// end Y = start Y
						0		
						}, 
					new int[] {duration, 1, 1}, 	// duration (ms)
					new int[] {delay, 1, 1}		// use given delay
				) // end TransformState 
			
			,

			new TransformState(
					TransformType.ROTATE, 							// Translate
					new Boolean[] {false, true, false}, 				// X axis
					TimeDomain.FINITE, 									// Timed Animation
					new MotionEquation[] {
							null,		
							//MotionEquation.SPRING_UNDAMP_ALT_K4_5,	// bad
							MotionEquation.LOGISTIC,		// good
							//MotionEquation.SPRING_UNDAMP_ALT_K9_5,	// good
							null,
							},
					//currentPoints[ROT].clone(),
					new float[] {0,0,0}, 		// start point: current position or end of current running animation
					new float[] {0,90,0}, 		// end point
					new int[] {1, duration,  1}, 	// duration (ms)
					new int[] {0, delay, 0}		// use given delay
				) // end TransformState 
			
		};
			

	}
	
	
} public class Block extends GLShape
{
	// General
	private static final String TAG = "Block";
	private static final Boolean debug = false;
	private final MainActivity game;	
	
	public Block(Context context)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, World.BLOCKTYPES.REGULAR, null, 0, 0, isVisible, isInteractable);
	}

	
	public Block(Context context, Color color, int row, int col, Boolean visible, Boolean interact)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, World.BLOCKTYPES.REGULAR, color, row, col, visible, interact);

	}
	
	public Block(Context context, World.BLOCKTYPES type, Color color, int row, int col, Boolean visible, Boolean interact)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, type, color, row, col, visible, interact);

	}
	
	public Block(Context context, Color color, int row, int col)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, World.BLOCKTYPES.REGULAR, color, row, col, isVisible, isInteractable);

	}
	
	public Block(Context context, Color color)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, World.BLOCKTYPES.REGULAR, color, 0, 0, true, false);
	}
	
	public Block(Context context, Color color, Boolean visible, Boolean interact)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, World.BLOCKTYPES.REGULAR, color, 0, 0, visible, interact);
	}
	
	private void init(Context context, World.BLOCKTYPES type, Color color, int row, int col, Boolean visible, Boolean interact)
	{
		isVisible = visible;
		isInteractable = interact;
		
		mAnimation = new AnimationBroker(context, row, col);
		if (color != null)
			mColor = new ColorBroker(color);
		else
			mColor = new ColorBroker();
		
		mBlockType = type;
		
	}
	
	/*
	@SuppressWarnings("unused")
	private void DEBUG(String logString)
	{
		if (debug == true) Log.d(TAG, logString);
	}
	
	@SuppressWarnings("unused")
	private void ERROR(String logString)
	{
		if (debug == true) Log.e(TAG, logString);
	}
	*/
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// Thread Pool Here?
	
	public void update(long now, Boolean primaryThread, Boolean secondaryThread)
	{
		// Process color
		if (!mColor.isEmpty())
			mColor.processColorElements(now);
		
		/*
		float[] temp;
		DecimalFormat f = new DecimalFormat("###.00"); 
		temp = mColor.mCurrentColorAmbient;
		game.text  = String.format("AMBIENT:   R: %6s  G: %6s  B: %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]) );
		temp = mColor.mCurrentColorDiffuse;
		game.text += String.format("DIFFUSE:   R: %6s  G: %6s  B: %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]) );
		game.textviewHandler.post( game.updateTextView );
		*/

		
		// Process position & motion  (if there is an animation)
		if ( mAnimation.mAvailableIndexes.size() != AnimationBroker.QUEUESIZE )
		{
			mAnimation.processAnimationElements(now);
			
			// if there is an animation queued, and it is not visible, make visible after first update iteration
			//if (!isVisible)
			//	isVisible = true;
		}

		/*
		float[] temp;
		DecimalFormat f = new DecimalFormat("###.00"); 
		temp = mAnimation.mCurrentPoints[0];
		game.text  = String.format("POS: %6s   %6s   %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]) );
		temp = mAnimation.mCurrentPoints[1];
		game.text  += String.format("ROT: %6s   %6s   %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]));
		temp = mAnimation.mCurrentPoints[2];
		game.text  += String.format("SCL: %6s   %6s   %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]));
		game.textviewHandler.post( game.updateTextView );
		*/
		
		// check for death
		if (mAnimation.mTimeOfDeath != -1 && !isDead)
		{
			// dying...
			if (isInteractable)
				isInteractable = false;
			
			if (mAnimation.mTimeOfDeath < System.currentTimeMillis())
			{
				isVisible = false;
				isDead = true;
			}
		
		}
		
	}
	

	//////////////////////////////////////////////////////////////////////////////////////////////////
	
	private final float[][] nullFloatArray = new float[][]{new float[]{0,0,0},new float[]{0,0,0},new float[]{0,0,0}};
	
	public void draw(GL10 gl)
	{
		draw(gl, nullFloatArray );
	}
	
	public void draw(GL10 gl, float[][] offset)
	{
		if (isVisible)
		{
			// Translate
			gl.glTranslatef( 
					mAnimation.mCurrentPoints[AnimationBroker.POS][AnimationBroker.X] + offset[AnimationBroker.POS][AnimationBroker.X], 
					mAnimation.mCurrentPoints[AnimationBroker.POS][AnimationBroker.Y] + offset[AnimationBroker.POS][AnimationBroker.Y], 
					mAnimation.mCurrentPoints[AnimationBroker.POS][AnimationBroker.Z] + offset[AnimationBroker.POS][AnimationBroker.Z] 
							);
			
			
			// Rotate
			gl.glRotatef( mAnimation.mCurrentPoints[AnimationBroker.ROT][AnimationBroker.X] + offset[AnimationBroker.ROT][AnimationBroker.X], 1, 0, 0);
			gl.glRotatef( mAnimation.mCurrentPoints[AnimationBroker.ROT][AnimationBroker.Y] + offset[AnimationBroker.ROT][AnimationBroker.Y], 0, 1, 0);
			gl.glRotatef( mAnimation.mCurrentPoints[AnimationBroker.ROT][AnimationBroker.Z] + offset[AnimationBroker.ROT][AnimationBroker.Z], 0, 0, 1);
			
			// Scale
			gl.glScalef( 
					mAnimation.mCurrentPoints[AnimationBroker.SIZ][AnimationBroker.X] + offset[AnimationBroker.SIZ][AnimationBroker.X], 
					mAnimation.mCurrentPoints[AnimationBroker.SIZ][AnimationBroker.Y] + offset[AnimationBroker.SIZ][AnimationBroker.Y], 
					mAnimation.mCurrentPoints[AnimationBroker.SIZ][AnimationBroker.Z] + offset[AnimationBroker.SIZ][AnimationBroker.Z]
							);
			
			// color
		
			
			gl.glEnable(GL10.GL_DEPTH_TEST);
			
			if (isSeeThru)
			{
				// transparent!
				
				gl.glEnable(GL10.GL_BLEND);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);	
			}
			else
			{
				gl.glDisable(GL10.GL_BLEND);
			}
			
			// Set color/lighting
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, mColor.mCurrentColorAmbient, 0);
			gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, mColor.mCurrentColorDiffuse, 0);
			
			
			if (mBlockType == World.BLOCKTYPES.REGULAR)
			{
				// eventually
				//game.world.mBlock.draw(gl, mBlockValue.ordinal());
				
				if(mBlockValue == BlockValue.NORMAL)
					game.getWorld().mBlock.draw(gl, null );
				else
					game.getWorld().mBlock.draw(gl, mBlockValue.ordinal() );
				
			}
			else if (mBlockType == World.BLOCKTYPES.GROUPWRAPPER)
			{
				game.getWorld().mWrapperBlock.draw(gl, null);
			}
			
		}
	}
	
} public enum Color {
	//Color  	MaterialAmbience			   			Material Diffusion							Enabled?
	NONE		(new float[] { 0, 0, 0, 0 }	,  			new float[] { 0, 0, 0, 0 }, 				false	), // NONE must be first!

	
	// Alli 5
	
	TAN (new float[] {0.82353f, 0.78824f, 0.6f, 1f}, new float[] {1, 1, 1, 1}, true),
	GREEN (new float[] {0.23137f, 0.45882f, 0.11765f, 1f}, new float[] {1, 1, 1, 1}, true),
	BLUE (new float[] {0.05098f, 0.3451f, 0.65098f, 1f}, new float[] {1, 1, 1, 1}, true),
	YELLOW (new float[] {1f, 0.6f, 0f, 1f}, new float[] {1, 1, 1, 1}, true),
	RED (new float[] {0.68235f, 0f, 0f, 1f}, new float[] {1, 1, 1, 1}, true),
	PLUM (new float[] {0.27843f, 0.0902f, 0.12549f, 1f}, new float[] {1, 1, 1, 1}, true),
	
	// Original picks
	/*
	RED			(new float[] { 1f, 0, 0, 1 }, 			new float[] { 1, 1, 1, 1 }, 			true	), 
	GREEN		(new float[] { 0, 1f, 0, 1 }, 			new float[] { 1, 1, 1, 1 }, 				true	),  
	BLUE		(new float[] { 0, 0, 1f, 1 }, 			new float[] { 1, 1, 1, 1 }, 				true	), 
	ORANGE      (new float[] { 0.8f, 0.5f, 0f, 1 }, 	new float[] { 1, 1, 1, 1 }, 				true	),
	PURPLE	 	(new float[] { 0.78f, 0.08f, 0.52f, 1 }, new float[] { 1, 1, 1, 1 }, 			false	),
	DARKGREY	(new float[] { 0.2f, 0.2f, 0.2f, 1 }, 	new float[] { 1, 1, 1, 1 }, 				true	), 
	*/
	
	// Logo
	
	LOGO_TAN 	(new float[] {0.91373f, 0.88235f, 0.72549f, 1f},	new float[] {1, 1, 1, 1},	false),
	
	LOGO_BLUE 	(new float[] {0.40784f, 0.61176f, 0.82745f, 1f},	new float[] {1, 1, 1, 1},	false),
	LOGO_GREEN 	(new float[] {0.52941f, 0.72941f, 0.43137f, 1f},	new float[] {1, 1, 1, 1},	false),
	LOGO_RED	(new float[] {0.84314f, 0.38039f, 0.38039f, 1f},	new float[] {1, 1, 1, 1},	false),
	LOGO_YELLOW	(new float[] {1f, 0.78039f, 0.45098f, 1f},			new float[] {1, 1, 1, 1},	false),
	
	// Continue!
	
	WHITE		(new float[] { 1f, 1f, 1f, 1 }, 		new float[] { 1, 1, 1, 1 }, 				false	), 

	RED_LIGHT	(new float[] { 0.65f, 0.16f, 0.16f, 1 }, new float[] { 1, 1, 1, 1 }, 				false	), 
	
	PITCHBLACK	(new float[] { 0f, 0f, 0f, 1 }, 		new float[] { 0, 0, 0, 1 }, 				false	),
	BLACK		(new float[] { 0f, 0f, 0f, 1 }, 		new float[] { 1, 1, 1, 1 }, 				false	), 
	BLACKGREY	(new float[] { 0.05f, 0.05f, 0.05f, 1 }, new float[] { 1, 1, 1, 1 }, 				false	), 

	DARKGREY	(new float[] { 0.2f, 0.2f, 0.2f, 1 }, 	new float[] { 1, 1, 1, 1 }, 				false	), 
	GREY		(new float[] { 0.4f, 0.4f, 0.4f, 1 }, 	new float[] { 1, 1, 1, 1 }, 				false	),
 
	GHOSTWHITE	(new float[] { 1f, 1f, 1f, 0.5f }, 		new float[] { 0.5f, 0.5f, 0.5f, 0.5f }, 	false	), 
	GHOSTBLACK	(new float[] { 0f, 0f, 0f, 0.5f }, 		new float[] { 0.8f, 0.8f, 0.8f, 0.8f }, 	false	), 
	GHOSTDARKBLACK	(new float[] { 0f, 0f, 0f, 0.8f }, 	new float[] { 0.8f, 0.8f, 0.8f, 0.8f }, 	false	), 
	
	TRANSPARENT	(new float[] { 0f, 0f, 0f, 0f }, 		new float[] { 0f, 0f, 0f, 0f }, 	false	), 
	
	// Backgrounds
	BACKGROUND_DEFAULT		(Color.BLACK.ambient(),							Color.BLACK.diffuse(),			false	), // DEFAULT must be last!
	BACKGROUND_RED			(new float[] {0.05f, 0.0f, 0.0f, 1f }, 			new float[] { 1, 1, 1, 1 }, 	false	), 
	BACKGROUND_LIGHT_RED	(new float[] {0.2f, 0.0f, 0.0f, 1f }, 			new float[] { 1, 1, 1, 1 }, 	false	), 
	
	// Default Color
	DEFAULT		(Color.GREY.ambient(),					Color.GREY.diffuse(),						false	), // DEFAULT must be last!
	
	; 
	
	private static final Random randObjPool = new Random( System.currentTimeMillis() );
	
	//private static final String TAG = "Color";
	private final float[] ambient;
	private final float[] diffuse;
	public final boolean isUsable;
	
	Color(float[] matAmbient, float[] matDiffuse, boolean enabled) 
	{
		
		//Log.d(TAG,"Constructing Color...");
		this.ambient = matAmbient;
		this.diffuse = matDiffuse;
		this.isUsable = enabled;
	}
	
	public float[] ambient() { return ambient; }
	public float[] diffuse() { return diffuse; }

	
	private static Color getRandomColor() 
	{
		//Log.d(TAG,"gettingRandomNumberInRange...");
		int minPick = 1; // Don't Include "NONE" color
		int maxPick = Color.values().length - 2; // Don't include "DEFAULT" color
		return Color.values()[minPick + randObjPool.nextInt(maxPick) ];
	}

	public static Color pickColorExcept(Color exceptedColor) {
		//Log.d(TAG,"PickingColor(Except)...");
		
		Color pick = getRandomColor();
		
		//keep picking!
		while (pick.isUsable == false || pick == exceptedColor)
		{
			pick = getRandomColor();
		}
		
		//Log.d(TAG,"   Except="+String.valueOf(exceptedColor)+ "   Pick=" +String.valueOf(pick) );
		return pick;
	}

	public static Color pickColor() {
		//Log.d(TAG,"pickingColor...");

		Color pick = getRandomColor();
		
		//keep picking!
		while (pick.isUsable == false)
		{
			pick = getRandomColor();
		}
		
		//Log.d(TAG,"   Pick=" +String.valueOf(pick) );
		return pick;
	}
	
	
	
	public static Color pickLogoColor() {
		//Log.d(TAG,"pickingColor...");

		int minPick = LOGO_BLUE.ordinal();
		int maxPick = LOGO_YELLOW.ordinal(); 
		return Color.values()[minPick + randObjPool.nextInt(maxPick - minPick + 1) ];

	}
	
	public static Color getNextLogoColor(Color c) {
		//Log.d(TAG,"pickingColor...");

		if (c.ordinal() >= LOGO_YELLOW.ordinal())
			return LOGO_BLUE;
		else
			return Color.values()[ c.ordinal() + 1 ];

	}
	
	
} public class ColorState
{
	public Boolean				isTransforming	= false;	// true if this state is being acted upon
	public Boolean				isCompleted		= false;	// set to true upon completion (called in stop)
	public long					mStartTime		= -1;
	
	public final MotionEquation[]	mEquation;		// Which equation of motion to use while transforming...
	public final Color				mStartColor;
	public final float[]			mStartColorAmbient;
	public final float[]			mStartColorDiffuse;
	public final Color				mEndColor;
	public final float[]			mEndColorAmbient;
	public final float[]			mEndColorDiffuse;
	public final int[]				mDuration;		// in milliseconds; default to 1 second
	public final int[]				mTriggerDelay;	// milliseconds to wait after start() is called before starting the transition; [R, G, B]
	
	public ColorState(MotionEquation[] equation, Color start, Color end, int[] dur, int[] triggerDelay)
	{
		mEquation = equation;
		
		mStartColor = start;
		mStartColorAmbient = start.ambient().clone();
		mStartColorDiffuse = start.diffuse().clone();
		
		mEndColor = end;
		mEndColorAmbient = end.ambient().clone();
		mEndColorDiffuse = end.diffuse().clone();
		
		mDuration = dur;
		mTriggerDelay = triggerDelay;
	}
	
	// Specialised, used only by ColorBroker
	
	public void start(long now)
	{
		isTransforming = true;
		mStartTime = now;
	}
	
	public void stop()
	{
		isTransforming = false;
		isCompleted = true;
	}
	
	/**
	 * Finite Transform: returns the time t between 0 and mDuration, discontinuing the transform upon one period
 	 * The RGB values are delayed respective to the triggerDelay
 	 *  
	 * @param now
	 * @param componenet   R=0, G=1, B=2, A=3
	 * @return
	 */
	public long getRunningTime(long now, int component)
	{
		
		if (isTransforming == true)
			if ( 
					( now - ( mStartTime + mTriggerDelay[0] )) >= mDuration[0] && 
					( now - ( mStartTime + mTriggerDelay[1] )) >= mDuration[1] && 
					( now - ( mStartTime + mTriggerDelay[2] )) >= mDuration[2] &&
					( now - ( mStartTime + mTriggerDelay[2] )) >= mDuration[3] 
				)
					stop();
		
		// Return between 0 and duration
		// delay validated in animation broker
		return Math.min( Math.max( now - ( mStartTime + mTriggerDelay[component] ) , 0 )  , mDuration[component] );
	}
	
} public class Coord<Type> extends Object{
	private final static int ROW = 0;
	private final static int COL = 1;
	private final static int X = 0;
	private final static int Y = 1;
	private final static int Z = 2;
	
    private ArrayList<Type> coord= new ArrayList<Type>();

    public Coord(Type rx, Type cy) {
        super();
        coord.add(ROW, rx);
        coord.add(COL, cy);
    }

    public Coord(Type x, Type y, Type z) {
        super();
        coord.add(X, x);
        coord.add(Y, y);
        coord.add(Z, z);
    }
    
    public ArrayList<Type> getRawCoord(){ return coord; }
    public Type getRow()	{ return coord.get(ROW); 	}
    public Type getCol()	{ return coord.get(COL);	}
    public Type getX()		{ return coord.get(X); 		}
    public Type getY()		{ return coord.get(Y); 		}
    public Type getZ()		{ return coord.get(Z); 		}
    
    public void setRow(Type row)	{ coord.set(ROW, row); 	}
    public void setCol(Type col)	{ coord.set(COL, col);	}
    public void setX(Type x)		{ coord.set(X, x); 		}
    public void setY(Type y)		{ coord.set(Y, y); 		}
    public void setZ(Type z)		{ coord.set(Z, z); 		}
    
    /*
    public float[] getRowCol()	{ return new float[] {(Float) coord.get(ROW), (Float) coord.get(COL)}; }
    public float[] getXYZ()		{ return new float[] {(Float) coord.get(X), (Float) coord.get(Y), (Float) coord.get(Z)}; }
    */
    
    @Override
    public boolean equals(Object other) 
    {
    	if (other instanceof Coord) 
        { 
        	return getRawCoord().equals(((Coord<?>) other).getRawCoord()); 
        }

        return false;
    }
    
    @Override
    public int hashCode()
    {
	    return getRawCoord().hashCode();
    }
    
    public String toString()
    { 
    	if (coord.size() == 2)
    		return "(" + coord.get(ROW) + ", " + coord.get(COL) + ")"; 
    	
    	return "[" + coord.get(X) + ", " + coord.get(Y)+ ", " + coord.get(Z) + "]";
    }

    public Coord<Type> clone()
    {
    	if (coord.size() == 2)
    		return new Coord<Type>(getRow(), getCol());
    	
    	return new Coord<Type>(getX(), getY(), getZ());
    }
    
    @SuppressWarnings("unchecked")
	public Type[] toArray()
    {
    	return (Type[]) coord.toArray();
    }
    
} public enum DropSectionState
{
	// these are set int DropSection.java
	FOLDED   (1),
	PEEK     (1),
	PEEK_UNFOLDEDSCORE (1),
	UNFOLDED (1),
	FULL     (1);
	
	private double heightPercentage;

	DropSectionState(double height)
	{
		heightPercentage = height;
	}
	
	public double getHeight()
	{
		return heightPercentage;
	}
	
	public void setHeight(double height)
	{
		heightPercentage = height;
	}
} public class FontManager 
{
	private final Context mContext; 
	private final MainActivity game;
	private HashSet<String>	mFontNames = new HashSet<String>();
	private ConcurrentHashMap<String, TexFont>  mTexFont = new ConcurrentHashMap<String, TexFont>();
	private ConcurrentHashMap<String, Integer>  mFontId  = new ConcurrentHashMap<String, Integer>();
	private ConcurrentHashMap<String, Float>  mFontHorizontalCount  = new ConcurrentHashMap<String, Float>();
	
	public static final String SCORE_FONT = "BlueHighwayRg40.bff";//"Fortheloveofhate.bff";
	
	//public static final String MENUITEM_FONT = "operatinginstructions60.bff";
	public static final String MENUMAJORITEM_FONT = "telegrafico30.bff";
	public static final String MENUMINORITEM_FONT = "BlueHighwayRg40.bff";
	public static final String ARROW_FONT = "Eyecicles40.bff";
	
	/*
	public static final float[] BANNER_TITLE_COLOR = Color.WHITE.ambient().clone();
	public static final float[] BANNER_VALUE_COLOR = Color.TAN.ambient().clone();
	public static final float[] BANNER_SCORE_COLOR = Color.WHITE.ambient().clone();
	*/
	public static final Color BANNER_TITLE_COLOR = Color.TAN;
	public static final Color BANNER_VALUE_COLOR = Color.WHITE;
	public static final Color BANNER_SCORE_COLOR = Color.WHITE;
	public static final Color ERROR_COLOR = Color.RED_LIGHT;
	
	public FontManager(Context context)
	{
		mContext = context;
		game = (MainActivity) context;
	
		// font name & # of "1"s that fits on the screen horizontally
		addFont("telegrafico30.bff", 48f );			// menu major
		addFont("BlueHighwayRg40.bff", 58f );		// menu minor / score
		//addFont("Fortheloveofhate.bff", 28.236f);	// score?
		addFont("Eyecicles40.bff", 20f);			// arrows
	}
	
	public void loadFonts(GL10 gl)
	{
		// Generate GL texture ID
		int texId[] = new int[mFontNames.size()];
		gl.glGenTextures(texId.length, texId, 0);
		
		int idx=0;
		for (String font : mFontNames)
			mFontId.put(font, texId[idx++]);
		
		for (Entry<String, Integer> fontEntry : mFontId.entrySet())
		{
			//Log.d("LOADFONT", fontEntry.getKey());
			
			// Add new texFont
			mTexFont.put(fontEntry.getKey(), new TexFont(mContext, gl, mFontId.get(fontEntry.getKey())));
			
			// Load font
			try 
			{
				mTexFont.get(fontEntry.getKey()).LoadFont(fontEntry.getKey(), gl, mFontHorizontalCount.get(fontEntry.getKey()));
				//Log.d("LOADED", fontEntry.getKey());
			} 
			catch (IOException e) 
			{
				// TODO Auto-generated catch block
				//Log.e("FontManager.loadFonts()", e.toString());
			}
		}
		
	}
	
	
	public void addFont(String fontAssetName, float horizontalCount)
	{
		mFontNames.add(fontAssetName);
		mFontHorizontalCount.put(fontAssetName, horizontalCount);
	}
	
	public TexFont getFont(String fontAssetName)
	{
		return mTexFont.get(fontAssetName);
	}
	
	public float getFontHeight(String fontAssetName)
	{
		tempFont = getFont(fontAssetName);
		return tempFont.fntCellHeight*tempFont.charScale;	
	}

	
	public int getStringWidth(String fontAssetName, String text)
	{
		return mTexFont.get(fontAssetName).getStringWidth(text);	
	}
	
	public int[] getCharWidthArray(String fontAssetName)
	{
		tempFont = getFont(fontAssetName);
		int[] ret = tempFont.charWidth.clone();
		if (tempFont != null)
		{
			// scale font for usage
			for (int idx=0; idx < tempFont.charWidth.length; idx++)
			{
				ret[idx] = (int) (tempFont.charWidth[idx] * tempFont.charScale);
			}
			return ret;
				
		}
			
		
		return null;
	}
	
	public int getFirstCharOffset(String fontAssetName)
	{
		tempFont = getFont(fontAssetName);
		if (tempFont != null)
			return tempFont.firstCharOffset;
		
		return -1;
	}
	
	private TexFont tempFont;
	

	
	
	public void printAt(String fontAssetName, GL10 gl, String text, float x, float y, float[] rgba, Boolean leftJust, float fontScaleOffset)
	{
		tempFont = getFont(fontAssetName);
		if (tempFont != null)
			tempFont.printAt(gl, text, x, y, rgba, leftJust, fontScaleOffset);
	}

	
	public void printOffsetAt(String fontAssetName, GL10 gl, String text, float x, float y, float[] rgba, float[] peakrgba, Boolean leftJust, float[] charOffset, int pixXOffset, int pixYOffset, float[] charWidthMod, float[] charHeightMod, float fontScaleOffset)
	{
		tempFont = getFont(fontAssetName);
		if (tempFont != null)
			tempFont.printOffsetAt(gl, text, x, y, rgba, peakrgba, leftJust, charOffset, pixXOffset, pixYOffset, charWidthMod, charHeightMod, fontScaleOffset);
	}

	public void print3D(String fontAssetName, GL10 gl, String text,float[] rgba, float xScale, float yScale)
	{
		tempFont = getFont(fontAssetName);
		if (tempFont != null)
			tempFont.print3D(gl, text, rgba, xScale, yScale);
	}
	
	/*
	public void printDropFadeInAt(String fontAssetName, GL10 gl, String text, float x, float y, float[] rgba, Boolean leftJust, float percentOfPhrase, int pixOffset, int wordWidth)
	{
		tempFont = getFont(fontAssetName);
		if (tempFont != null)
			tempFont.printLogisticOffsetAt(gl, text, x, y, rgba, World.mMenuBackdropColor.ambient(), leftJust, percentOfPhrase, 0, pixOffset,  wordWidth, true);
	}
	
	public void printSlideRightFadeInAt(String fontAssetName, GL10 gl, String text, float x, float y, float[] rgba, Boolean leftJust, float percentOfPhrase, int pixOffset, int wordWidth)
	{
		tempFont = getFont(fontAssetName);
		if (tempFont != null)
			tempFont.printLogisticOffsetAt(gl, text, x, y, rgba, World.mMenuBackdropColor.ambient(), leftJust, percentOfPhrase, -pixOffset, 0,  wordWidth, true);
	}
	*/
	
} public class GL3DText extends GLShape
{
	// General
	private static final String TAG = "Block";
	private static final Boolean debug = false;
	private final MainActivity game;
	
	
	public String mFontName = "";
	public String mLabel = "";
	private Boolean mLeftJust = true;
	private Boolean isDimensionsSet = false;
	private Double mAugmentHeight = 1.5;
	private float mFontScaleOffset = 1f;
	
	
	public GL3DText(Context context)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context,  null, 0, 0, isVisible, isInteractable, mFontScaleOffset);
	}
	
	public GL3DText(Context context,String fontName, String labelText, Boolean leftJust, Color color, int row, int col, float scale, Boolean visible, Boolean interact)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, color, row, col, visible, interact, scale);
		setLabel(fontName, labelText, leftJust);
	}
	
	public GL3DText(Context context,String fontName, String labelText, Boolean leftJust, Color color, int row, int col, float scale)
	{
		// get the game object from context
		game = (MainActivity) (context);
		
		// set block attributes
		init(context, color, row, col, isVisible, isInteractable, scale);
		setLabel(fontName, labelText, leftJust);
	}
	
	/*
	public GL3DText(Context context,String fontName, String labelText, Boolean leftJust, Color color)
	{
		// get the game object from context
		game = (StackAttack) (context);
		
		// set block attributes
		init(context, color, 0, 0, true, false);
		setLabel(fontName, labelText, leftJust);
	}
	
	public GL3DText(Context context,String fontName, String labelText, Boolean leftJust, Color color, Boolean visible, Boolean interact)
	{
		// get the game object from context
		game = (StackAttack) (context);
		
		// set block attributes
		init(context,color, 0, 0, visible, interact);
		setLabel(fontName, labelText, leftJust);
	}
	*/
	private void init(Context context, Color color, int row, int col, Boolean visible, Boolean interact, float scale)
	{
		isVisible = visible;
		isInteractable = interact;
		mFontScaleOffset = scale;
		
		//mAnimation = new AnimationBroker(context, row, col, true, true); // move & compensate for y offset upon addition
		mAnimation = new AnimationBroker(context, row, col, true);
		if (color != null)
			mColor = new ColorBroker(color);
		else
			mColor = new ColorBroker();
		
		
	}
	
	public void setLabel(String fontName, String labelText, Boolean leftJust, Double augHeight, float fontScaleOffset)
	{
		mFontName = fontName;
		mLabel = labelText;
		mLeftJust = leftJust;
		mAugmentHeight = augHeight;
		mFontScaleOffset = fontScaleOffset;	
	}
	
	public void setLabel(String fontName, String labelText, Boolean leftJust)
	{
		mFontName = fontName;
		mLabel = labelText;
		mLeftJust = leftJust;
	}
	
	public void setLabel(String labelText)
	{
		mLabel = labelText;	
	}
	
	/*
	@SuppressWarnings("unused")
	private void DEBUG(String logString)
	{
		if (debug == true) Log.d(TAG, logString);
	}
	
	@SuppressWarnings("unused")
	private void ERROR(String logString)
	{
		if (debug == true) Log.e(TAG, logString);
	}
	*/
	
	//////////////////////////////////////////////////////////////////////////////////////////////////
	// Thread Pool Here?
	
	public void update(long now, Boolean primaryThread, Boolean secondaryThread)
	{
		// Process color
		if (!mColor.isEmpty())
			mColor.processColorElements(now);
		
		/*
		float[] temp;
		DecimalFormat f = new DecimalFormat("###.00"); 
		temp = mColor.mCurrentColorAmbient;
		game.text  = String.format("AMBIENT:   R: %6s  G: %6s  B: %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]) );
		temp = mColor.mCurrentColorDiffuse;
		game.text += String.format("DIFFUSE:   R: %6s  G: %6s  B: %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]) );
		game.textviewHandler.post( game.updateTextView );
		*/

		
		// Process position & motion  (if there is an animation)
		if ( mAnimation.mAvailableIndexes.size() != AnimationBroker.QUEUESIZE )
		{
			mAnimation.processAnimationElements(now);
			
			// if there is an animation queued, and it is not visible, make visible after first update iteration
			if (!isVisible)
				isVisible = true;
		}

		/*
		float[] temp;
		DecimalFormat f = new DecimalFormat("###.00"); 
		temp = mAnimation.mCurrentPoints[0];
		game.text  = String.format("POS: %6s   %6s   %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]) );
		temp = mAnimation.mCurrentPoints[1];
		game.text  += String.format("ROT: %6s   %6s   %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]));
		temp = mAnimation.mCurrentPoints[2];
		game.text  += String.format("SCL: %6s   %6s   %6s\n", f.format(temp[0]), f.format(temp[1]), f.format(temp[2]));
		game.textviewHandler.post( game.updateTextView );
		*/
		
		// check for death
		if (mAnimation.mTimeOfDeath != -1 && !isDead)
		{
			// dying...
			if (isInteractable)
				isInteractable = false;
			
			if (mAnimation.mTimeOfDeath < System.currentTimeMillis())
			{
				isVisible = false;
				isDead = true;
			}
		
		}
		
	}
	

	//////////////////////////////////////////////////////////////////////////////////////////////////
	
	// do not use
	public void draw(GL10 gl, float[][] offset) {}
	
	public void draw(GL10 gl)
	{
		if (isVisible)
		{
			// Translate
			gl.glTranslatef( 
					mAnimation.mCurrentPoints[AnimationBroker.POS][AnimationBroker.X], 
					mAnimation.mCurrentPoints[AnimationBroker.POS][AnimationBroker.Y], 
					mAnimation.mCurrentPoints[AnimationBroker.POS][AnimationBroker.Z] 
							);
			
			// Rotate
			gl.glRotatef( mAnimation.mCurrentPoints[AnimationBroker.ROT][AnimationBroker.X] , 1, 0, 0);
			gl.glRotatef( mAnimation.mCurrentPoints[AnimationBroker.ROT][AnimationBroker.Y] , 0, 1, 0);
			gl.glRotatef( mAnimation.mCurrentPoints[AnimationBroker.ROT][AnimationBroker.Z] , 0, 0, 1);
			
			// Scale
			gl.glScalef( 
					mAnimation.mCurrentPoints[AnimationBroker.SIZ][AnimationBroker.X], 
					mAnimation.mCurrentPoints[AnimationBroker.SIZ][AnimationBroker.Y], 
					mAnimation.mCurrentPoints[AnimationBroker.SIZ][AnimationBroker.Z] 
							);
			
			// color
		
			
			/*
			if (isSeeThru)
			{
				// transparent!
				
				gl.glEnable(GL10.GL_BLEND);
				gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE);
				
			}
			else
			{
				
				gl.glDisable(GL10.GL_BLEND);
			}
			*/
			// Set color/lighting
			//gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_AMBIENT, mColor.mCurrentColorAmbient, 0);
			//gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, mColor.mCurrentColorDiffuse, 0);
			
			
			// DRAW FONT STRING
			game.getWorld().mDropSection.mFonts.print3D(mFontName, gl, mLabel, mColor.mCurrentColorAmbient, 0.003f*mFontScaleOffset, 0.003f*mFontScaleOffset);
		}
	}
	
} abstract class GLShape extends Identity
{
	
	// Specific Delegate Members 
	public AnimationBroker	mAnimation;
	public ColorBroker 		mColor;
	//public GLRect			mShape;	// not here! high memory cost


	// ...Tangibility
	public Boolean	isInteractable = true;	// User cannot manipulate it
	public Boolean	isFrozen = false;		// User and settle cannot manipulate it
	public Boolean	isVisible = true; 		// not visible until first animation: false
	public Boolean 	isSeeThru = false;
	
	// used to trigger deletion / settling
	public Boolean isDead = false;
	public Boolean isMatching = false;
	
	// Block dep items
	protected World.BLOCKTYPES mBlockType;
	public BlockValue mBlockValue = BlockValue.NORMAL;
	
	public GLShape(/*Context context*/)
	{

	}

	//abstract public void update(long now);
	abstract public void update(long now, Boolean primaryThread, Boolean secondaryThread);
	abstract public void draw(GL10 gl, float[][] offset);
	abstract public void draw(GL10 gl);

} public enum MotionEquation
{
	
	LINEAR,
	SIN,
	LOGISTIC,
	SPRING_UNDAMP_P4,
	SPRING_UNDAMP_P6,
	SPRING_UNDAMP_ALT_K9_5,
	SPRING_UNDAMP_ALT_K9_3,
	SPRING_UNDAMP_ALT_K4_5,
	LOGISTIC_OVERSHOOT_SLOW,
	LOGISTIC_OVERSHOOT_FAST,
	SIN_OVERSHOOT_SLOW,
	SIN_OVERSHOOT_FAST
	;
	
	private static final float PI = (float) Math.PI;
	
	MotionEquation()
	{

	}

	
	public static double applyFinite(TransformType transform, MotionEquation eq, long t, double duration, double startPoint, double endPoint)
	{
		
		
		switch (eq)
		{

		case LINEAR: 
			/*
				#LINEAR
				duration = 500
				start = 25
				end = 55
				fn = (t/duration)
				
				print float(fn(t=duration)) #= ~1
				print float(fn(t=0) ) #= ~0
				
				p = fn*(end-start)
				p.plot((t,0,duration))
			*/
			
			// Scale / Color / Rotate / Transform
			return startPoint + (t/(double)duration)*(endPoint-startPoint);

		
		case SIN: 
			/*	
				#SIN
				duration = 500
				start = 25
				end = 55
				A = 1/2
				w1 = ((2*pi)/(2*duration))
				fn = A*(sin(w1*(t-(duration/2.0))) + 1)
				
				print float(fn(t=duration)) #= ~1
				print float(fn(t=0) ) #= ~0
				
				p = fn*(end-start)
				plot(p, (t,0, duration))
			*/
						
			// Scale / Color / Rotate / Translate
			return startPoint + (0.5*(Math.sin( ((2.0*PI)/(2.0*duration))*(t - (duration/2.0))) + 1 ))*(endPoint - startPoint);

	

			
		case LOGISTIC: 
			/*
			#LOGISTIC
			duration = 500
			start = 25
			end = 55
			k = 2
			po = 1
			r = (1/(duration/8.0))
			fn = ((((k*po*e^(r*(t - (duration/2))))/(k + po*(e^(r*(t - (duration/2)))-1))) - po  + (k/2))*(1/2))*1.035 - 0.018
			
			print float(fn(t=duration)) #= ~1
			print float(fn(t=0) ) #= ~0
			
			p = fn*(end-start)
			p.plot((t,0,duration))
			*/
			
			return startPoint + (endPoint - startPoint)*
					(((((2*Math.exp((1/(duration/8.0))*(t - (duration/2.0)))) / (2 + (Math.exp((1/(duration/8.0))*(t - (duration/2.0)))-1))))*(0.5))*1.035 - 0.018);


		case SPRING_UNDAMP_P4: 
			/*	
			#Spring Undamp 4
			duration = 500
			start = 25
			end = 55
			wd = 10.584055/duration
			s = .4
			wn = wd/sqrt(1-s^2)
			fn = (wn^2)*(1/(wn^2))*( 1 - ((e^(-s*wn*t))/(sqrt(1-(s^2))))*cos(wd*t + 0.4115192) )
			
			print float(fn(t=duration)) #= ~1
			print float(fn(t=0) ) #= ~0
			
			p = fn*(end-start)
			plot(y, (t,0, duration))
			*/
			
			return startPoint +  (endPoint - startPoint)*( (Math.pow((10.584055/duration)*(1 / Math.sqrt( 1-(0.4*0.4) )), 2))*(1/(Math.pow((10.584055/duration)*(1 / Math.sqrt( 1-(0.4*0.4) )), 2)))*( 1 - ((Math.exp(-0.4*(10.584055/duration)*(1 / Math.sqrt( 1-(0.4*0.4) ))*t)/(1 / Math.sqrt( 1-(0.4*0.4) ))) *Math.cos((10.584055/duration)*t + 0.4115192) ) ) );

		case SPRING_UNDAMP_P6: 
			/*	
				#Spring Undamp 6
				
				duration = 500
				start = 25
				end = 55
				wd = 10.584055/duration
				s = .4
				wn = wd/sqrt(1-s^2)
				fn = (wn^2)*(1/(wn^2))*( 1 - ((e^(-s*wn*t))/(sqrt(1-(s^2))))*cos(wd*t + 0.4115192) )
				
				print float(fn(t=duration)) #= ~1
				print float(fn(t=0) ) #= ~0
				
				p = fn*(end-start)
				plot(y, (t,0, duration))
			*/
			return startPoint + (endPoint - startPoint)*( (Math.pow((10.0/duration)*(1 / Math.sqrt( 1-(0.6*0.6) )),2))*(1/(Math.pow((10.0/duration)*(1 / Math.sqrt( 1-(0.6*0.6) )), 2)))*( 1 - ((Math.exp(-0.6*(10.0/duration)*(1 / Math.sqrt( 1-(0.6*0.6) ))*t)/(1 / Math.sqrt( 1-(0.6*0.6) ))) *Math.cos((10.0/duration)*t + 0.6505) ) ) );
			
		
			
			
		// High overshoot, long ring period
		case SPRING_UNDAMP_ALT_K9_5:
			
			/*
				# Spring Undamp Alt
				dur = 400
				t = var('t')
				k = 9
				m = 1
				wo = sqrt(k/m)
				T = 1/(wo)
				u = wo*sqrt(1-(T^2))
				
				startDeg = 172
				endDeg   = 270
				
				
				x = cos(u*t*(5/dur))*e^(-T*wo*(5/dur)*t)
				pos = x*(startDeg-endDeg) + endDeg
				
				
				print float(x(t=224)), float(pos(t=224))
				print float(x(t=dur))
				plot(pos, (t,0,dur))
			*/
			return endPoint + (startPoint - endPoint)*(Math.cos((3*Math.sqrt(1-Math.pow(1.0/3.0, 2)))*t*(5.0/duration))*Math.exp((-1)*(5.0/duration)*t));

			
			
			
		// High overshoot, short ring period
		case SPRING_UNDAMP_ALT_K9_3:
			
			/*
				# Spring Undamp Alt
				dur = 400
				t = var('t')
				k = 9
				m = 1
				wo = sqrt(k/m)
				T = 1/(wo)
				u = wo*sqrt(1-(T^2))
				
				startDeg = 172
				endDeg   = 270
				
				
				x = cos(u*t*(5/dur))*e^(-T*wo*(5/dur)*t)
				pos = x*(startDeg-endDeg) + endDeg
				
				
				print float(x(t=224)), float(pos(t=224))
				print float(x(t=dur))
				plot(pos, (t,0,dur))
			*/
			return endPoint + (startPoint - endPoint)*(Math.cos((3*Math.sqrt(1-Math.pow(1.0/3.0, 2)))*t*(2.975/duration))*Math.exp((-1)*(2.975/duration)*t));
		
			
			
			
		// Low overshoot, short ring period
		case SPRING_UNDAMP_ALT_K4_5:
			
			/*
				# Spring Undamp Alt
				dur = 400
				t = var('t')
				k = 4
				m = 1
				wo = sqrt(k/m)
				T = 1/(wo)
				u = wo*sqrt(1-(T^2))
				
				startDeg = 172
				endDeg   = 270
				
				
				x = cos(u*t*(5/dur))*e^(-T*wo*(5/dur)*t)
				pos = x*(startDeg-endDeg) + endDeg
				
				
				print float(x(t=224)), float(pos(t=224))
				print float(x(t=dur))
				plot(pos, (t,0,dur))
			*/
			return endPoint + (startPoint - endPoint)*(Math.cos((2*Math.sqrt(1-Math.pow(1.0/2.0, 2)))*t*(5.0/duration))*Math.exp((-1)*(5.0/duration)*t));
			
			
		case LOGISTIC_OVERSHOOT_SLOW: 
			/*	
			# Logistic Overshoot
			duration = 500
			start = 25
			end = 55
			
			# Logistic
			k = 2
			po = 1
			r = 1/(duration/8)
			lfn = ((((k*po*e^(r*(t - (duration/2))))/(k + po*(e^(r*(t - (duration/2)))-1))) - po  + (k/2))*(1/2))*1.035 - 0.018
			
			# Delta Function
			a = 1
			sizeNum = 10
			size = sizeNum/duration
			offset = 0.8*sizeNum
			bump = ((1/(a*sqrt(pi)))*e^(-( offset - (size*t)  )^2/a^2))*0.25 + 1
			
			plot( [bump*lfn*(end-start) , end-start] , (t,0, duration))
			*/
			
			
			
			// Scale / Color / Rotate / Translate
			return 
					startPoint +
					(
						// LOGISTIC
							(((((2*Math.exp((1/(duration/8.0))*(t - (duration/2.0)))) / (2 + (Math.exp((1/(duration/8.0))*(t - (duration/2.0)))-1))))*(0.5))*1.035 - 0.018) * 
						
						// Delta
						(((1.0/(float)(FloatMath.sqrt(PI)))*Math.exp(-Math.pow( 0.75*7.0 - (7.0/(float)duration*t), 2)))*0.5 + 1) *
						
						// Min-Max
						(endPoint - startPoint)
					)
					;
			
		case SIN_OVERSHOOT_SLOW: 
			/*	
			# Sin Overshoot
			duration = 500
			start = 25
			end = 55
			
			# Logistic
			A = 1/2
			w1 = ((2*pi)/(2*duration))
			fn = A*(sin(w1*(t-(duration/2.0))) + 1)
			
			# Delta Function
			a = 1
			duration = 1000
			sizeNum = 10
			finalDur = 500
			size = sizeNum/finalDur
			offset = 0.8*size*duration
			bump = ((1/(a*sqrt(pi)))*e^(-( offset - (size*t)  )^2/a^2))*0.25 + 1
			
			plot( [bump*fn*(end-start) , end-start] , (t,0, duration))
			*/
			
			// Scale / Color / Rotate / Translate
			return 
					startPoint +
					(
						// SIN
						(0.5*(Math.sin( ((2.0*PI)/(2.0*duration))*(t - (duration/2.0))) + 1 )) *
						
						// Delta
						(((1.0/(float)(FloatMath.sqrt(PI)))*Math.exp(-Math.pow( 0.75*7.0 - (7.0/(float)duration*t), 2)))*0.5 + 1) *
						
						// Min-Max
						(endPoint - startPoint)
					)
					;
			
		case SIN_OVERSHOOT_FAST:
			/*
			# Sin Overshoot
			duration = 500
			start = 25
			end = 55
			
			
			#Slope Delta
			a = 1
			sizeNum = 2
			size = sizeNum/duration
			offset = 0.2*size*duration
			bump = ((1/(a*sqrt(pi)))*e^(-( offset - (size*t)  )^2/a^2))*2 + 1
			
			# Sin
			A = 1/2
			w1 = ((2*pi)/(2*duration))
			fn = A*(sin(w1*(t-(duration/2.0))) + 1)
			
			# End Delta
			end_a = 1
			end_sizeNum = 7
			end_size = end_sizeNum/duration
			end_offset = 0.75*end_sizeNum
			end_bump = ((1/(end_a*sqrt(pi)))*e^(-( end_offset - (end_size*t)  )^2/end_a^2))*0.5 + 1
			
			
			plot([fn, fn*bump*end_bump, bump, end_bump], (t,0,duration)) 
			*/
			// Transform
			/*
			if (transform == TransformType.TRANSLATE)
				return 
						// Slope Delta
						(((1.0/(float)(FloatMath.sqrt(PI)))*Math.exp(-Math.pow( 0.2*2.0 - (2.0/(float)duration*t), 2)))*2 + 1) *
							
						// SIN
						(0.5*(Math.sin( ((2.0*PI)/(2.0*duration))*(t - (duration/2.0))) + 1 )) *
						
						// End Delta
						(((1.0/(float)(FloatMath.sqrt(PI)))*Math.exp(-Math.pow( 0.75*7.0 - (7.0/(float)duration*t), 2)))*0.5 + 1) *
						
						// Min-Max
						(endPoint - startPoint);
			*/
			
			// Scale / Color / Rotate / Translate
			return 
					startPoint +
					(
						// Slope Delta
						(((1.0/(float)(FloatMath.sqrt(PI)))*Math.exp(-Math.pow( 0.35*3.0 - (3.0/(float)duration*t), 2)))*0.75 + 1) *
							
						// SIN
						(0.5*(Math.sin( ((2.0*PI)/(2.0*duration))*(t - (duration/2.0))) + 1 )) *
						
						// End Delta
						(((1.0/(float)(FloatMath.sqrt(PI)))*Math.exp(-Math.pow( 0.75*7.0 - (7.0/(float)duration*t), 2)))*0.5 + 1) *
						
						// Min-Max
						(endPoint - startPoint)
					)
					;
			
		}
		
		// default case
		return -1;
	}
	
	
	
	
	
	public static double applyContinuous(TransformType transform, MotionEquation eq, long t, double rate, double unit, double startPoint)
	{
		
		
		switch (eq)
		{

		case LINEAR: 
			/*
			#LINEAR
			duration = 500
			start = 25
			end = 55
			fn = (t/duration)
			
			print float(fn(t=duration)) #= ~1
			print float(fn(t=0) ) #= ~0
			
			p = fn*(end-start)
			p.plot((t,0,duration))
			*/
			
			// Scale / Color / Rotate
			return startPoint + (t*(rate/unit));

		case SIN: 
			/*	
			#SIN_OSC
			
			rate = 60
			start = 0
			period = 1000
			w1 = ((2*pi)/(period))
			fn = A*(sin(w1*t) )
			
			print float(fn(t=period)) #= ~1
			print float(fn(t=0) ) #= ~0
			
			p = start + fn
			plot(p, (t,0, period))
			*/
			
			//return startPoint + (rate*Math.sin( ((2.0*PI)/unit) *(t - ((unit)/2.0))) );
			return startPoint + (rate*Math.sin( ((2.0*PI)/unit)*t));
			
		case LOGISTIC: 
			/*
			#LOGISTIC
			duration = 500
			start = 25
			end = 55
			k = 2
			po = 1
			r = 1/(duration/8)
			fn = ((((k*po*e^(r*(t - (duration/2))))/(k + po*(e^(r*(t - (duration/2)))-1))) - po  + (k/2))*(1/2))*1.035 - 0.018
			
			print float(fn(t=duration)) #= ~1
			print float(fn(t=0) ) #= ~0
			
			p = fn*(end-start)
			p.plot((t,0,duration))
			*/
			return 0;

		case SPRING_UNDAMP_P4: 
			/*	
			#Spring Undamp 4
			duration = 500
			start = 25
			end = 55
			wd = 10.584055/duration
			s = .4
			wn = wd/sqrt(1-s^2)
			fn = (wn^2)*(1/(wn^2))*( 1 - ((e^(-s*wn*t))/(sqrt(1-(s^2))))*cos(wd*t + 0.4115192) )
			
			print float(fn(t=duration)) #= ~1
			print float(fn(t=0) ) #= ~0
			
			p = fn*(end-start)
			plot(y, (t,0, duration))
			*/
			return 0;

		case SPRING_UNDAMP_P6: 
			/*	
			#Spring Undamp 6
			duration = 500
			start = 25
			end = 55
			wd = 10.584055/duration
			s = .4
			wn = wd/sqrt(1-s^2)
			fn = (wn^2)*(1/(wn^2))*( 1 - ((e^(-s*wn*t))/(sqrt(1-(s^2))))*cos(wd*t + 0.4115192) )
			
			print float(fn(t=duration)) #= ~1
			print float(fn(t=0) ) #= ~0
			
			p = fn*(end-start)
			plot(y, (t,0, duration))
			*/
			return 0;
			
		}
		
		// default case
		return -1;
	}
	
	
	
} public class PxCropSwipeLeft implements PxBase
{
	private final Context mContext;
	private final MainActivity game;
	
	private PxImage mArrow;
	private PxImage mCircle;
	
	PxCropSwipeLeft(Context context, float circleX, float circleY, float arrowHeadX, float arrowHeadY, Boolean lockXMove, Boolean lockYMove, Boolean interactable)
	{
		mContext = context;
		game = (MainActivity) mContext;
		
		int circleWidth = 128, circleHeight = 128;
		float circleAbsScale = (float) ( (1f/circleWidth)*(game.getWorld().mScreenBlockLength) )*1.7f;
		
		int arrowWidth = 709, arrowHeight = 74;
		float arrowAbsScale = (1f/arrowWidth)*game.getWorld().mScreenWidth;
		
		mCircle = new PxImage(mContext,  
				R.drawable.fingercircle_w128h128, circleWidth, circleHeight, 
				circleAbsScale, 
				(-circleWidth/2f), (-circleHeight/2f),
				circleX, circleY,
				0,0,
				lockXMove, lockYMove, 
				interactable,		
				true,		// scale on interaction
				false		// crop on interaction
				);
		
		mArrow = new PxImage(mContext,  
				R.drawable.dotarrowleft_w700h74, arrowWidth, arrowHeight, 
				arrowAbsScale,
				0, (-arrowHeight/2f),
				arrowHeadX, arrowHeadY,
				circleX, circleY,
				lockXMove, lockYMove,
				interactable,		
				false,		// scale on interaction
				true		// crop on interaction
				);
		
	}
	
	@Override
	public void loadImage(GL10 gl) {
		mArrow.loadImage(gl);
		mCircle.loadImage(gl);
	}

	public void setFingerPos(Coord<Integer> coord)
	{
		float	x = (coord.getCol() + 0.5f)*game.getWorld().mScreenBlockLength,
				y = game.getWorld().mScreenHeight - (coord.getRow() + 0.5f)*game.getWorld().mScreenBlockLength;
		
		// move circle
		if (mCircle.mLockXDir == false)
		{
			if (mCircle.mXDriver.isTransforming)
				mCircle.mXDriver.stop();
			mCircle.mXDriver.mCurrent = x;
		}
		
		if (mCircle.mLockYDir == false)
		{
			if (mCircle.mYDriver.isTransforming)
				mCircle.mYDriver.stop();
			mCircle.mYDriver.mCurrent = y;
		}
		
		// move arrow
		mArrow.interact(x, y, 0, 1);
		
	}
	
	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount) {
		return pickup(x, y, pixYOffset, digitCount, false);
	}

	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount, Boolean force) {
		if(mCircle.pickup(x, y, pixYOffset, digitCount, force))
		{
			mArrow.pickup(x, y, pixYOffset, digitCount, force);
			return true;
		}
		return false;
	}

	@Override
	public Boolean interact(float x, float y, int pixYOffset, int digitCount) {
		if(mCircle.interact(x, y, pixYOffset, digitCount))
		{
			mArrow.interact(x, y, pixYOffset, digitCount);
			return true;
		}
		return false;
	}

	@Override
	public void drop(int digitCount) {
		mArrow.drop(digitCount);
		mCircle.drop(digitCount);
	}

	@Override
	public void drop(int digitCount, Boolean toGridResolution) {
		mArrow.drop(digitCount, toGridResolution);
		mCircle.drop(digitCount, toGridResolution);
	}

	@Override
	public void fadeIn(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mArrow.fadeIn(eq, dur, delay, full);
		mCircle.fadeIn(eq, dur, delay, full);
	}

	@Override
	public void fadeOut(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mArrow.fadeOut(eq, dur, delay, full);
		mCircle.fadeOut(eq, dur, delay, full);
	}

	@Override
	public void moveTo(Float pXend, Float pYend, MotionEquation eq, Integer dur, Integer delay) {
		mArrow.moveTo(pXend, pYend, eq, dur, delay);
		mCircle.moveTo(pXend, pYend, eq, dur, delay);
	}

	@Override
	public void update(long now, Boolean primaryThread, Boolean secondaryThread) {
		mArrow.update(now, primaryThread, secondaryThread);
		mCircle.update(now, primaryThread, secondaryThread);
	}

	@Override
	public void draw(GL10 gl, float pixYOffset) {
		mArrow.draw(gl, pixYOffset);
		mCircle.draw(gl, pixYOffset);
	}

} public class PxDoubleStaticSwipeSideways implements PxBase
{
	private final Context mContext;
	private final MainActivity game;
	
	private PxImage mRightCircle;
	private PxImage mLeftCircle;
	
	PxDoubleStaticSwipeSideways(Context context)
	{
		mContext = context;
		game = (MainActivity) mContext;
	
		
		int circleWidth = 249, circleHeight = 128;
		float circleAbsScale = (float) ( (1f/circleHeight)*(game.getWorld().mScreenBlockLength) )*2f;
		float centerOffset = circleHeight*circleAbsScale*0.75f;
		mLeftCircle = new PxImage(mContext,  
				R.drawable.fingercirclesideways_w249h128, circleWidth, circleHeight, 
				circleAbsScale, 
				(-circleWidth/2f), (-circleHeight/2f),
				(game.getWorld().mScreenWidth/2), (game.getWorld().mScreenHeight/2) - centerOffset,
				0,0,
				false, true, 
				true,		
				true,		// scale on interaction
				false,		// crop on interaction
				true		// sticky interaction
				);
		
		mRightCircle = new PxImage(mContext,  
				R.drawable.fingercirclesideways_w249h128, circleWidth, circleHeight, 
				circleAbsScale, 
				(-circleWidth/2f), (-circleHeight/2f),
				(game.getWorld().mScreenWidth/2), (game.getWorld().mScreenHeight/2) + centerOffset,
				0,0,
				false, true,  
				true,		
				true,		// scale on interaction
				false,		// crop on interaction
				true		// sticky interaction
				);
		
	}
	
	@Override
	public void loadImage(GL10 gl) {
		mRightCircle.loadImage(gl);
		mLeftCircle.loadImage(gl);
	}

	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount) {
		return pickup(x, y, pixYOffset, digitCount, false);
	}

	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount, Boolean force) {
		if (digitCount == 2)
		{
			if(mLeftCircle.pickup(x, y, pixYOffset, digitCount, force))
			{
				mRightCircle.pickup(x, y, pixYOffset, digitCount, true);
				return true;
			}
			else if(mRightCircle.pickup(x, y, pixYOffset, digitCount, force))
			{
				mLeftCircle.pickup(x, y, pixYOffset, digitCount, true);
				return true;
			}
		}
		return false;
	}

	@Override
	public Boolean interact(float x, float y, int pixYOffset, int digitCount) {
		//game.text = "\n\n\n\n\n\n\n";
		
		if (digitCount == 2)
		{
			
			if(mLeftCircle.interact(x, y, pixYOffset, digitCount))
			{
				mRightCircle.interact(x, y, pixYOffset, digitCount);
				//game.textviewHandler.post( game.updateTextView );
				return true;
			}
			else if(mRightCircle.interact(x, y, pixYOffset, digitCount))
			{
				mLeftCircle.interact(x, y, pixYOffset, digitCount);
				//game.textviewHandler.post( game.updateTextView );
				return true;
			}
		}
		return false;
	}

	@Override
	public void drop(int digitCount) {
		mRightCircle.drop(digitCount, false);
		mLeftCircle.drop(digitCount, false);
	}

	@Override
	public void drop(int digitCount, Boolean toGridResolution) {
		// ignore resolution
		mRightCircle.drop(digitCount, false);
		mLeftCircle.drop(digitCount, false);
	}

	@Override
	public void fadeIn(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mRightCircle.fadeIn(eq, dur, delay, full);
		mLeftCircle.fadeIn(eq, dur, delay, full);
	}

	@Override
	public void fadeOut(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mRightCircle.fadeOut(eq, dur, delay, full);
		mLeftCircle.fadeOut(eq, dur, delay, full);
	}

	@Override
	public void moveTo(Float pXend, Float pYend, MotionEquation eq, Integer dur, Integer delay) {
		mRightCircle.moveTo(pXend, pYend, eq, dur, delay);
		mLeftCircle.moveTo(pXend, pYend, eq, dur, delay);
	}

	@Override
	public void update(long now, Boolean primaryThread, Boolean secondaryThread) {
		mRightCircle.update(now, primaryThread, secondaryThread);
		mLeftCircle.update(now, primaryThread, secondaryThread);
	}

	@Override
	public void draw(GL10 gl, float pixYOffset) {
		mRightCircle.draw(gl, pixYOffset);
		mLeftCircle.draw(gl, pixYOffset);
	}

} public class PxDoubleStaticSwipeUp implements PxBase
{
	private final Context mContext;
	private final MainActivity game;
	
	private PxImage mRightCircle;
	private PxImage mLeftCircle;
	
	PxDoubleStaticSwipeUp(Context context, float circleY)
	{
		mContext = context;
		game = (MainActivity) mContext;
	
		
		int circleWidth = 128, circleHeight = 191;
		float circleAbsScale = (float) ( (1f/circleWidth)*(game.getWorld().mScreenBlockLength) )*2f;
		float centerOffset = circleWidth*circleAbsScale*0.75f;
		mLeftCircle = new PxImage(mContext,  
				R.drawable.fingercircleup_w128h191, circleWidth, circleHeight, 
				circleAbsScale, 
				(-circleWidth/2f), (-circleWidth/2f),
				(game.getWorld().mScreenWidth/2) - centerOffset, circleY,
				0,0,
				true, false, 
				true,		
				true,		// scale on interaction
				false,		// crop on interaction
				true		// sticky interaction
				);
		
		mRightCircle = new PxImage(mContext,  
				R.drawable.fingercircleup_w128h191, circleWidth, circleHeight, 
				circleAbsScale, 
				(-circleWidth/2f), (-circleWidth/2f),
				(game.getWorld().mScreenWidth/2) + centerOffset, circleY,
				0,0,
				true, false,
				true,		
				true,		// scale on interaction
				false,		// crop on interaction
				true		// sticky interaction
				);
		
	}
	
	@Override
	public void loadImage(GL10 gl) {
		mRightCircle.loadImage(gl);
		mLeftCircle.loadImage(gl);
	}

	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount) {
		return pickup(x, y, pixYOffset, digitCount, false);
	}

	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount, Boolean force) {
		if (digitCount == 2)
		{
			if(mLeftCircle.pickup(x, y, pixYOffset, digitCount, force))
			{
				mRightCircle.pickup(x, y, pixYOffset, digitCount, true);
				return true;
			}
			else if(mRightCircle.pickup(x, y, pixYOffset, digitCount, force))
			{
				mLeftCircle.pickup(x, y, pixYOffset, digitCount, true);
				return true;
			}
		}
		return false;
	}

	@Override
	public Boolean interact(float x, float y, int pixYOffset, int digitCount) {
		//game.text = "\n\n\n\n\n\n\n";
		
		if (digitCount == 2)
		{
			
			y = Math.max(y, game.getWorld().mScreenHeight*0.4f);
			
			if(mLeftCircle.interact(x, y, pixYOffset, digitCount))
			{
				mRightCircle.interact(x, y, pixYOffset, digitCount);
				//game.textviewHandler.post( game.updateTextView );
				return true;
			}
			else if(mRightCircle.interact(x, y, pixYOffset, digitCount))
			{
				mLeftCircle.interact(x, y, pixYOffset, digitCount);
				//game.textviewHandler.post( game.updateTextView );
				return true;
			}
		}
		return false;
	}

	@Override
	public void drop(int digitCount) {
		mRightCircle.drop(digitCount, false);
		mLeftCircle.drop(digitCount, false);
	}

	@Override
	public void drop(int digitCount, Boolean toGridResolution) {
		// ignore resolution
		mRightCircle.drop(digitCount, false);
		mLeftCircle.drop(digitCount, false);
	}

	@Override
	public void fadeIn(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mRightCircle.fadeIn(eq, dur, delay, full);
		mLeftCircle.fadeIn(eq, dur, delay, full);
	}

	@Override
	public void fadeOut(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mRightCircle.fadeOut(eq, dur, delay, full);
		mLeftCircle.fadeOut(eq, dur, delay, full);
	}

	@Override
	public void moveTo(Float pXend, Float pYend, MotionEquation eq, Integer dur, Integer delay) {
		mRightCircle.moveTo(pXend, pYend, eq, dur, delay);
		mLeftCircle.moveTo(pXend, pYend, eq, dur, delay);
	}

	@Override
	public void update(long now, Boolean primaryThread, Boolean secondaryThread) {
		mRightCircle.update(now, primaryThread, secondaryThread);
		mLeftCircle.update(now, primaryThread, secondaryThread);
	}

	@Override
	public void draw(GL10 gl, float pixYOffset) {
		mRightCircle.draw(gl, pixYOffset);
		mLeftCircle.draw(gl, pixYOffset);
	}

} public class PxImage implements PxBase
{
	private final Context mContext;
	private final MainActivity game;
	
	private Boolean isLoaded = false;
	
	private Boolean	mInteractable = false;
	private Boolean mScaleOnInteraction = false;
	private Boolean mCropOnInteraction = false;
	private Boolean isPickedUp = false;
	
	private Long mPickupStartTime = -1l;
	
	public Boolean mLockXDir = false,
					mLockYDir = false;
	
	private Boolean mStickyInteraction = false;	// always drop in original coords
	private Coord<Float> mStickyPixCoord;
	
	public float absx=0, absy=0;
	
	public float 	mLeftPos=0, 
					mRightPos=0, 
					mTopPos=0, 
					mBottomPos=0,
					mXRelOffset,
					mYRelOffset,
					mXOffset,
					mYOffset,
					mXScaledOffset,
					mYScaledOffset,
					mAbsoluteScale,
					mWidth,
					mHeight,
					mAbsScaledWidth,
					mAbsScaledHeight,
					mCropWidth,
					mCropHeight,
					mScaledHeight, 
					mScaledWidth,
					mXCrop,
					mYCrop;
	
	protected Boolean mLeftJust = true;
	
	private int texID;
	private int [] UVarray = new int[4];
	
	public Driver mXDriver, mYDriver, mAlphaDriver, mScaleDriver;
	
	private Integer mImageResId;
	
	private Bitmap mBitmap;
	
	public PxImage(Context context, int resId, int width, int height, float scaleRatio, float xRelOff, float yRelOff, float initX, float initY, float xCrop, float yCrop, Boolean lockXMove, Boolean lockYMove, Boolean interactable, Boolean scaleOnInteraction, Boolean cropOnInteraction)
	{
		mContext = context;
		game = (MainActivity) mContext;
		
		init(resId, width, height, scaleRatio, xRelOff, yRelOff, initX, initY, xCrop, yCrop, lockXMove, lockYMove, interactable, scaleOnInteraction, cropOnInteraction, false);
	}
	
	public PxImage(Context context, int resId, int width, int height, float scaleRatio, float xRelOff, float yRelOff, float initX, float initY, float xCrop, float yCrop, Boolean lockXMove, Boolean lockYMove, Boolean interactable, Boolean scaleOnInteraction, Boolean cropOnInteraction, Boolean stickyInteraction)
	{
		mContext = context;
		game = (MainActivity) mContext;
		
		init(resId, width, height, scaleRatio, xRelOff, yRelOff, initX, initY, xCrop, yCrop, lockXMove, lockYMove, interactable, scaleOnInteraction, cropOnInteraction, stickyInteraction);
	}
	
	private void init(int resId, int width, int height, float scaleRatio, float xRelOff, float yRelOff, float initX, float initY, float xCrop, float yCrop, Boolean lockXMove, Boolean lockYMove, Boolean interactable, Boolean scaleOnInteraction, Boolean cropOnInteraction, Boolean stickyInteraction)
	{
		mImageResId = resId;
		
		mInteractable = interactable;
		mScaleOnInteraction = scaleOnInteraction;
		mCropOnInteraction = cropOnInteraction;
		
		mLockXDir = lockXMove;
		mLockYDir = lockYMove;
		
		mXCrop = (int) xCrop;
		mYCrop = (int) yCrop;
		
		// new Driver, init position
		mXDriver = new Driver(MotionEquation.LOGISTIC, 500, initX);
		mYDriver = new Driver(MotionEquation.LOGISTIC, 500, initY);
		mAlphaDriver = new Driver(MotionEquation.LOGISTIC, 500, 0f);
		mScaleDriver = new Driver(MotionEquation.LOGISTIC, 500, 1f);
		
		// select a bitmap based on dpi
		mBitmap = BitmapFactory.decodeResource(mContext.getResources(), mImageResId);
		mWidth = width;
		mHeight = height;
		mCropWidth = width;
		mCropHeight = height;

		
		// get crop & scale dim
		mAbsoluteScale = scaleRatio;
		mXRelOffset = xRelOff*mAbsoluteScale;
		mYRelOffset = yRelOff*mAbsoluteScale;
		mAbsScaledWidth = (int) (mWidth*mAbsoluteScale);
		mAbsScaledHeight = (int) (mHeight*mAbsoluteScale);
		mScaledWidth = (int) (mAbsScaledWidth*mScaleDriver.mCurrent);
		mScaledHeight = (int) (mAbsScaledHeight*mScaleDriver.mCurrent);
	
		mXOffset = mXRelOffset;
		mYOffset = mYRelOffset;
		
		mXScaledOffset = mXOffset*mScaleDriver.mCurrent;
		mYScaledOffset = mYOffset*mScaleDriver.mCurrent;
		
		mStickyInteraction = stickyInteraction;
		if (mStickyInteraction)
		{
			mStickyPixCoord = new Coord<Float>(initX, initY);
		}
	}

	@Override
	public void loadImage(GL10 gl)
	{
		
		int texid[] = new int[1];
		gl.glGenTextures(texid.length, texid, 0);
		texID = texid[0];
		
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texID);
	    
	    // Set texture parameters
	    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
	    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
	    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
	    gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
		
	    GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
	    
	    mBitmap.recycle();
		
	    isLoaded = true;
	}

	
	public Boolean isWithinClickableArea(float xPix, float yPix, int pixYOffset)
	{
		//game.text += "   " + ((game.getWorld().mScreenHeight - yPix)) + " <= " + (mTopPos /*+ (game.getWorld().mScreenHeight - pixYOffset)*/) + "      " + ((game.getWorld().mScreenHeight - yPix)) + " >= " + (mBottomPos /*+ (game.getWorld().mScreenHeight - pixYOffset)*/) + "\n" ;
		if (
				xPix >= mLeftPos && 
				xPix <= mRightPos &&
				(game.getWorld().mScreenHeight - yPix) <= mTopPos /*+ (game.getWorld().mScreenHeight - pixYOffset)*/ &&
				(game.getWorld().mScreenHeight - yPix) >= mBottomPos /*+ (game.getWorld().mScreenHeight - pixYOffset) */
			)
			return true;
			
		return false;
	}
	
	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount)
	{
		return pickup(x, y, pixYOffset, digitCount, false);
	}
	
	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount, Boolean force)
	{
		if (mInteractable)
		{

			if (!mCropOnInteraction)
			{
				// move
				if (!isPickedUp && ( isWithinClickableArea(x, y, pixYOffset)  ||  force )) 
				{
					//game.text = "      Pickup indeed!" + "\n" + game.text;
					//game.textviewHandler.post(game.updateTextView);
					
					if (mLockXDir == false)
					{
						if (mXDriver.isTransforming)
							mXDriver.stop();
						mXDriver.mCurrent = x;
						isPickedUp = true;
					}
					if (mLockYDir == false)
					{
						if (mYDriver.isTransforming)
							mYDriver.stop();
						mYDriver.mCurrent = game.getWorld().mScreenHeight - y;
						isPickedUp = true;
					}
					
					if (isPickedUp)
					{
						mPickupStartTime = System.currentTimeMillis();
						if (mScaleOnInteraction)
							mScaleDriver.start(mScaleDriver.mCurrent, 1.2f, MotionEquation.LOGISTIC, 350, 0);
					}
					
					
					return true;
				}
			}
			else
			{
				// crop

				//if (isWithinClickableArea(x, y, pixYOffset))
				{
					if (mLockXDir == false)
					{
						mXCrop = (int) x;
					}
					if (mLockYDir == false)
					{
						mYCrop = (int) y;
					}
				}
			}
		}
		return false;
	}
	
	@Override
	public Boolean interact(float x, float y, int pixYOffset, int digitCount)
	{
		absx = x;
		absy = y;
		
		if (mInteractable)
		{
			//game.text += x+" , " + y + "  --->  ";	
			
			
			//game.text += x+" , " + y + "\n";
			
			if (!mCropOnInteraction)
			{
				// move
				if (isPickedUp /*&& isWithinClickableArea(x, y, pixYOffset)*/) 
				{
					//game.text = "      Drag indeed!" + "\n" + game.text;
					//game.textviewHandler.post(game.updateTextView);
					
					if (mLockXDir == false)
					{
						if (mXDriver.isTransforming)
							mXDriver.stop();
						mXDriver.mCurrent = x;
					}
					if (mLockYDir == false)
					{
						if (mYDriver.isTransforming)
							mYDriver.stop();
						mYDriver.mCurrent = game.getWorld().mScreenHeight - y;
					}
					
					return true;
				}
			}
			else
			{
				// crop
				
				//if (isWithinClickableArea(x, y, pixYOffset))
				{
					if (mLockXDir == false)
					{
						mXCrop = (int) x;
					}
					if (mLockYDir == false)
					{
						mYCrop = (int) y;
					}
				}
					
			}
		}
		return false;
	}
	
	@Override
	public void drop(int digitCount)
	{
		drop(digitCount, true);
	}
	
	@Override
	public void drop(int digitCount, Boolean toGridResolution)
	{
		if (mInteractable)
		{
			if (isPickedUp)
			{
				isPickedUp = false;
				mPickupStartTime = -1l;
				
				if (mScaleOnInteraction)
					mScaleDriver.start(mScaleDriver.mCurrent, 1f, MotionEquation.LOGISTIC, 350, 0);
				
				if (mStickyInteraction)
				{
					// drop where it was picked up
					
					if (mLockXDir == false)
					{
						float x = mStickyPixCoord.getX();
						mXDriver.start(mXDriver.mCurrent, x, MotionEquation.LOGISTIC, 400, 0);
					}
					if (mLockYDir == false)
					{
						float y = mStickyPixCoord.getY();
						mYDriver.start(mYDriver.mCurrent, y, MotionEquation.LOGISTIC, 400, 0);
					}
					
				}
				else if (toGridResolution)
				{
					// drop in nearest grid location
					
					Coord<Integer> coord = game.getWorld().convertPixToRC(mXDriver.mCurrent, mYDriver.mCurrent);
					
					//x += mXOffset;
					//y += mYOffset;
					
					if (mLockXDir == false)
					{
						float x = game.getWorld().mScreenBlockLength*(coord.getCol()) + game.getWorld().mScreenBlockLength/2;
						mXDriver.start(mXDriver.mCurrent, x, MotionEquation.LOGISTIC, 400, 0);
					}
					if (mLockYDir == false)
					{
						float y = game.getWorld().mScreenHeight - game.getWorld().mScreenBlockLength*(coord.getRow())  + game.getWorld().mScreenBlockLength/2;
						mYDriver.start(mYDriver.mCurrent, y, MotionEquation.LOGISTIC, 400, 0);
					}
					
					
				}
			}
		}
	}

	@Override
	public void fadeIn(MotionEquation eq, Integer dur, Integer delay, Boolean full)
	{
		if (full)
			mAlphaDriver.start(0f, 1f, eq, dur, delay);
		else
			mAlphaDriver.start(mAlphaDriver.mCurrent, 1f, eq, dur, delay);
	}
	
	@Override
	public void fadeOut(MotionEquation eq, Integer dur, Integer delay, Boolean full)
	{
		if (full)
			mAlphaDriver.start(1f, 0f, eq, dur, delay);
		else
			mAlphaDriver.start(mAlphaDriver.mCurrent, 0f, eq, dur, delay);
			
	}
	
	@Override
	public void moveTo(Float pXend, Float pYend, MotionEquation eq, Integer dur, Integer delay)
	{
		mXDriver.start(mXDriver.mCurrent, pXend, eq, dur, delay);
		mYDriver.start(mYDriver.mCurrent, pYend, eq, dur, delay);
	}
	
	@Override
	public void update(long now, Boolean primaryThread, Boolean secondaryThread)
	{
		mXDriver.update(now, primaryThread, secondaryThread);
		mYDriver.update(now, primaryThread, secondaryThread);
		mAlphaDriver.update(now, primaryThread, secondaryThread);
		mScaleDriver.update(now, primaryThread, secondaryThread);
		
		if (primaryThread)
		{	
			
			//game.text  = "Start  : " + mAlphaDriver.mStartPoint+"\n";
			//game.text += "End    : " + mAlphaDriver.mEndPoint+"\n";
			//game.text += "Current: " + mAlphaDriver.mCurrent+"\n";
			//game.textviewHandler.post(game.updateTextView);
			
			
			// adject width & height based on scale
			mScaledWidth = (int) (mAbsScaledWidth*mScaleDriver.mCurrent);
			mScaledHeight = (int) (mAbsScaledHeight*mScaleDriver.mCurrent);
			
			mXOffset = mXRelOffset*mScaleDriver.mCurrent;
			mYOffset = mYRelOffset*mScaleDriver.mCurrent;
			
			// Adjust Offset based on scale (center on dimensional differences)
			mXScaledOffset = mXOffset - (mScaledWidth - mAbsScaledWidth)/2f;
			mYScaledOffset = mYOffset - (mScaledHeight - mAbsScaledHeight)/2f;		
			
			// set relative positions (for interaction reference)
			if (mLeftJust)
			{
				// left just
				mLeftPos	= mXScaledOffset + mXDriver.mCurrent;
				mRightPos	= mXScaledOffset + mXDriver.mCurrent + mScaledWidth;
			}
			else
			{
				// right just
				mLeftPos	= mXScaledOffset + mXDriver.mCurrent - mScaledWidth;
				mRightPos	= mXScaledOffset + mXDriver.mCurrent;
			}
			// vertical 
			mTopPos		= mYScaledOffset + mYDriver.mCurrent + mScaledHeight;
			mBottomPos	= mYScaledOffset + mYDriver.mCurrent;
			
			
			// Crop
			if (mCropOnInteraction)
			{
				if (mLockXDir == false)
				{
					// anchored left
					if (mLeftJust)
						mCropWidth = (mWidth/mScaledWidth)*(mScaledWidth + Math.min(0, mLeftPos + ( mXCrop - mRightPos) ) - mLeftPos );
					// anchored right
					else
					{
						// todo
						mCropHeight = (mHeight/mScaledHeight)*(mScaledHeight + Math.min(0, mTopPos + ( mYCrop - mBottomPos) ) - mTopPos );
					}
				}
				if (mLockYDir == false)
				{
					mCropHeight = (mHeight/mScaledHeight)*(mScaledHeight + Math.min(0, mBottomPos + (mYCrop - mTopPos) ) - mBottomPos);
				}
			}
			
			/*
			game.text += "========================\n";
			game.text += "scaW / 2      : " + (mScaledWidth/2) + "\n";
			game.text += "mXScaledOffset: " + mXScaledOffset + "\n";
			game.text += "mLeftPos      : " + mLeftPos + "\n";
			game.text += "Center Coords : " + ((int)(mLeftPos + mScaledWidth/2)) + " == " + ((int)absx) + "\n";
			game.text += "scaW - absScaW: " + (mScaledWidth - mAbsScaledWidth) + "\n";
			game.text += "relX          : " + (mXDriver.mCurrent - mLeftPos) + "\n";
			game.text += "curX          : " + mXDriver.mCurrent + "\n";
			*/
			
			/*
			game.text += "========================\n";
			game.text += "mXOffset        : " + mXOffset + "\n";
			game.text += "mXScaledOffset  : " + mXScaledOffset + "\n";
			game.text += "scaW - absScaW  : " + (mScaledWidth - mAbsScaledWidth) + "\n";
			game.text += "xPos            : " + mXDriver.mCurrent + "\n";
			game.text += "mLeftPos        : " + mLeftPos + "\n";	
			*/
			
			/*
			game.text += "========================\n";
			game.text += "mXCrop    : " + mXCrop + "\n";
			game.text += "mYCrop    : " + mYCrop + "\n";
			game.text += "cropWidth : " + mCropWidth + "\n";
			game.text += "cropHeight: " + mCropHeight + "\n";
			*/
			
			/*
			game.text += "Left  : " + mLeftPos + "\n";
			game.text += "Right : " + mRightPos + "\n";
			game.text += "Top   : " + mTopPos + "\n";
			game.text += "Bottom: " + mBottomPos + "\n";
			game.text += "--------------------------\n";
			game.text += "ScaWidth : " + mScaledWidth + "\n";
			game.text += "ScaHeight: " + mScaledHeight + "\n";
			game.text += "--------------------------\n";
			game.text += "ScaXOff: " + mXScaledOffset + "\n";
			game.text += "ScaYOff: " + mYScaledOffset + "\n";
			*/
			//game.textviewHandler.post(game.updateTextView);
			
		}
		else if (secondaryThread)
		{	
			// check if should drop
			if (isPickedUp)
			{
				if (((mPickupStartTime+ShapeManager.mActiveBlockExpirationDuration) < now) && mPickupStartTime != -1 )
				{
					drop(0);
				}
			}
		}
		
	}

	@Override
	public void draw(GL10 gl, float pixYOffset)
	{
		// LOAD...
		if (!isLoaded)
		{
			loadImage(gl);
		}
		
		// DRAW...
		
		// rely on z-ordering
		gl.glDisable(GL10.GL_DEPTH_TEST);
		
		// GL11ETX wont work with lighting on some devices!
		// Disable lighting
		gl.glDisable(GL10.GL_LIGHT0);	
		gl.glDisable(GL10.GL_LIGHTING);
		
		gl.glEnable(GL10.GL_BLEND);
		gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
		
		gl.glColor4f(1f, 1f, 1f, mAlphaDriver.mCurrent);
		
		// Set up GL for rendering the text
		gl.glEnable(GL10.GL_TEXTURE_2D);
		gl.glBindTexture(GL10.GL_TEXTURE_2D, texID);
		
		// Update the crop rect
		UVarray[0] = 0;
		UVarray[1] = (int) mCropHeight;
		UVarray[2] = (int) mCropWidth;
		UVarray[3] = (int) -mCropHeight;
		
		// Set crop area
		((GL11) gl).glTexParameteriv(GL10.GL_TEXTURE_2D, GL11Ext.GL_TEXTURE_CROP_RECT_OES,UVarray,0);
		
		// Draw texture (scaled)			
		((GL11Ext) gl).glDrawTexfOES(
				mXScaledOffset + mXDriver.mCurrent ,	// x
				mYScaledOffset + mYDriver.mCurrent + pixYOffset,	// y
				World.GLEX11HUD_Z_ORDER,
				(mCropWidth/mWidth)*mScaledWidth*mScaleDriver.mCurrent,
				(mCropHeight/mHeight)*mScaledHeight*mScaleDriver.mCurrent );

		gl.glDisable(GL10.GL_TEXTURE_2D);
		gl.glDisable(GL10.GL_BLEND);
		
		// Enable Lighting!
		gl.glEnable(GL10.GL_LIGHT0);
		gl.glEnable(GL10.GL_LIGHTING);
		
		// re enable depth
		gl.glEnable(GL10.GL_DEPTH_TEST);
		
	}


	
} public class PxMoveHinter implements PxBase
{
	private final Context mContext;
	private final MainActivity game;
	
	private PxImage mMoveHint;
	private PxImage mCircle;
	
	PxMoveHinter(Context context, float circleX, float circleY,  Boolean lockXMove, Boolean lockYMove, Boolean interactable)
	{
		mContext = context;
		game = (MainActivity) mContext;
	
		
		int circleWidth = 128, circleHeight = 128;
		float circleAbsScale = (float) ( (1f/circleWidth)*(game.getWorld().mScreenBlockLength) )*1.7f;
		int hintWidth = 579, hintHeight = 644;
		float hintAbsScale = (float) ( (1f/hintWidth)*(game.getWorld().mScreenBlockLength*4) );
		
		mCircle = new PxImage(mContext,  
				R.drawable.fingercircle_w128h128, circleWidth, circleHeight, 
				circleAbsScale, 
				(-circleWidth/2f), (-circleHeight/2f),
				circleX, circleY,
				0,0,
				lockXMove, lockYMove, 
				interactable,		
				true,		// scale on interaction
				false		// crop on interaction
				);
		
		mMoveHint = new PxImage(mContext,  
				R.drawable.movehints_w579h644, hintWidth, hintHeight, 
				hintAbsScale, 
				(-hintWidth/2f), (-hintHeight/2f),
				circleX, circleY,
				0, 0,
				lockXMove, lockYMove,
				interactable,		
				false,		// scale on interaction
				false		// crop on interaction
				);
		
	}
	
	@Override
	public void loadImage(GL10 gl) {
		mMoveHint.loadImage(gl);
		mCircle.loadImage(gl);
	}

	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount) {
		return pickup(x, y, pixYOffset, digitCount, false);
	}

	@Override
	public Boolean pickup(float x, float y, int pixYOffset, int digitCount, Boolean force) {
		if(mCircle.pickup(x, y, pixYOffset, digitCount, force))
		{
			mMoveHint.pickup(x, y, pixYOffset, digitCount, force);
			return true;
		}
		return false;
	}

	@Override
	public Boolean interact(float x, float y, int pixYOffset, int digitCount) {
		//game.text = "\n\n\n\n\n\n\n";
		if(mCircle.interact(x, y, pixYOffset, digitCount))
		{
			mMoveHint.interact(x, y, pixYOffset, digitCount);
			//game.textviewHandler.post( game.updateTextView );
			return true;
		}
		return false;
	}

	@Override
	public void drop(int digitCount) {
		mMoveHint.drop(digitCount);
		mCircle.drop(digitCount);
	}

	@Override
	public void drop(int digitCount, Boolean toGridResolution) {
		mMoveHint.drop(digitCount, toGridResolution);
		mCircle.drop(digitCount, toGridResolution);
	}

	@Override
	public void fadeIn(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mMoveHint.fadeIn(eq, dur, delay, full);
		mCircle.fadeIn(eq, dur, delay, full);
	}

	@Override
	public void fadeOut(MotionEquation eq, Integer dur, Integer delay, Boolean full) {
		mMoveHint.fadeOut(eq, dur, delay, full);
		mCircle.fadeOut(eq, dur, delay, full);
	}

	@Override
	public void moveTo(Float pXend, Float pYend, MotionEquation eq, Integer dur, Integer delay) {
		mMoveHint.moveTo(pXend, pYend, eq, dur, delay);
		mCircle.moveTo(pXend, pYend, eq, dur, delay);
	}

	@Override
	public void update(long now, Boolean primaryThread, Boolean secondaryThread) {
		mMoveHint.update(now, primaryThread, secondaryThread);
		mCircle.update(now, primaryThread, secondaryThread);
	}

	@Override
	public void draw(GL10 gl, float pixYOffset) {
		mMoveHint.draw(gl, pixYOffset);
		mCircle.draw(gl, pixYOffset);
	}

} public class TutorialBannerMenu
{

	private final Context mContext;
	private final MainActivity game;
	
	public Boolean mEnabled = false;
	
	public static final String NONE			= "none";
	public static final String RULES		= "rules";
	public static final String GAMEPLAY     = "gameplay";
	public static final String MATCHING		= "matching";
	public static final String GARBAGE		= "garbage";
	public static final String BOARD		= "board";
	public static final String ADVANCED		= "advanced";
	
	private final int mHintDelay = 9000;
	private final Color mHintColor = Color.YELLOW;
	
	public int mTopBoarder = 10;
	public int mHorizontalBoarder = 10;
	
	private float mLeftPerc = 0.07f;
	private float mRightPerc = 1f - mLeftPerc;
	private float mFontScale = 0.85f;
	
	private ConcurrentHashMap<String, Menu> mMenus = new ConcurrentHashMap<String, Menu>();
	// holy hell this is cool: http://www.onkarjoshi.com/blog/201/concurrenthashset-in-java-from-concurrenthashmap/
	private String mMostCurrentMenu = NONE;
	private Set<String> mCurrentMenus = Collections.newSetFromMap(new ConcurrentHashMap<String,Boolean>());
	private ConcurrentHashMap<String, Long> mNonCurrentMenuTrigger = new ConcurrentHashMap<String, Long>();

	private Boolean isLocked = false;
	
	public TutorialBannerMenu(Context context)
	{
		mContext = context;
		game = (MainActivity) mContext;

		
	}
	
	public void lock()
	{
		isLocked = true;
	}
	
	public void unlock()
	{
		isLocked = false;
	}
	
	public void setDependencies()
	{
		for (Menu menu : mMenus.values())
			menu.setDependencies();			
	}
	
	float fontHeight;
	private int getVeriticalPosition(String font, float scale)
	{
		fontHeight = game.getWorld().mDropSection.mFonts.getFontHeight(font) * scale;
		
		float bottom = -game.getWorld().mDropSection.mYPixFoldedHeight;
		float middle = bottom + (-bottom/2f);
		
		// TEMP TEMP TEMP
		return (int) (middle - fontHeight/2f); //-70
		
	}
	
	private int getCenteredPosition(int width)
	{
		return (int) (game.getWorld().mScreenWidth/2 - width/2) ;
	}
	
	private int getCenteredPosition(String phrase)
	{
		return (int) (game.getWorld().mScreenWidth/2 - (game.getWorld().mDropSection.mFonts.getStringWidth(FontManager.MENUMINORITEM_FONT, phrase)*mFontScale)/2) ;
	}

	
	private int getLeftPosition(String phrase, int pos)
	{
		return pos;
	}
	
	private int getRightPosition(String phrase, int pos)
	{
		return (int) (pos - game.getWorld().mDropSection.mFonts.getStringWidth(FontManager.MENUMINORITEM_FONT, phrase)*mFontScale) ;
	}
	
	
	public void addMenu(String menuTitle, Menu menu)
	{
		mMenus.put(menuTitle, menu);
	}
	
	
	
	public Menu getMenu(String menuTitle)
	{
		return mMenus.get(menuTitle);
	}

	public void transitionToMenu(String menuTitle, Boolean doOutro )
	{
		transitionToMenu(menuTitle, doOutro, null);
	}
	
	public void transitionToMenu(String menuTitle, Boolean doOutro, Integer text_duration)
	{
		if (isLocked)
			return;
		
		// cancel any menus on outro, prepare for intro
		mNonCurrentMenuTrigger.remove(menuTitle);
		
		synchronized(mCurrentMenus)
		{
			for (String menu : mCurrentMenus)
			{
				if (menuTitle != menu)
				{
					// only commit outro if it has not recently been committed...
					if (!mNonCurrentMenuTrigger.containsKey(menu))
					{
						// instructed to do outro?
						if (doOutro)
						{
							// start outro
							mMenus.get(menu).outro();
							
							// trigger menu to not be updated
							mNonCurrentMenuTrigger.put(menu, mMenus.get(menu).getOutroDuration() + System.currentTimeMillis());
						}
						else
						{
							mNonCurrentMenuTrigger.put(menu, System.currentTimeMillis());
						}
					}
				}
			}
		}


		if (menuTitle != NONE)
		{
			
			// start intro
			if (text_duration == null)
				mMenus.get(menuTitle).intro();
			else
				mMenus.get(menuTitle).intro(text_duration);
			
			synchronized(mCurrentMenus)
			{
				// add new menu to current menus
				mCurrentMenus.add(menuTitle);
				mMostCurrentMenu = menuTitle;
			}
		}
		
	}
	
	public void outro()
	{
		synchronized(mCurrentMenus)
		{
			for (String menu : mCurrentMenus)
			{

				// start outro
				mMenus.get(menu).outro();
				
				// trigger menu to not be upated
				mNonCurrentMenuTrigger.put(menu, mMenus.get(menu).getOutroDuration() + System.currentTimeMillis());
			
			}
		}
	}
	
	/*
	public void hide()
	{
		for (String menu : mCurrentMenus)
			mMenus.get(menu).hide();
		
	}
	*/
	
	public void interact(int x, int y, int pixFontOffset)
	{
		//game.text += "TUTORIAL HIT!\n";
		//game.textviewHandler.post(game.updateTextView);
		
		if (mEnabled)
		{
			synchronized(mCurrentMenus)
			{			
				for (String menu : mCurrentMenus)
					mMenus.get(menu).interact(x, y, pixFontOffset);
			}
		}
	}
	
	public Boolean gotoBackMenu()
	{
		synchronized(mCurrentMenus)
		{
			if (mCurrentMenus.contains(mMostCurrentMenu) && mEnabled)
			{
				// only apply to the most current menu
				return mMenus.get(mMostCurrentMenu).gotoBackMenu(null, null);
			}
		}
		
		return false;
	}
	
	public void update(long now, Boolean primaryThread, Boolean secondaryThread)
	{
		if (!mEnabled)
			return;
		
		// remove any menus from the currentMenu array (if needed)
		if (secondaryThread)
		{
			if (mNonCurrentMenuTrigger.size() > 0)
			{
				for (Map.Entry<String, Long> entry : mNonCurrentMenuTrigger.entrySet())
				{
					if (entry.getValue() < now)
					{
						synchronized(mCurrentMenus)
						{
							// remove from the list
							mCurrentMenus.remove(entry.getKey());
						}
						// remove trigger
						mNonCurrentMenuTrigger.remove(entry.getKey());
					}
				}
			}
		}
	
		synchronized(mCurrentMenus)
		{
			// do updates
			for (String menu : mCurrentMenus)
				mMenus.get(menu).update(now, primaryThread, secondaryThread);
		}
	}

	public void draw(GL10 gl, float pixYOffset)
	{
		if (!mEnabled)
			return;
		
		
		synchronized(mCurrentMenus)
		{
			for (String menu : mCurrentMenus)
				mMenus.get(menu).draw(gl, pixYOffset);
		}
		

	}
	
	
	////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	// MENUS!
	
	private GLButton<Void> gen_menuButton;
	
	public void generateMenus()
	{

		Menu menu;
		String text;

		
		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// 1) Rules...
		
		menu = new Menu(mContext, NONE);
		text = "<~ MainMenu";
		String font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getLeftPosition(text, (int) (game.getWorld().mScreenWidth*mLeftPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						game.initGame( game.getWorld().mBoards.mGameState.mGameDiff, game.getWorld().mBoards.mGameState.mGameMode );
						game.setIsGameOver(false);
						game.setIsGameStarted(false);
						game.getWorld().mMenus.transitionToMenu(MenuManager.MAINMENU, true,  DropSection.DEFAULT_DURATION,  DropSection.SLOW_EQ);
						
						// Disable Tutorial on the way out!
						//game.getWorld().mTutorialMenu.mEnabled = false;	// let a new game do this (let outro be drawn)
						game.getWorld().mTutorialState.goTo(NONE, true);
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		text = "Basics ~>";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getRightPosition(text, (int) (game.getWorld().mScreenWidth*mRightPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(GAMEPLAY, true);
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		gen_menuButton.setHint(mHintDelay, false, true, mHintColor); // hint Y color *after* X seconds, do forever
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		
		addMenu(RULES, menu);
		
		
		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// 2) Game Play...
		
		menu = new Menu(mContext, NONE);

		text = "<~ Goal";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getLeftPosition(text, (int) (game.getWorld().mScreenWidth*mLeftPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(RULES, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		
		
		text = "Matching Blocks ~>";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getRightPosition(text, (int) (game.getWorld().mScreenWidth*mRightPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(MATCHING, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		gen_menuButton.setHint(mHintDelay, false, true, mHintColor); // hint Y color *after* X seconds, do forever
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		
		addMenu(GAMEPLAY, menu);
		
		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// 3) Matching...
		
		menu = new Menu(mContext, NONE);

		text = "<~ Basics";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getLeftPosition(text, (int) (game.getWorld().mScreenWidth*mLeftPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(GAMEPLAY, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		
		text = "Garbage Blocks ~>";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getRightPosition(text, (int) (game.getWorld().mScreenWidth*mRightPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(GARBAGE, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;			
		gen_menuButton.setHint(mHintDelay, false, true, mHintColor); // hint Y color *after* X seconds, do forever
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		
		addMenu(MATCHING, menu);		
		
		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// 4) Garbage Blocks...
		
		menu = new Menu(mContext, NONE);

		text = "<~ Matching";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getLeftPosition(text, (int) (game.getWorld().mScreenWidth*mLeftPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(MATCHING, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;			
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		text = "Board Movement ~>";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getRightPosition(text, (int) (game.getWorld().mScreenWidth*mRightPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(BOARD, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		gen_menuButton.setHint(mHintDelay, false, true, mHintColor); // hint Y color *after* X seconds, do forever
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		
		addMenu(GARBAGE, menu);		
		
		
		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// 5) Board Movement
		
		menu = new Menu(mContext, NONE);

		text = "<~ Garbage";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getLeftPosition(text, (int) (game.getWorld().mScreenWidth*mLeftPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(GARBAGE, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;			
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		text = "Advanced Boards ~>";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getRightPosition(text, (int) (game.getWorld().mScreenWidth*mRightPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(ADVANCED, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		gen_menuButton.setHint(mHintDelay, false, true, mHintColor); // hint Y color *after* X seconds, do forever
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		addMenu(BOARD, menu);		
		
		///////////////////////////////////////////////////////////////////////////////////////////////////////
		// 6) Advanced Boards...
		
		menu = new Menu(mContext, NONE);

		text = "<~ Board Movement";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getLeftPosition(text, (int) (game.getWorld().mScreenWidth*mLeftPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						game.getWorld().mTutorialState.goTo(BOARD, true);
						
						//TBD
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;			
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		text = "PLAY!";
		font = FontManager.MENUMINORITEM_FONT;
		gen_menuButton = new GLButton<Void>(
				mContext, FontManager.MENUMINORITEM_FONT, World.mMenuForegroundColor,
				text, getRightPosition(text, (int) (game.getWorld().mScreenWidth*mRightPerc)), getVeriticalPosition(font, mFontScale) , true, mFontScale,    //x, y, leftjust?, fontScale
				new Callable<Void>() {
					public Void call() {
						
						// Disable Tutorial on the way out!
						game.getWorld().mTutorialMenu.mEnabled = false;
						game.getWorld().mTutorialState.goTo(NONE, true);
						game.setIsGameStarted(false);
						
						// TBD
						game.initGame(GameDifficulty.EASY, GameMode.CLASSIC);
						game.startGame(null, null, false);
						
						return null;
					}
				}
			);
		gen_menuButton.mIntroTransform =  TextTransform.MoveInFromRight;
		gen_menuButton.mOutroTransform =  TextTransform.MoveOutLeft;		
		gen_menuButton.setHint(mHintDelay, false, true, mHintColor); // hint Y color *after* X seconds, do forever
		
		menu.addItem( (GLMenuItem) gen_menuButton );
		
		addMenu(ADVANCED, menu);	
		
		
	}
	
	
	
	
}
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.HashSet;
import java.util.Map;
import com.wagoodman.stackattack.Animation;
import com.wagoodman.stackattack.Block;
import com.wagoodman.stackattack.Color;
import com.wagoodman.stackattack.ColorState;
import com.wagoodman.stackattack.Coord;
import com.wagoodman.stackattack.DropSectionState;
import com.wagoodman.stackattack.FontManager;
import com.wagoodman.stackattack.GL3DText;
import com.wagoodman.stackattack.GLShape;
import com.wagoodman.stackattack.MotionEquation;
import com.wagoodman.stackattack.PxCropSwipeLeft;
import com.wagoodman.stackattack.PxDoubleStaticSwipeSideways;
import com.wagoodman.stackattack.PxDoubleStaticSwipeUp;
import com.wagoodman.stackattack.PxImage;
import com.wagoodman.stackattack.PxMoveHinter;
import com.wagoodman.stackattack.TutorialBannerMenu;
import android.content.Context;
			isInTutorial = true;
	}
	



	
	


	public void loadFirstTutorialBoard()
	{
		game.getWorld().mBoards.mBoardMembers.clear();
		game.getWorld().createBoards(1);
		
		buildStockBoard();
		
		
		Board temp;

		
		
		for (int idx=0; idx < game.getWorld().mBoards.mBoardMembers.size(); idx++)
		{
			temp = new Board(mContext);
			
			// Populate Board
			normalizeBoard(temp, 
					false, // all grey
					false, // show red
					true,  // allow add
					false, // show blue hint
					false  // show yellow
					);

			temp.isInteractable = false;
			
			// add new board
			game.getWorld().mBoards.mBoardMembers.set(idx, temp);
			
		}
		
		/*
		// banner
		game.getWorld().mWidgetLayer.addItem("RuleBanner",
				mRuleDangerBanner,
				700,
				0,
				null		// dont trigger outro
			);
		*/
		
		
		// show text 1
		game.getWorld().mWidgetLayer.addItem(
				"RuleText1",
				mRuleInstructions1,
				Animation.SQUISHROCKFLIPIN , null, mRuleCoords1 , 1000, 0  , null
			);
		
		// arrow 1
		game.getWorld().mWidgetLayer.addItem("RuleArrow1",
				mRuleArrow1,
				700,
				0,
				null		// dont trigger outro
			);
		
		
		// show text 2
		game.getWorld().mWidgetLayer.addItem(
				"RuleText2",
				mRuleInstructions2,
				Animation.SQUISHROCKFLIPIN , null, mRuleCoords2 , 1000, 0  , null
			);
		
		// arrow2
		game.getWorld().mWidgetLayer.addItem("RuleArrow2",
				mRuleArrow2,
				700,
				0,
				null		// dont trigger outro
			);

		
		
		
	}
	
	


	private void load_Gameplay() 
	{

		for (Board board : game.getWorld().mBoards.mBoardMembers)
		{
			
			// Populate Board
			normalizeBoard(board, 
					true,  // all grey
					false, // show red
					false, // allow add
					true, // show blue hint
					false  // show yellow
					);
			
			// find coords
			hintCoord = board.mGrid.getBlockPosition(mBlueHintBlock);
			
			
			for (Map.Entry<String, GLShape> entry : board.mBlocks.entrySet())
			{
				// turn everything grey except for the red match blocks
				if ( entry.getKey() == mBlueHintBlock )
				{
					// ensure they are red!
					entry.getValue().mColor.dequeue();
					entry.getValue().mColor.enqueue( 
							new ColorState( 
									new MotionEquation[] {MotionEquation.LINEAR, MotionEquation.LINEAR, MotionEquation.LINEAR, MotionEquation.LINEAR}, 