//Kurtis Fields - 2007
var worldMinX = 0 ; var worldMinY = 0 ; var worldMaxX = 640 ; var worldMaxY = 480 ;

var g_alertOnce = true ;
function alertOnce(msg) {
	if(g_alertOnce) {
		alert(msg) ;
		g_alertOnce = false ;
	}
}	
var degRads = (Math.PI*2)/360 ;

function distance2d(x1, y1, x2, y2) {
	var diffX = x1 - x2 ;
	var diffY = y1 - y2 ;
	return Math.sqrt((diffX*diffX)+(diffY*diffY)) ;	
}
function sign(x) {
	return x < 0.0 ? -1.0 : 1.0 ;
}
var Wiggles = {}

Wiggles.Console = Class.create();
Wiggles.Console.prototype = {
	initialize: function() {
		this.element = document.createElement("div") ;
		this.element.id = 'spriteConsole' ;
		this.element.style.width = 360 ;
		this.element.style.height = 600 ;
		this.element.style.position = 'absolute' ;
		//this.element.style.left = 0 ;
		this.element.style.left = '0px' ;
		//this.element.style.top = 0 ;
		this.element.style.top = '0px' ;
		this.element.style.overflow = 'scroll' ;
		this.element.style.backgroundColor = 'white' ;
		this.element.style.paddingLeft = '10px' ;
		//this.element.style.display = 'none' ;
		//this.element.style.zindex = this.m_z ;
		document.body.appendChild(this.element) ;		
		this.write("<h1>Wiggles v1.0</h1>") ;
	},
	write: function(txt) {
		var p = document.createElement("p") ;
		p.innerHTML = txt ;
		this.element.appendChild(p) ;
	}
}
var spriteConsole = null ;

Wiggles.SpriteEngine = Class.create();
Wiggles.SpriteEngine.prototype = {
	initialize: function() {
		this.actors = new Array() ;
		this.beacons = new Array() ;
		this.bodies = new Array() ;
		this.m_collisions = new Array() ;
		this.idCounter = 0 ;
		//
		this.m_gravityX = 0 ;
		//this.m_gravityY = 9.8 ;		
		this.m_gravityY = 0 ;
	},
	addActor: function(actor) {
		this.actors.push(actor) ;
	},
	removeActor: function(actor) {
		this.actors  = this.actors.without(actor) ;
	},	
	addBeacon: function(beacon) {
		this.beacons.push(beacon) ;
	},	
	removeBeacon: function(beacon) {
		this.beacons = this.beacons.without(beacon) ;
	},		
	addBody: function(body) {
		this.bodies.push(body) ;
	},	
	removeBody: function(body) {
		this.bodies = this.bodies.without(body) ;
	},			
	addCollision: function(collision) {
		this.m_collisions.push(collision) ;
	},	
	removeCollision: function(collision) {
		this.m_collisions = this.m_collisions.without(collision) ;
	},				
	findCollision: function(b1, b2) {
		var collision = null ;
		for(var i = 0 ; i < this.m_collisions.length ; i++) {
			collision = this.m_collisions[i] ;
			if(collision.m_b1 == b1 && collision.m_b2 == b2 || collision.m_b1 == b2 && collision.m_b2 == b1)
			return collision ;
		}
		return null ;
	},					
	step: function() {
		var dt = .1 ;
		var inv_dt = dt > 0.0 ? 1.0 / dt : 0.0 ;
		var b = null ;
		//
		this.broadphase() ;
		//
		for(var i = 0 ; i < this.bodies.length ; i++) {
			b = this.bodies[i] ;
			if(b.m_invMass == 0.0)
				continue;
			b.m_velX += dt * (this.m_gravityX + b.m_invMass * b.m_forceX);
			b.m_velY += dt * (this.m_gravityY + b.m_invMass * b.m_forceY);
			b.m_angularVel += dt * b.m_invI * b.m_torque;
		}
		//	... insert penetration constraints here ...
		for(var i = 0 ; i < this.m_collisions.length ; ++i)	{
			if(!this.m_collisions[i].m_touched) continue ;
			this.m_collisions[i].preStep(inv_dt);
		}
		/*for (int i = 0; i < (int)joints.size(); ++i) {
			joints[i]->PreStep(inv_dt);	
		}*/
		var iterations = 1 ;
		for (var i = 0; i < iterations; ++i) {
			for (var j = 0 ; j < this.m_collisions.length ; ++j) {
				if(!this.m_collisions[j].m_touched) continue ;
				this.m_collisions[j].applyImpulse();
			}
			/*for (int j = 0; j < (int)joints.size(); ++j) {
				joints[j]->ApplyImpulse();
			}*/
		}
		for(var i = 0 ; i < this.m_collisions.length ; ++i)	{
			if(!this.m_collisions[i].m_touched) continue ;
			this.m_collisions[i].postStep();
		}		
		//
		for(var i = 0 ; i < this.bodies.length ; i++) {
			b = this.bodies[i] ;
			if(b.m_invMass == 0.0)
				continue;			
			/*b.m_x += dt * (b.m_velX + b.m_biasedVelX) ;
			b.m_y += dt * (b.m_velY + b.m_biasedVelY) ;*/
			b.setPos(b.m_x + dt * (b.m_velX + b.m_biasedVelX), b.m_y + dt * (b.m_velY + b.m_biasedVelY)) ;
			
			b.m_rotation += dt * (b.m_angularVel + b.m_biasedAngularVel);
	
			// Bias velocities are reset to zero each step.
			b.m_biasedVelX = 0 ;
			b.m_biasedVelY = 0 ;
			b.m_biasedAngularVel = 0 ;
			b.m_forceX = 0 ;
			b.m_forceY = 0 ;
			b.m_torque = 0 ;
			//
			b.step() ;
		}
		//
		for(var i = 0 ; i < this.actors.length ; i++) {
			this.actors[i].step() ;
		}
	},
	broadphase: function() {
		var b1 = null ; var b2 = null ;
		for(var i = 0 ; i < this.bodies.length ; i++) {
			b1 = this.bodies[i] ;
			for(var j = 0 ; j < this.bodies.length ; j++) {
				b2 = this.bodies[j] ;
				if(b1 == b2) continue ;				
				if (b1.m_invMass == 0.0 && b2.m_invMass == 0.0) continue ;
				if(!b1.intersects(b2)) continue ;
				var collision = this.findCollision(b1, b2) ;
				if(collision == null) {
					collision = new Wiggles.Collision(b1,b2) ;
					this.addCollision(collision) ;
				}
				collision.collide() ;
			}
		}
	},
	query: function(x, y, distance) {
		var beacon = null ;
		var result = null ;
		for(var i = 0 ; i < this.beacons.length ; i++) {
			beacon = this.beacons[i] ;
			var dist = distance2d(x, y, beacon.m_x, beacon.m_y) ;
			if(dist < distance) {
				if(result == null)
					result = new Array(beacon) ;
				else
					result.push(beacon) ;
			}
		}
		return result ;
	},	
	genId: function(name) {
		return name + this.idCounter ;
		this.idCounter += 1 ;
	}
}
var spriteEngine = new Wiggles.SpriteEngine() ;
////////////////
Wiggles.Collision = Class.create();
Wiggles.Collision.prototype = {
	initialize: function(b1, b2) {
		this.m_b1 = b1 ;
		this.m_b2 = b2 ;
		this.m_contactX = 0 ;
		this.m_contactY = 0 ;
		this.m_normalX = 0 ;
		this.m_normalY = 0 ;
		this.m_separation = 0 ;
		//
		this.m_numContacts = 2 ;
		this.m_contacts = new Array(this.m_numContacts) ;
		for(var i = 0 ; i < this.m_numContacts ; ++i) {
			this.m_contacts[i] = new Wiggles.Contact() ;
		}
		this.m_touched = false ;
	},
	collide: function() {
		//spriteConsole.write("Collision.collide:") ;
		if(this.m_touched)
			return ;
			
		switch(this.m_b1.m_shapeType) {
			case 'ball' :
				switch(this.m_b2.m_shapeType) {
					case 'ball' :
						return this.collideBallBall(this.m_b1, this.m_b2) ; break ;
					case 'box' :
						return this.collideBallBox(this.m_b1, this.m_b2) ; break ;
				}
				break ;
			case 'box' :
				switch(this.m_b2.m_shapeType) {
					case 'ball' :
						return this.collideBallBox(this.m_b2, this.m_b1) ; break ;
					case 'box' :
						return this.collideBoxBox(this.m_b2, this.m_b1) ; break ;
				}
				break ;					
		}
	},
	collideBallBall: function(b1, b2) {
		var radialDistance = b1.m_radius + b2.m_radius ;
		var x1 = b1.m_x ; var y1 = b1.m_y ;
		var x2 = b2.m_x ; var y2 = b2.m_y ;
		var dist = distance2d(x1,y1,x2,y2) ;
		if(dist < radialDistance) {
			this.m_normalX = (x2 - x1) / dist ;
			this.m_normalY = (y2 - y1) / dist ;
			this.m_contactX = x1 + this.m_normalX * b1.m_radius ;
			this.m_contactY = y1 + this.m_normalY * b1.m_radius ;
			this.m_separation = radialDistance - dist ;
			this.mergeContact() ;
			return true ;
		}
		this.m_touched = false ;		
		return false ;
	},
	collideBallBox: function(b1, b2) {
		var edge = null ;
		var inside = b2.contains(b1) ;
		for(var i = 0 ; i < 4 ; i++) {
			edge = b2.m_edges[i] ;
			if(edge.intersectBall(b1, this, inside)) {
				this.mergeContact() ;
				return true ;
			}
		}
		this.m_touched = false ;
		return false ;
	},
	collideBoxBox: function(b1, b2) {
		var edge = null ;
		var inside = b2.contains(b1) ;
		for(var i = 0 ; i < 4 ; i++) {
			edge = b2.m_edges[i] ;
			if(edge.intersectBox(b1, this, inside)) {
				this.mergeContact() ;
				return true ;
			}
		}
		this.m_touched = false ;
		return false ;		
	},
	mergeContact: function() {
		this.m_touched = true ;				
		var c = null ;
		var cInsert = null ;
		for (var i = 0; i < this.m_numContacts ; ++i) {
			c = this.m_contacts[i] ;
			if(c.m_touched) continue ;
			if(c.m_x == this.m_contactX && c.m_y == this.m_contactY) {
				cInsert = c ;
				break ;
			}
			if(c.m_touched == false) {
				cInsert = c ;
				//break ;
			}
		}
		if(cInsert == null)
			return  ;
		//else
		//
		cInsert.m_x = this.m_contactX ;
		cInsert.m_y = this.m_contactY ;
		cInsert.m_normalX = this.m_normalX ;
		cInsert.m_normalY = this.m_normalY ;
		cInsert.m_separation = this.m_separation ;
		cInsert.m_touched = true ;
	},
		/*spriteConsole.write("mergeContact:" + 
					" cX: " + this.m_contactX +
					" cY:" + this.m_contactY + 
					" nX: " + this.m_normalX +
					" nY: " + this.m_normalY +
					" separation: " + this.m_separation) ;*/
	
	preStep: function(inv_dt)	{
		var k_allowedPenetration = 0.01;
		var k_biasFactor = 0.8;
		var b1 = this.m_b1 ;
		var b2 = this.m_b2 ;
		var c = null ;
		for (var i = 0; i < this.m_numContacts ; ++i) {
			c = this.m_contacts[i] ;			
			if(!c.m_touched)
				continue ;
			/*spriteConsole.write("processContact:" + 
					" cX: " + c.m_x +
					" cY:" + c.m_y + 
					" nX: " + c.m_normalX +
					" nY: " + c.m_normalY +
					" separation: " + c.m_separation) ;*/
			//Vec2 r1 = c->position - body1->position;
			var r1x = c.m_x - b1.m_x ;
			var r1y = c.m_y - b1.m_y ;			
			//Vec2 r2 = c->position - body2->position;
			var r2x = c.m_x - b2.m_x ;
			var r2y = c.m_y - b2.m_y ;				
			// Precompute normal mass, tangent mass, and bias.
			//dot = a.x * b.x + a.y * b.y;
			//float rn1 = Dot(r1, c->normal);
			var rn1 = r1x * c.m_normalX + r1y * c.m_normalY ;
			//float rn2 = Dot(r2, c->normal);
			var rn2 = r2x * c.m_normalX + r2y * c.m_normalY ;
			
			//float kNormal = body1->invMass + body2->invMass;
			var kNormal = b1.m_invMass + b2.m_invMass;
			
			//kNormal += body1->invI * (Dot(r1, r1) - rn1 * rn1) + body2->invI * (Dot(r2, r2) - rn2 * rn2);
			var r1Dot = r1x * r1x + r1y * r1y ;
			var r2Dot = r2x * r2x + r2y * r2y ;
			
			kNormal += b1.m_invI * (r1Dot - rn1 * rn1) + b2.m_invI * (r2Dot - rn2 * rn2);
			
			//c->massNormal = 1.0f / kNormal;
			c.m_massNormal = 1.0 / kNormal ;
	
			//skip friction for now...
			/*Vec2 tangent = Cross(c->normal, 1.0f);
			float rt1 = Dot(r1, tangent);
			float rt2 = Dot(r2, tangent);
			float kTangent = body1->invMass + body2->invMass;
			kTangent += body1->invI * (Dot(r1, r1) - rt1 * rt1) + body2->invI * (Dot(r2, r2) - rt2 * rt2);
			c->massTangent = 1.0f /  kTangent;*/
	
			//c->bias = -k_biasFactor * inv_dt * Min(0.0f, c->separation + k_allowedPenetration);
			c.m_bias = -k_biasFactor * inv_dt * Math.min(0.0, c.m_separation + k_allowedPenetration);
	
			// Apply normal + friction impulse
			//Vec2 P = c->Pn * c->normal + c->Pt * tangent;
			//f6:skipping friction
			var pX = c.m_Pn * c.m_normalX ;
			var pY = c.m_Pn * c.m_normalY ;
			//body1->velocity -= body1->invMass * P;
			b1.m_velX -= b1.m_invMass * pX ;
			b1.m_velY -= b1.m_invMass * pY ;
			
			//body1->angularVelocity -= body1->invI * Cross(r1, P);
			//cross = a.x * b.y - a.y * b.x;
			var r1Cross = r1x * pY - r1y * pX ;
			b1.m_angularVel -= b1.m_invI * r1Cross ;
	
			//body2->velocity += body2->invMass * P;
			b2.m_velX += b2.m_invMass * pX ;
			b2.m_velY += b2.m_invMass * pY ;
			
			//body2->angularVelocity += body2->invI * Cross(r2, P);
			var r2Cross = r2x * pY - r2y * pX ;
			b1.m_angularVel += b2.m_invI * r2Cross ;
	
			// Initialize bias impulse to zero.
			//c->Pnb = 0.0f;
			c.m_Pnb = 0.0 ;
		}
	},
	applyImpulse: function() {
		var b1 = this.m_b1 ;
		var b2 = this.m_b2;
		for (var i = 0; i < this.m_numContacts ; ++i) {
			var c = this.m_contacts[i] ;
			if(!c.m_touched)
				continue ;			
			//c->r1 = c->position - b1->position;
			c.m_r1X = c.m_x - b1.m_x ;
			c.m_r1Y = c.m_y - b1.m_y ;
			//if(isNaN(c.m_r1X)) alertOnce('c.m_r1X') ;
			//c->r2 = c->position - b2->position;
			c.m_r2X = c.m_x - b2.m_x ;
			c.m_r2Y = c.m_y - b2.m_y ;
			// Relative velocity at contact
			//Vec2 dv = b2->velocity + Cross(b2->angularVelocity, c->r2) - b1->velocity - Cross(b1->angularVelocity, c->r1);
			//scalar cross(vec,vec) = a.x * b.y - a.y * b.x;
			//vec cross(scalar,vec) = (-s * a.y, s * a.x);
			var a1CrossX = -b1.m_angularVel * c.m_r1Y ;
			var a1CrossY = b1.m_angularVel * c.m_r1X ;
			
			var a2CrossX = -b2.m_angularVel * c.m_r2Y ;
			var a2CrossY = b2.m_angularVel * c.m_r2X ;
			if(isNaN(a1CrossX)) alertOnce('a1CrossX') ;
			var dvX = b2.m_velX + a2CrossX - b1.m_velX - a1CrossX ;
			var dvY = b2.m_velY + a2CrossY - b1.m_velY - a1CrossY ;
			// Compute normal impulse
			//float vn = Dot(dv, c->normal);
			//dot = a.x * b.x + a.y * b.y;
			var vn = dvX * c.m_normalX + dvY * c.m_normalY ;
			//float dPn = c->massNormal * (-vn + c->bias);
			var dPn = c.m_massNormal * (-vn + c.m_bias);
			// Clamp the accumulated impulse
			//float Pn0 = c->Pn;
			var Pn0 = c.m_Pn;
			//c->Pn = Max(Pn0 + dPn, 0.0f);
			c.m_Pn = Math.max(Pn0 + dPn, 0.0) ;
			
			//dPn = c->Pn - Pn0;
			dPn = c.m_Pn - Pn0;
			// Apply contact impulse
			//Vec2 Pn = dPn * c->normal;
			var PnX = dPn * c.m_normalX ;
			var PnY = dPn * c.m_normalY ;
	
			//b1->velocity -= b1->invMass * Pn;
			b1.m_velX -= b1.m_invMass * PnX ;
			b1.m_velY -= b1.m_invMass * PnY ;
			//b1->angularVelocity -= b1->invI * Cross(c->r1, Pn);
			//scalar cross(vec,vec) = a.x * b.y - a.y * b.x;
			var p1Cross = c.m_r1X * PnY - c.m_r1Y * PnX ;
			b1.m_angularVel -= b1.m_invI * p1Cross ;	
			//b2->velocity += b2->invMass * Pn;
			b2.m_velX += b2.m_invMass * PnX ;
			b2.m_velY += b2.m_invMass * PnY ;
			//b2->angularVelocity += b2->invI * Cross(c->r2, Pn);
			var p2Cross = c.m_r2X * PnY - c.m_r2Y * PnX ;
			b2.m_angularVel += b2.m_invI * p2Cross ;	
			//f6:skip friction
			/*// Relative velocity at contact
			dv = b2->velocity + Cross(b2->angularVelocity, c->r2) - b1->velocity - Cross(b1->angularVelocity, c->r1);
	
			// Compute friction impulse
			float maxPt = friction * c->Pn;
	
			Vec2 tangent = Cross(c->normal, 1.0f);
			float vt = Dot(dv, tangent);
			float dPt = c->massTangent * (-vt);
	
			// Clamp friction
			float oldTangentImpulse = c->Pt;
			c->Pt = Clamp(oldTangentImpulse + dPt, -maxPt, maxPt);
			dPt = c->Pt - oldTangentImpulse;
	
			// Apply contact impulse
			Vec2 Pt = dPt * tangent;
	
			b1->velocity -= b1->invMass * Pt;
			b1->angularVelocity -= b1->invI * Cross(c->r1, Pt);
	
			b2->velocity += b2->invMass * Pt;
			b2->angularVelocity += b2->invI * Cross(c->r2, Pt);*/
		}
	},
	postStep: function() {
		this.m_touched = false ;
		for (var i = 0; i < this.m_numContacts ; ++i) {
			this.m_contacts[i].m_touched = false ;
		}
	}
}
Wiggles.Contact = Class.create();
Wiggles.Contact.prototype = {
	initialize: function() {
		//Contact() : Pn(0.0f), Pt(0.0f), Pnb(0.0f) {}
	
		//Vec2 position;
		this.m_x = 0 ;
		this.m_y = 0 ;
		//Vec2 normal;
		this.m_normalX = 0 ;
		this.m_normalY = 0 ;
		//Vec2 r1, r2;
		this.m_r1X = 0 ;
		this.m_r1Y = 0 ;
		this.m_r2X = 0 ;
		this.m_r2Y = 0 ;
		//float separation;
		this.m_separation = 0 ;
		//float Pn;	// accumulated normal impulse
		this.m_Pn = 0 ;
		//float Pt;	// accumulated tangent impulse
		//float Pnb;	// accumulated normal impulse for position bias
		this.m_Pnb = 0 ;
		//float massNormal, massTangent;
		this.m_massNormal = 0 ;
		//float bias;
		this.m_bias = 0 ;
		//f6:newstuff
		this.m_touched = false ;
	}
}
////////////////
Wiggles.Edge = Class.create();
Wiggles.Edge.prototype = {
	initialize: function() {
	},
	setPoints: function(x1, y1, x2, y2) {
		this.m_x1 = x1 ;
		this.m_y1 = y1 ;		
		this.m_x2 = x2 ;
		this.m_y2 = y2 ;
		//
		var diffX = x2 - x1 ;
		var diffY = y2 - y1 ;
		this.m_length = Math.sqrt((diffX*diffX)+(diffY*diffY)) ;
		
		this.m_dirX = diffX / this.m_length ;
		this.m_dirY = diffY / this.m_length ;
	
		var nx = -this.m_dirY ;
		var ny = this.m_dirX ;		
		
		this.m_normalX = nx ;
		this.m_normalY = ny ;
		
		/*alertOnce("Edge:setPoints:" + 
			" dirX: " + this.m_dirX +
			" dirY: " + this.m_dirY +
			" nX: " + this.m_normalX +
			" nY: " + this.m_normalY +
			" length: " + this.m_length) ;*/
	},
	closestPoint: function(x, y, collision) {
		var px = x - this.m_x1 ;
		var py = y - this.m_y1
		// distance along v0-v1 of closest point on line to point.
		var dist = px * this.m_dirX + py * this.m_dirY ;
		if (dist < 0)
			dist = 0.0 ;
		else if (dist > this.m_length)
			dist = this.m_length ;
		
		var closestX = this.m_x1 + dist * this.m_dirX ;
		var closestY = this.m_y1 + dist * this.m_dirY ;
		var diffX = x - closestX ;
		var diffY = y - closestY ;
		collision.m_contactX = closestX ;
		collision.m_contactY = closestY ;
		return Math.sqrt((diffX*diffX)+(diffY*diffY)) ;	  
		//return Math.abs(Math.sqrt((diffX*diffX)+(diffY*diffY))) ;
	},
	intersectLine: function(a2x, a2y, b2x, b2y) {
		var a1x = this.m_x1 ;
		var a1y = this.m_y1 ;
		var b1x = this.m_x2 ;
		var b1y = this.m_y2 ;
				
		var A = a2x - a1x;
		var B = b2x - a2x;
		var C = b1x - a1x;
		
		var D = a1y - a2y;
		var E = b1y - a1y;
		var F = b2y - a2y;
		
		var s, t;
		
		s = (A + B*D/F) / (C - B * E / F);
		if ( (s < 0.0) || (s > 1.0) )
			return false;
		
		t = (D + s * E ) / F;
		if ( (t < 0.0) || (t > 1.0) )
			return false;
		
		return true;		
	},
	intersectBall: function(b1, collision, inside) {
		var collide = false ;
		var x = b1.m_x ; var y = b1.m_y ; var radius = b1.m_radius ;
		var separation = 0 ;
		var cX ; var cY
		var dist = this.closestPoint(x, y, collision) ;
		
		var dx0 = x - collision.m_contactX ;
		var dy0 = y - collision.m_contactY ;
		var side = (dx0 * this.m_normalX + dy0 * this.m_normalY > 0) ? 1 : -1 ;
		if(inside) {
			var x1 = b1.m_x + (-b1.m_velX * 1000) ;
			var y1 = b1.m_y + (-b1.m_velY * 1000) ;
			var x2 = b1.m_x ;
			var y2 = b1.m_y ;
			collide = this.intersectLine(x1, y1, x2, y2) ;
			//separation = -(dist + radius) ;
			//separation = dist - radius ;
		}
		else {
			//separation = dist - radius ;
			collide = side == 1 && dist < radius ;
		}
		//if (dist < radius || inside) {			
		if (collide) {			
			// penetration
			collision.m_normalX = this.m_normalX ;
			collision.m_normalY = this.m_normalY ;
			collision.m_separation = dist - radius ;
				
			/*spriteConsole.write("intersectBallEdge:" + 
					" inside: " + inside +  
					" side: " + side +
					" dirX: " + this.m_dirX +
					" dirY: " + this.m_dirY +
					" cX: " + collision.m_contactX +
					" cY:" + collision.m_contactY + 
					" nX: " + collision.m_normalX +
					" nY: " + collision.m_normalY +
					" separation: " + collision.m_separation) ;*/
			  return true ;			
		}
		return false ;
	},
	intersectBox: function(b1, collision, inside) {
		var collide = false ;
		var x = b1.m_x ; var y = b1.m_y ; var radius = b1.m_halfWidth ;
		var separation = 0 ;
		var dist = this.closestPoint(x, y, collision) ;
		//
		
		var dx0 = x - collision.m_contactX ;
		var dy0 = y - collision.m_contactY ;
		var side = (dx0 * this.m_normalX + dy0 * this.m_normalY > 0) ? 1 : -1 ;
		if(inside) {
			var x1 = b1.m_x + (-b1.m_velX * 1000) ;
			var y1 = b1.m_y + (-b1.m_velY * 1000) ;
			var x2 = b1.m_x ;
			var y2 = b1.m_y ;
			collide = this.intersectLine(x1, y1, x2, y2) ;
			//separation = -(dist + radius) ;
			//separation = dist - radius ;
		}
		else {
			//separation = dist - radius ;
			collide = side == 1 && dist < radius ;
		}
		//if (dist < radius || inside) {			
		if (collide) {			
			// penetration
			collision.m_normalX = this.m_normalX ;
			collision.m_normalY = this.m_normalY ;
			collision.m_separation = dist - radius ;
				
			/*spriteConsole.write("intersectBallEdge:" + 
					" inside: " + inside +  
					" side: " + side +
					" dirX: " + this.m_dirX +
					" dirY: " + this.m_dirY +
					" cX: " + collision.m_contactX +
					" cY:" + collision.m_contactY + 
					" nX: " + collision.m_normalX +
					" nY: " + collision.m_normalY +
					" separation: " + collision.m_separation) ;*/
			  return true ;			
		}
		return false ;
	}	
	
}
////////////////
Wiggles.Beacon = Class.create();
Wiggles.Beacon.prototype = {
	initialize: function(sprite, type) {
		this.m_sprite = sprite ;
		this.m_type = type ;
		this.m_x = 0 ;
		this.m_y = 0 ;
	},
	setPos: function(x, y) {
		this.m_x = x ;
		this.m_y = y ;		
	}
}
////////////////
Wiggles.Body = Class.create();
Wiggles.Body.prototype = {
	initialize: function() {
		this.m_sprite = null ;
		this.m_x = 0 ;
		this.m_y = 0 ;
		this.m_velX = 0 ;
		this.m_velY = 0 ;
		this.m_forceX = 0 ;
		this.m_forceY = 0 ;
		//
		this.m_rotation = 0 ;
		this.m_angularVel = 0 ;
		this.m_biasedVelX = 0 ;
		this.m_biasedVelY = 0 ;
		this.m_biasedAngularVel = 0 ;
		this.m_torque = 0 ;
	
		this.m_dimX = 0 ;
		this.m_dimY = 0 ;
		//
		this.m_halfWidth = 0 ;
		this.m_halfHeight = 0 ;
		this.m_minX = 0 ;
		this.m_minY = 0 ;
		this.m_maxX = 0 ;
		this.m_maxY = 0 ;					
		//
		this.m_friction = 0 ;
		this.m_mass = 0 ;
		this.m_invMass = 0 ;
		this.m_I = 0 ;
		this.m_invI = 0 ;		
	},
	setPos: function(x, y) {
		this.m_x = x ;
		this.m_y = y ;
		this.validate() ;
	},
	setMinMax: function(minX, minY, maxX, maxY, mass) {
		this.m_minX = minX ;
		this.m_minY = minY ;
		this.m_maxX = maxX ;
		this.m_maxY = maxY ;
		//		
		this.m_dimX = maxX - minX ;
		this.m_dimY = maxY - minY ;
		this.setMass(mass) ;
		//
		this.m_halfWidth = this.m_dimX / 2 ;
		this.m_halfHeight = this.m_dimY / 2 ;				
		this.m_x = minX +  this.m_halfWidth ;
		this.m_y = minY + this.m_halfHeight ;
		//hmm...
		this.validate() ;
	},
	setSize: function(width, height, mass) {
		this.m_dimX = width ;
		this.m_dimY = height ;
		this.setMass(mass) ;
		this.m_halfWidth = this.m_dimX / 2 ;
		this.m_halfHeight = this.m_dimY / 2 ;		
	},
	validate: function() {
		this._validate() ;
	},
	_validate: function() {
		this.m_minX = this.m_x - this.m_halfWidth ;
		this.m_minY = this.m_y - this.m_halfHeight ;
		this.m_maxX = this.m_x + this.m_halfWidth ;
		this.m_maxY = this.m_y + this.m_halfHeight ;				
	},
	setMass: function(mass) {
		this.m_mass = mass ;
		
		if (mass < Number.MAX_VALUE)
		{
			this.m_invMass = 1.0 / mass;
			this.m_I = mass * (this.m_dimX * this.m_dimX + this.m_dimY * this.m_dimY) / 12.0 ;
			this.m_invI = 1.0 / this.m_I ;
		}
		else
		{
			this.m_invMass = 0.0 ;
			this.m_I = Number.MAX_VALUE ;
			this.m_invI = 0.0 ;
		}		
	},
	addForce: function(x, y) {
		this.m_forceX += x ;
		this.m_forceY += y ;
	},
	step: function() {
		this.m_sprite._setPos(this.m_x, this.m_y) ;
	},
	intersects: function(body) {
		if(this.m_minX > body.m_maxX)  return false;
		if (this.m_minY > body.m_maxY)  return false;
		if (body.m_minX > this.m_maxX)  return false;
		if (body.m_minY > this.m_maxY)  return false;		
		return true;
	},
	contains: function(body) {
		if(body.m_minX < this.m_minX)  return false;
		if (body.m_minY < this.m_minY)  return false;
		if (body.m_maxX > this.m_maxX)  return false;
		if (body.m_maxY > this.m_maxY)  return false;		
		return true;
	},
	overlaps: function(body) {
		return this.intersects(body) || this.contains(body) || body.contains(this) ;
	}
}
/////
Wiggles.BallBody = Class.create();
Wiggles.BallBody.prototype = Object.extend(new Wiggles.Body(), {
	initialize: function(sprite) {
		this.m_sprite = sprite ;
		this.m_shapeType = 'ball' ;
	},
	validate: function() {
		this._validate() ;
		this.m_radius = this.m_halfWidth ;
	}
}) ;
/////
Wiggles.BoxBody = Class.create();
Wiggles.BoxBody.prototype = Object.extend(new Wiggles.Body(), {
	initialize: function(sprite) {
		this.m_sprite = sprite ;		
		this.m_shapeType = 'box' ;
		this.m_edges = new Array(4) ;
		for(var i = 0 ; i < 4 ; i++) 
			this.m_edges[i] = new Wiggles.Edge() ;
	},
	validate: function() {
		this._validate() ;
		this.m_edges[0].setPoints(this.m_minX, this.m_minY, this.m_minX, this.m_maxY) ;
		this.m_edges[1].setPoints(this.m_minX, this.m_maxY, this.m_maxX, this.m_maxY) ;
		this.m_edges[2].setPoints(this.m_maxX, this.m_maxY, this.m_maxX, this.m_minY) ;
		this.m_edges[3].setPoints(this.m_maxX, this.m_minY, this.m_minX, this.m_minY) ;
		/*this.m_edges[0].setPoints(this.m_minX, this.m_maxY, this.m_minX, this.m_minY) ;
		this.m_edges[1].setPoints(this.m_maxX, this.m_maxY, this.m_minX, this.m_maxY) ;
		this.m_edges[2].setPoints(this.m_maxX, this.m_minY, this.m_maxX, this.m_maxY) ;
		this.m_edges[3].setPoints(this.m_minX, this.m_minY, this.m_maxX, this.m_minY) ;*/
	}
}) ;
////////////////
Wiggles.Sprite = Class.create();
Wiggles.Sprite.prototype = {
	initialize: function() {
		this.m_beacon = null ;
		this.m_heading = 0 ;
		this.m_x = 0 ;
		this.m_y = 0 ;
		this.m_z = 10 ;
		//
		this.m_fromX = 0 ;
		this.m_fromY = 0 ;
		this.m_toX = 0 ;
		this.m_toY = 0 ;	
		//
		this.m_halfWidth = 0 ;
		this.m_halfHeight = 0 ;
		this.m_minX = 0 ;
		this.m_minY = 0 ;
		this.m_maxX = 0 ;
		this.m_maxY = 0 ;		
		//
		this.m_energy = 5 ;
		//
		this.octant = (Math.PI*2)/8 ;				
	},
	createSprite: function(elementId, imgName) {
		this.element = document.createElement("img") ;
		this.element.id = elementId ;
		//this.element.src = "images/" + imgName + ".gif" ;
		this.setImageSrc(imgName) ;
		this.element.style.position = 'absolute' ;
		this.element.style.display = 'none' ;
		this.element.style.zindex = this.m_z ;
		document.body.appendChild(this.element) ;
		//
		/*Neat idea but ... not reliable ... must wait for image to load.
		this.m_halfWidth = this.element.width / 2 ;
		this.m_halfHeight = this.element.height / 2 ;*/
	},
	setImageSrc: function(imgName) {
		this.element.src = "images/" + imgName + ".gif" ;
	},
	moveTo: function(x, y) {
		/*if(x < 0 || y < 0 || x > 1000 || y > 1000 || isNaN(x) || isNaN(y))
			alertOnce('Sprite::moveTo: OutOfBounds: x:' + x + 'y:' + y) ;*/
		this.m_fromX = this.m_x ;
		this.m_fromY = this.m_y ;
		this.m_toX = x ;
		this.m_toY = y ;				
	},
	setPos: function(x, y) {
		this._setPos(x,y) ;
		if(this.m_body != null)
			this.m_body.setPos(x, y) ;
	},
	_setPos: function(x, y) {
		/*if(x < 0 || y < 0 || x > 760 || y > 760 || isNaN(x) || isNaN(y)) {
			alertOnce('Sprite::setPos: OutOfBounds: x:' + x + 'y:' + y) ;
			return ;
		}*/
		if(isNaN(x) || isNaN(y)) {
			alertOnce('Sprite::setPos: NaN: x:' + x + 'y:' + y) ;
			return ;
		}
		this.m_x = x ;
		this.m_y = y ;
		//
		this.validate() ;
	},
	validate: function() {
		this._validate() ;
	},
	_validate: function() {
		this.m_minX = this.m_x - this.m_halfWidth ;
		this.m_minY = this.m_y - this.m_halfHeight ;
		this.m_maxX = this.m_x + this.m_halfWidth ;
		this.m_maxY = this.m_y + this.m_halfHeight ;
		//
		if(this.m_beacon != null)
			this.m_beacon.setPos(this.m_x, this.m_y) ;
		//
		this.element.style.left = this.m_minX.toFixed() + "px" ;
		this.element.style.top = this.m_minY.toFixed() + "px" ;						
	},	
	setOrigin: function(x, y) {
		this.setPos(x, y) ;
		this.m_fromX = x ;
		this.m_fromY = y ;		
		this.m_toX = x ;
		this.m_toY = y ;		
	},
	setZ: function(z) {
		this.m_z = z ;
		this.element.style.zindex = z ;
	},
	getZ: function() {
		return this.m_z ;
	},
	getX: function() {
		return this.m_x ;
	},
	getY: function() {
		return this.m_y ;
	},
	setSize: function(width, height) {
		this.m_halfWidth = width / 2 ;
		this.m_halfHeight = height / 2 ;
	},
	intersects: function(sprite) {
		if(this.m_minX > sprite.m_maxX)  return false;
		if(this.m_minY > sprite.m_maxY)  return false;
		if(sprite.m_minX > this.m_maxX)  return false;
		if(sprite.m_minY > this.m_maxY)  return false;		
		return true;
	},
	show: function() {
		this.element.style.display = 'block' ;
	},
	hide: function() {
		this.element.style.display = 'none' ;
	},
	materializeAt: function(x, y) {
		//this.setPos(x, y) ;
		this.setOrigin(x, y) ;
		this.show() ;
	},
	turnLeft: function(angleDelta) {
		var angle = this.m_heading - angleDelta ;
		if(angle < 0)
			this.m_heading = 360+angle ;
		else
			this.m_heading = angle ;
	},
	turnRight: function(angleDelta) {
		var angle = this.m_heading + angleDelta ;
		if(angle > 359)
			this.m_heading = angle-360 ;
		else
			this.m_heading = angle ;
	},
	project: function(distance) {
		var px = this.m_x+(distance*(Math.cos(this.m_heading*degRads))) ;
		var py = this.m_y+(distance*(Math.sin(this.m_heading*degRads))) ;
		//alertOnce('Sprite::project: x:' + px + ' y:' + py) ;		
		this.moveTo(px, py) ;
	},
	atGoal: function()	{
		var bDistance = distance2d(this.m_x, this.m_y, this.m_toX, this.m_toY) ;
		return bDistance < 5 ;
	},
	step: function() {
	},
    move: function() {
    }
}
//
Wiggles.WiggleSeg = Class.create();
Wiggles.WiggleSeg.prototype = Object.extend(new Wiggles.Sprite(), {
	initialize: function() {
		this.setSize(22,22) ;
		this.m_next = null ;
		this.m_trackNdx = 0 ;
		this.m_trackMax = 132 ;
		this.m_onTrack = false ;
		this.m_track = null ;
	},
	putOnTrack: function(track) {
		this.m_track = track ;
		this.m_onTrack = true ;
		this.materializeAt(track[this.m_trackNdx*2], track[this.m_trackNdx*2+1]) ;
	},
	step: function() {
		this.move() ;
	},
	move: function() {
		this._move() ;
	},
	_move: function() {
		this.setPos(this.m_track[this.m_trackNdx*2], this.m_track[this.m_trackNdx*2+1]) ;	
		this.m_trackNdx += 1 ;
		if(this.m_trackNdx >= this.m_trackMax)
			this.m_trackNdx = 0 ;
		if(this.m_next == null) return ; //early out.
		//else
		if(this.m_next.m_onTrack == true)//another early out.
		{
			this.m_next.step();
			return ;
		}
		//else if there is a next wig and not on the track yet...
		if(this.m_trackNdx > 16) {
		  	this.m_next.putOnTrack(this.m_track) ;
		}
		return true ;
	}	
}) ;
//
Wiggles.WiggleTail = Class.create();
Wiggles.WiggleTail.prototype = Object.extend(new Wiggles.WiggleSeg(), {
	initialize: function(type) {
		this.m_type = type ;
		this.m_name = spriteEngine.genId(type) ;				
		this.createSprite(this.m_name, type + 'tail') ;
	}
}) ;
////////////////
var wigglySteering = new Array(
    0, -1,
    1, -1,
    1, 0,
	//
    -1, -1,
    0, -1,
    1, -1,
	//
    -1, 0,
    -1, -1,
    0, -1,
	//
    -1, 1,
    -1, 0,
    -1, -1,
	//
    0, 1,
    -1, 1,
    -1, 0,
	//
    1, 1,
    0, 1,
    -1, 1,
	//
    1, 0,
    1, 1,
    0, 1,
	//
    1, -1,
    1, 0,
    1, 1
) ;
Wiggles.WiggleHead = Class.create();
Wiggles.WiggleHead.prototype = Object.extend(new Wiggles.WiggleSeg(), {
	initialize: function() {
		this.face = 'happy' ;
	},
	happyFace: function() {
		this.face = 'happy' ;
		this.setImageSrc(this.m_type + 'head') ;
	},	
	munchyFace: function() {
		this.face = 'munchy' ;
		this.setImageSrc(this.m_type + 'munch') ;
	},
    openMouth: function() {
        this.munchyFace() ;
    },
    closeMouth: function() {
        this.happyFace() ;
    }
}) ;
//
Wiggles.Wiggle = Class.create();
//Wiggles.Wiggle.prototype = Object.extend(new Wiggles.WiggleSeg(), {
Wiggles.Wiggle.prototype = Object.extend(new Wiggles.WiggleHead(), {																 
	initialize: function(type) {
		this.m_type = type ;
		this.m_name = spriteEngine.genId(type) ;
		this.m_lengthMax = 6 ;		
		this.m_segs = new Array(this.m_lengthMax) ;
		this.preLoad() ;
		this.createSprite(this.m_name, type + 'head') ;
		this.sensorRange = 128 ;
		this.m_wheel = 0 ;
		this.m_state = 'wanderer' ;
		//
		this.m_track = new Array(this.m_trackMax*2) ;
		this.m_length = 1 ;
		this.m_butt = null ;
		//
		this.m_considerMax = 10 ;
		this.m_considerTimer = this.m_considerMax ;
		this.m_focus = null ;
		//
		this.m_munchTimer = 10 ;
		//
		this.grow() ;
		this.grow() ;
		this.grow() ;
		this.grow() ;
		this.grow() ;
		//
		spriteEngine.addActor(this) ;
	},
	preLoad: function() {
		for(var i = this.m_lengthMax ; i > 0 ; i--) {
			this.m_segs[i] = new Wiggles.WiggleTail(this.m_type) ;
		}
		this.m_segs[0] = this ;
	},
    grow: function() {
        var length = this.m_length + 1 ;
        if(length > this.m_lengthMax) return ;
        //else
        this.m_length = length ;
        var wasButt = this.m_butt ;
        //this.m_butt = new Wiggles.WiggleTail(this.m_type) ;
		this.m_butt = this.m_segs[length] ;
		this.m_butt.setZ(this.getZ() - length) ;
		if(wasButt != null) 
	        wasButt.m_next = this.m_butt ;
		else
			this.m_next = this.m_butt ;
    },
	changeState: function(state) {
		this.m_state = state ;
	},
	microLeftTurn: function() {
		var ph = this.m_wheel - 1 ;
		if(ph < 0 ) ph = 0 ;
			this.m_wheel = ph ;
	},
	microRightTurn: function() {
		var ph = this.m_wheel + 1 ;
		if(ph > 2) ph = 2 ;
			this.m_wheel = ph ;
	},
	step: function() {
		switch(this.m_state) {
			case 'wanderer' :
				this.wander() ;
				break ;
			case 'hunter' :
				this.hunt() ;
				break ;
			case 'eater' :
				this.eat() ;
				break ;
			case 'kicker' :
				this.kick() ;
				break ;
		}
	},
	wander: function() {
		//alertOnce('Wandering') ;
		if(this.atGoal()) {
			var pt = Math.floor(Math.random()*3) ;
			var pd = Math.floor(Math.random()*45) ;
			switch(pt){
				case 0 :
					this.turnLeft(pd) ;
					break ;
				case 1 :
					break ;
				case 2 :
					this.turnRight(pd) ;
			}
			//alertOnce(this.sensorRange) ;			
			this.project(this.sensorRange) ;
		}
		this.move() ;
		this.consider() ;
	},
	hunt: function() {
		//alertOnce('Hunting') ;
		if(this.intersects(this.m_focus)) { this.changeState('eater') ; }
		this.move() ;
		this.consider() ;
	},	
	eat: function() {
		//alertOnce('Eating') ;
		if(this.m_focus.isMunched()) {
			this.closeMouth() ; //needed if another sprite eats it before you!
	        this.m_energy = this.m_energy + this.m_focus.m_energy ;
			this.changeState('wanderer') ; 
			return ;
		}
		//else
		this.munch() ;
		//this.consider() ;
	},
    munch: function() {
		if(this.m_munchTimer > 0) {
			this.m_munchTimer -=1 ;
			return ;
		}
		else
			this.m_munchTimer = 10 ;
			
		if(this.face != 'munchy')
			this.openMouth() ;
		else {
			this.closeMouth() ;
			this.m_focus.receiveMunch() ;
		}
	},
	kick: function() {
		//alertOnce('Hunting') ;
		this.moveTo(this.m_focus.m_x, this.m_focus.m_y) ; //fixme: add--> follow(sprite)
		if(this.intersects(this.m_focus)) {
			//alertOnce('Boing') ;
			this.m_focus.receiveKick(this.m_heading, this.sensorRange) ;			
			//this.changeState('wanderer') ; 
			//return ;
			//this.moveTo(this.m_focus.m_x, this.m_focus.m_y) ; //fixme: add--> follow(sprite)
		}
		else if(distance2d(this.m_x, this.m_y, this.m_focus.m_x, this.m_focus.m_y) > this.sensorRange)
			this.changeState('wanderer') ; 
		//else
		this.move() ;
		//this.consider() ;
	},		
	move: function() {
		var x = this.getX() ;
		var y = this.getY() ;		
		var steeringNdx = 0 ;
		var bCompass = Math.PI+(Math.atan2(y - this.m_toY, x - this.m_toX)) ;
		if(bCompass != 0)
			steeringNdx = 8 - Math.round(bCompass/this.octant) ;
		if(steeringNdx < 0)
			steeringNdx = 0 ;			
		else
		if(steeringNdx > 7)
			steeringNdx = 7 ;
		//
		var pd = Math.floor(Math.random()*3) ;
		switch(pd){
		  case 0 :
			this.microLeftTurn() ;
			break ;
		  case 1 :
			break ;
		  case 2 :
			this.microRightTurn() ;
		  }		
		//
		steeringNdx = steeringNdx*6 ;
		steeringNdx = steeringNdx + (this.m_wheel * 2) ;
		//		
		var deltaX = wigglySteering[steeringNdx] ;
		var deltaY = wigglySteering[steeringNdx + 1] ;
		if(isNaN(deltaX) || isNaN(deltaY))
			alertOnce('Delta: OutOfBounds: x:' + x + 'y:' + y) ;		

		this.tryMove(deltaX, deltaY) ;
	},
	tryMove: function(deltaX, deltaY) {
		var nextX = 0 ;
		var nextY = 0 ;
		var needTurn = false ;
		if(this.m_minX < worldMinX) { deltaX = -this.m_minX ; needTurn = true ;	}
		else
		if(this.m_maxX > worldMaxX) { deltaX = worldMaxX - this.m_maxX ; needTurn = true ; }
		if(this.m_minY < worldMinY) { deltaY = worldMinY - this.m_minY ; needTurn = true ;	}
		else
		if(this.m_maxY > worldMaxY) { deltaY = worldMaxY - this.m_maxY ; needTurn = true ; }		
		if(needTurn) {
			this.turnRight(45) ;
			this.project(this.sensorRange) ;
		}
		nextX = this.getX() + deltaX ;
		nextY = this.getY() + deltaY ;					
		
		this.m_track[this.m_trackNdx*2] = nextX ;
		this.m_track[this.m_trackNdx*2+1] = nextY ;
		this._move() ;
	},
	consider: function() {
		if(this.m_considerTimer > 0) {
			this.m_considerTimer -= 1 ;
			return ;
		}
		//else
		this.m_considerTimer = this.m_considerMax ;
		var beacons = spriteEngine.query(this.m_x, this.m_y, this.sensorRange) ;
		
		switch(this.m_state) {
			case 'wanderer' :
				if(!this.considerEating(beacons))
					this.considerKicking(beacons) ;
				break ;
			case 'hunter' :
				break ;
			case 'eater' :
				break ;
			case 'kicker' :
				break ;				
		}
		//cleanup
		if(beacons != null)
			delete beacons ;
	},
	considerEating: function(beacons) {
		if(beacons == null) return false ;
		//else
		var apple = null ;
		for(var i = 0 ; i < beacons.length ; i++) {
			if(beacons[i].m_type == 'apple') {
				apple = beacons[i].m_sprite ;
				break ;
			}
		}
		//
		if(apple == null) return false ;
		//else
		this.m_focus = apple ;
		this.moveTo(apple.m_x, apple.m_y) ;
		this.changeState('hunter') ;
		return true ;
	},
	considerKicking: function(beacons) {
		if(beacons == null) return false ;
		//else
		var ball = null ;
		for(var i = 0 ; i < beacons.length ; i++) {
			if(beacons[i].m_type == 'ball') {
				ball = beacons[i].m_sprite ;
				break ;
			}
		}
		//
		if(ball == null) return false ;
		//else
		this.m_focus = ball ;
		this.moveTo(ball.m_x, ball.m_y) ;
		this.changeState('kicker') ;
		return true ;
	}	
}) ;
//
Wiggles.Apple = Class.create();
Wiggles.Apple.prototype = Object.extend(new Wiggles.Sprite(), {
	initialize: function() {
		this.setSize(22,22) ;		
		this.m_energy = 5 ;
		this.m_type = 'apple' ;
		this.m_name = spriteEngine.genId(this.m_type) ;				
		this.createSprite(this.m_name, this.m_type + this.m_energy) ;
		//
		this.m_beacon = new Wiggles.Beacon(this, this.m_type) ;
		spriteEngine.addBeacon(this.m_beacon) ;
	},
	receiveMunch: function() {
		this.m_energy -= 1 ;
		if(this.m_energy <= 0) {
			this.hide() ;
			spriteEngine.removeBeacon(this.m_beacon) ;
			setTimeout('spawnApple()', 10000) ;
			return ;			
		}
		//else
		this.setImageSrc(this.m_type + this.m_energy) ;
	},
	isMunched: function() { 
		return this.m_energy == 0 ; 
	}
	
}) ;
//
Wiggles.Ball = Class.create();
Wiggles.Ball.prototype = Object.extend(new Wiggles.Sprite(), {
	initialize: function() {
		this.setSize(22,22) ;		
		this.m_type = 'ball' ;
		this.m_name = spriteEngine.genId(this.m_type) ;				
		this.createSprite(this.m_name, this.m_type) ;
		//
		this.m_beacon = new Wiggles.Beacon(this, this.m_type) ;
		spriteEngine.addBeacon(this.m_beacon) ;
		//
		this.m_body = new Wiggles.BallBody(this) ;
		this.m_body.setSize(22,22,50) ;
		spriteEngine.addBody(this.m_body) ;
	},
	receiveKick: function(angle, distance) {
		var px = distance*(Math.cos(angle*degRads)) ;
		var py = distance*(Math.sin(angle*degRads)) ;		
		this.m_body.addForce(px, py) ;
		//this.m_body.addForce(-100,0) ;
	}
}) ;
//
Wiggles.Box = Class.create();
Wiggles.Box.prototype = Object.extend(new Wiggles.Sprite(), {
	initialize: function() {
		this.setSize(64,64) ;		
		this.m_type = 'box' ;
		this.m_name = spriteEngine.genId(this.m_type) ;				
		this.createSprite(this.m_name, this.m_type) ;
		//
		/*this.m_beacon = new Wiggles.Beacon(this, this.m_type) ;
		spriteEngine.addBeacon(this.m_beacon) ;*/
		//
		this.m_body = new Wiggles.BoxBody(this) ;
		this.m_body.setSize(64,64,50) ;
		//this.m_body.setSize(64,64,Number.MAX_VALUE) ;
		//this.m_body.setMinMax(this.m_minX,this.m_minY, this.m_maxX, this.m_maxY, Number.MAX_VALUE) ;
		spriteEngine.addBody(this.m_body) ;
	},
	/*validate: function() {
		this._validate() ;
		this.m_body.setMinMax(this.m_minX,this.m_minY, this.m_maxX, this.m_maxY, Number.MAX_VALUE) ;
	},*/
	receiveKick: function(angle, distance) {
		var px = distance*(Math.cos(angle*degRads)) ;
		var py = distance*(Math.sin(angle*degRads)) ;		
		this.m_body.addForce(px, py) ;
		//this.m_body.addForce(-100,0) ;
	}
}) ;
/////////////////////////
var spriteEngineInterval = null ;
//
function spriteEngine_step() {
	spriteEngine.step() ;
}
//Walls
function spawnWalls() {
	var minX = worldMinX ; var minY = worldMinY ; var maxX = worldMaxX ; var maxY = worldMaxY ;
	var thickness = 200 ;
	//North Wall
	var northWall = new Wiggles.BoxBody(null) ;
	northWall.setMinMax(minX-thickness, minY-thickness, maxX+thickness, minY, Number.MAX_VALUE) ;
	spriteEngine.addBody(northWall) ;	
	//East Wall
	var eastWall = new Wiggles.BoxBody(null) ;
	eastWall.setMinMax(maxX, minY-thickness, maxX+thickness, maxY+thickness, Number.MAX_VALUE) ;
	spriteEngine.addBody(eastWall) ;	
	//South Wall
	var southWall = new Wiggles.BoxBody(null) ;	
	southWall.setMinMax(minX-thickness, maxY, maxX+thickness, maxY+thickness, Number.MAX_VALUE) ;
	spriteEngine.addBody(southWall) ;
	//West Wall
	var westWall = new Wiggles.BoxBody(null) ;
	westWall.setMinMax(minX-thickness, minY-thickness, minX, maxY+thickness, Number.MAX_VALUE) ;
	spriteEngine.addBody(westWall) ;		
}
//Balls
function spawnBall() {
	var ball = new Wiggles.Ball() ;
	ball.materializeAt(Math.random() * (worldMaxX - 100), Math.random() * (worldMaxY - 100)) ;		
}
function spawnBalls() {
	for(var i = 0 ; i < 10 ; ++i) {
		spawnBall() ;
	}
}
//Boxes
function spawnBox() {
	var box = new Wiggles.Box() ;
	box.materializeAt(Math.random() * (worldMaxX - 100), Math.random() * (worldMaxY - 100)) ;
}
function spawnBoxes() {
	var total = 10 ;
	for(var i = 0 ; i < total ; ++i) {
		spawnBox() ;
	}
}
//
function materializeRandomFromCenter(sprite) {
	var halfMaxX = worldMaxX / 2 ;
	var halfMaxY = worldMaxX / 2 ;
	var diameter = 400 ;
	var radius = diameter / 2 ;
	sprite.materializeAt( (halfMaxX - radius) + (Math.random() * diameter), (halfMaxY - radius) + (Math.random() * diameter)) ;		
}
//Apples
function spawnApple() {
	var apple = new Wiggles.Apple() ;
	materializeRandomFromCenter(apple) ;
}
//Wiggles
function spawnWiggle(color) {
	var wiggle = new Wiggles.Wiggle(color) ;
	materializeRandomFromCenter(wiggle) ;		
}
function spawnWiggles() {
	spawnWiggle("green") ;
	spawnWiggle("blue") ;
	spawnWiggle("pink") ;
}
//
function document_onload() {
	//spriteConsole = new Wiggles.Console() ;
	worldMaxX = document.documentElement.clientWidth - 1 ;
	worldMaxY = document.documentElement.clientHeight - 1 ;	
	//
	spawnWalls() ;
	//fixme:boxes need to be spawned before balls.  why?
	//spawnBoxes() ;
	spawnBalls() ;
	spawnApple() ;
	spawnWiggles() ;
	var intervalTime = 10 ;
	//var intervalTime = 1 ;
	spriteEngineInterval = window.setInterval("spriteEngine_step() ;", intervalTime) ;
}
function addObserver(el, evname, func) {
	if (el.attachEvent) { // IE
		el.attachEvent("on" + evname, func);
	} else if (el.addEventListener) { // Gecko / W3C
		el.addEventListener(evname, func, true);
	} else {
		el["on" + evname] = func;
	}
};
addObserver(window, "load", document_onload) ;

