// World class for Verlet physics engine
// by Andreas Blixt <andreasblixt@msn.com> 2007
// Free for use. Please include this header.

World = function (gravityX, gravityY) {
	// Set horizontal gravity if one was specified.
	gravityX = parseFloat(gravityX);
	if (!isNaN(gravityX)) this.GravityX = gravityX;

	// Set vertical gravity if one was specified.
	gravityY = parseFloat(gravityY);
	if (!isNaN(gravityY)) this.GravityY = gravityY;

	// Create an array for the list of all objects in this world.
	this.All = [];

	// Create an array for the list of groups in this world.
	this.Groups = [];

	// Create an array for the lits of obstacles in this world.
	this.Obstacles = [];
};

World.prototype = {
	// The gravity in this world.
	GravityX: 0.0, GravityY: 0.05,

	// A list of all objects in this world.
	All: null,

	// A list of groups in this world.
	Groups: null,

	// A list of obstacles in this world.
	Obstacles: null,

	// Boundaries.
	BoundaryMinX: NaN, BoundaryMaxX: NaN,
	BoundaryMinY: NaN, BoundaryMaxY: NaN,

	// Creates a new group.
	CreateGroup: function () {
		return new Group(this);
	},

	// Creates a new obstacle.
	CreateObstacle: function (x, y, width, height) {
		return new Obstacle(this, x, y, width, height);
	},

	// Returns the object with the specified ID, or null if none was found.
	GetByID: function (id) {
		for (var i in this.All) if (this.All[i].ID == id) return this.All[i];
		return null;
	},

	// Updates all the objects in this world.
	Update: function () {
		// Loop through all the groups in this world.
		for (var i in this.Groups) {
			var g = this.Groups[i];

			// Loop through all the constraints in the current group.
			// Loop through them 5 times so that they appear more stiff.
			for (var j = 0; j < 5; j++)
			for (var k in g.Constraints) {
				var c = g.Constraints[k];

				// Calculate the current distance between the atoms constrained by the current constraint.
				var dx = c.Atom2.CurrentX - c.Atom1.CurrentX;
				var dy = c.Atom2.CurrentY - c.Atom1.CurrentY;
				var dist = Math.sqrt(dx * dx + dy * dy);

				// Alter the length of the constraint based on a sine wave.
				if (!(isNaN(c.Amplitude) || isNaN(c.Frequency) || isNaN(c.Step))) {
					c.Length = c.BaseLength + Math.sin(c.Step) * c.Amplitude;
					c.Step += c.Frequency;
				}

				// Calculate how much the atoms need to be moved to satisfy the constraint.
				var diff = dist ? (dist - c.Length) / dist / 3.0 : 0.5;

				// Apply the above value to the distance between the atoms.
				dx *= diff;
				dy *= diff;

				// Move the atoms.
				if (!c.Atom1.Static) {
					c.Atom1.CurrentX += dx;
					c.Atom1.CurrentY += dy;
				}

				if (!c.Atom2.Static) {
					c.Atom2.CurrentX -= dx;
					c.Atom2.CurrentY -= dy;
				}
			}

			// Loop through all the atoms in the current group.
			for (var j in g.Atoms) {
				var a = g.Atoms[j];

				// Skip static atoms.
				if (a.Static) continue;

				// Store the current position of the current atom.
				var tx = a.CurrentX;
				var ty = a.CurrentY;

				// Apply momentum and gravity to the current atom.
				a.CurrentX += tx - a.PreviousX + this.GravityX;
				a.CurrentY += ty - a.PreviousY + this.GravityY;

				// Check for collisions with obstacles.
				for (var k in this.Obstacles) {
					var o = this.Obstacles[k];

					// Bottom edge of atom vs. top edge of obstacle.
					var du = a.CurrentY + a.Radius - o.Y;

					// Top edge of atom vs. bottom edge of obstacle.
					var dd = o.Y + o.Height - a.CurrentY + a.Radius;

					// Right edge of atom vs. left edge of obstacle.
					var dl = a.CurrentX + a.Radius - o.X;

					// Left edge of atom vs. right edge of obstacle.
					var dr = o.X + o.Width - a.CurrentX + a.Radius;

					// Check whether there is an overlap.
					if (du > 0 && dd > 0 && dl > 0 && dr > 0) {
						// Find smallest overlap.
						var min, which = 0;
						if (which == 0 || du < min) { min = du; which = 1; }
						if (which == 0 || dd < min) { min = dd; which = 2; }
						if (which == 0 || dl < min) { min = dl; which = 3; }
						if (which == 0 || dr < min) { min = dr; which = 4; }

						// Handle overlap.
						switch (which) {
							case 1:
								tx += (a.CurrentX - tx) * 0.5;
								a.CurrentY -= du;
							break;

							case 2:
								tx += (a.CurrentX - tx) * 0.5;
								a.CurrentY += dd;
							break;

							case 3:
								ty += (a.CurrentY - ty) * 0.5;
								a.CurrentX -= dl;
							break;

							case 4:
								ty += (a.CurrentY - ty) * 0.5;
								a.CurrentX += dr;
							break;
						}
					}
				}

				// Limit the atom within the set boundaries.
				if (!isNaN(this.BoundaryMinX) && a.CurrentX - a.Radius < this.BoundaryMinX) { ty = a.CurrentY; a.CurrentX = this.BoundaryMinX + a.Radius; }
				if (!isNaN(this.BoundaryMaxX) && a.CurrentX + a.Radius > this.BoundaryMaxX) { ty = a.CurrentY; a.CurrentX = this.BoundaryMaxX - a.Radius; }
				if (!isNaN(this.BoundaryMinY) && a.CurrentY - a.Radius < this.BoundaryMinY) { tx = a.CurrentX; a.CurrentY = this.BoundaryMinY + a.Radius; }
				if (!isNaN(this.BoundaryMaxY) && a.CurrentY + a.Radius > this.BoundaryMaxY) { tx = a.CurrentX; a.CurrentY = this.BoundaryMaxY - a.Radius; }

				// Store the position the current atom had before it was moved.
				a.PreviousX = tx;
				a.PreviousY = ty;
			}
		}
	}
};