PageRenderTime 102ms CodeModel.GetById 75ms app.highlight 21ms RepoModel.GetById 1ms app.codeStats 0ms

/js/lib/circlecollision/CircleManager.js

http://github.com/onedayitwillmake/RealtimeMultiplayerNodeJs
JavaScript | 348 lines | 198 code | 51 blank | 99 comment | 58 complexity | 224fb576c7943bb12d543bdebb6392f8 MD5 | raw file
  1/**
  2	  ####  #####  ##### ####    ###  #   # ###### ###### ##     ##  #####  #     #      ########    ##    #  #  #####
  3	 #   # #   #  ###   #   #  #####  ###    ##     ##   ##  #  ##    #    #     #     #   ##   #  #####  ###   ###
  4	 ###  #   #  ##### ####   #   #   #   ######   ##   #########  #####  ##### ##### #   ##   #  #   #  #   # #####
  5 -
  6 File:
  7 	PackedCircle.js
  8 Created By:
  9 	Mario Gonzalez
 10 Project	:
 11 	None
 12 Abstract:
 13 	 Manages a set of packed circles.
 14 Basic Usage:
 15	http://onedayitwillmake.com/CirclePackJS/
 16*/
 17(function()
 18{
 19	// Retrieve the namespace
 20	RealtimeMultiplayerGame.namespace("RealtimeMultiplayerGame.modules.circlecollision");
 21
 22    /**
 23     * @constructor
 24     */
 25	RealtimeMultiplayerGame.modules.circlecollision.CircleManager = function()
 26	{
 27		this.allCircles = [];
 28		return this;
 29	};
 30
 31	RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype = {
 32		allCircles:					[],						// An array containing all the circles in this CircleManager
 33		numberOfCollisionPasses:	1,						// Number of times to run the collision check, higher is more accurate with less overlapping but slower
 34		numberOfTargetingPasses:	0,						// Number of times to move a circle towards its target
 35		bounds:						{},						// Object containing x,y,width,height
 36		collisionCallback:			null,					// An object containing a scope and a function block
 37
 38		// These can be passed to the handleBoundaryForCircle function
 39		BOUNDARY_WRAP_X 			: 1 << 0,
 40		BOUNDARY_WRAP_Y 			: 1 << 1,
 41		BOUNDARY_CONSTRAIN_X 		: 1 << 2,
 42		BOUNDARY_CONSTRAIN_Y 		: 1 << 3,
 43
 44		/**
 45		 * Adds a circle to the simulation
 46		 * @param aCircle
 47		 */
 48		addCircle: function(aCircle) {
 49			aCircle.id = this.allCircles.length;
 50			this.allCircles.push(aCircle);
 51			return this;
 52		},
 53
 54		/**
 55		 * Removes a circle from the simulations
 56		 * @param aCircle	Circle to remove
 57		 */
 58		removeCircle: function(aCircle) {
 59			var index = 0,
 60				found = false,
 61				len = this.allCircles.length;
 62
 63			if(len === 0) {
 64				throw "Error: (CircleManager) attempting to remove circle, and allCircles.length === 0!!"
 65			}
 66
 67			while (len--) {
 68				if(this.allCircles[len] === aCircle) {
 69					found = true;
 70					index = len;
 71					break;
 72				}
 73			}
 74
 75			if(!found) {
 76				throw "Could not locate circle in allCircles array!"
 77			}
 78
 79			// Remove
 80			this.allCircles[index].dealloc();
 81			this.allCircles[index] = null;
 82
 83			return this;
 84		},
 85
 86		/**
 87		 * Forces all circles to move to where their delegate position is
 88		 * Assumes all targets have a 'position' property!
 89		 */
 90		forceCirclesToMatchDelegatePositions: function()
 91		{
 92			var len = this.allCircles.length;
 93
 94			// push toward target position
 95			for(var n = 0; n < len; n++)
 96			{
 97				var aCircle = this.allCircles[n];
 98				if(!aCircle || !aCircle.delegate) {
 99					continue;
100				}
101
102				aCircle.position.set(aCircle.delegate.x + aCircle.offset.x, aCircle.delegate.y + aCircle.offset.y);
103			}
104		},
105
106
107		pushAllCirclesTowardTarget: function(aTarget) {
108			var v = new RealtimeMultiplayerGame.model.Point().set(0,0),
109				circleList = this.allCircles,
110				len = circleList.length;
111
112			// push toward target position
113			for(var n = 0; n < this.numberOfTargetingPasses; n++)
114			{
115				for(var i = 0; i < len; i++)
116				{
117					var c = circleList[i];
118
119					if(c.isFixed) continue;
120
121					v.x = c.position.x - (c.targetPosition.x+c.offset.x);
122					v.y = c.position.y - (c.targetPosition.y+c.offset.y);
123					v.multiply(c.targetChaseSpeed);
124
125					c.position.x -= v.x;
126					c.position.y -= v.y;
127				}
128			}
129		},
130
131		/**
132		 * Packs the circles towards the center of the bounds.
133		 * Each circle will have it's own 'targetPosition' later on
134		 */
135		handleCollisions: function()
136		{
137			this.removeExpiredElements();
138
139			var v = new RealtimeMultiplayerGame.model.Point().set(0, 0),
140				circleList = this.allCircles,
141				len = circleList.length;
142
143			// Collide circles
144			for(var n = 0; n < this.numberOfCollisionPasses; n++)
145			{
146				for(var i = 0; i < len; i++)
147				{
148					var ci = circleList[i];
149
150
151					for (var j = i + 1; j< len; j++)
152					{
153						var cj = circleList[j];
154
155						// Circle collision should be ignored (ci == cj, or collisionGroups are incorrect
156						if( !this.circlesCanCollide(ci, cj) ) {
157							continue;   // It's us!
158						}
159
160						var dx = cj.position.x - ci.position.x,
161							dy = cj.position.y - ci.position.y;
162
163						// The distance between the two circles radii, but we're also gonna pad it a tiny bit
164						var r = (ci.radius + cj.radius),
165							d = ci.position.getDistanceSquared(cj.position);
166
167						/**
168						 * Collision detected!
169						 */
170						if (d < (r * r) - 0.02 )
171						{
172							v.x = dx;
173							v.y = dy;
174							v.normalize();
175
176							var inverseForce = (r - Math.sqrt(d)) * 0.5;
177							v.multiply(inverseForce);
178
179							// Move cj opposite of the collision as long as its not fixed
180							if(!cj.isFixed)
181							{
182								if(ci.isFixed)
183									v.multiply(2.0);	// Double inverse force to make up for the fact that the other object is fixed
184
185								// ADD the velocity
186								cj.position.translatePoint(v);
187							}
188
189							// Move ci opposite of the collision as long as its not fixed
190							if(!ci.isFixed)
191							{
192								if(cj.isFixed)
193									v.multiply(2.0);	// Double inverse force to make up for the fact that the other object is fixed
194
195								 // SUBTRACT the velocity
196								ci.position.subtract(v);
197							}
198
199							// Emit the collision event from each circle, with itself as the first parameter
200							if(this.collisionCallback && n === 0)
201							{
202								this.collisionCallback.block.call( this.collisionCallback.scope, ci, cj, v );
203							}
204						}
205					}
206				}
207			}
208		},
209
210		/**
211		 * Performs boundary check against a circle.
212		 * Valid options are:
213		 * RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_WRAP_X
214		 * RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_WRAP_Y
215		 * RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_CONSTRAIN_X
216		 * RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_CONSTRAIN_Y
217		 *
218		 * These can be combined in the form of:
219		 * RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_WRAP_X | RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_CONSTRAIN_Y
220		 * @param {RealtimeMultiplayerGame.modules.circlecollision.PackedCircle}	aCircle Circle to perform boundary check against
221		 * @param {Number} boundsRule	A bitmask representing the boundary rules
222		 */
223		handleBoundaryForCircle: function(aCircle, boundsRule) {
224			if(boundsRule === undefined) {
225				throw "No Boundary rule defined!";
226			}
227			var xpos = aCircle.position.x;
228			var ypos = aCircle.position.y;
229
230			var radius = aCircle.radius;
231			var diameter = radius*2;
232
233			// Toggle these on and off,
234			// Wrap and bounce, are opposite behaviors so pick one or the other for each axis, or bad things will happen.
235			var wrapXMask = RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_WRAP_X;
236			var wrapYMask = RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_WRAP_Y;
237			var constrainXMask = RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_CONSTRAIN_X;
238			var constrainYMask = RealtimeMultiplayerGame.modules.circlecollision.CircleManager.prototype.BOUNDARY_CONSTRAIN_Y;
239
240			// Convert to bitmask - Uncomment the one you want, or concact your own :)
241//			boundsRule = wrapXMask | wrapYMask;  // Wrap Y axis, but constrain horizontally
242
243
244			// Wrap X
245			if(boundsRule & wrapXMask && xpos-diameter > this.bounds.width) {
246				aCircle.position.x = this.bounds.x - radius;
247			} else if(boundsRule & wrapXMask && xpos+diameter < this.bounds.x) {
248				aCircle.position.x = this.bounds.width - radius;
249			}
250			// Wrap Y
251			if(boundsRule & wrapYMask && ypos-diameter > this.bounds.height) {
252				aCircle.position.y = this.bounds.y - radius;
253			} else if(boundsRule & wrapYMask && ypos+diameter < this.bounds.y) {
254				aCircle.position.y = this.bounds.height + radius;
255			}
256
257			// Constrain X
258			if(boundsRule & constrainXMask && xpos+radius >= this.bounds.width) {
259				aCircle.position.x = aCircle.position.x = this.bounds.width-radius;
260			} else if(boundsRule & constrainXMask && xpos-radius < this.bounds.x) {
261				aCircle.position.x = this.bounds.x + radius;
262			}
263
264			// Constrain Y
265			if(boundsRule & constrainYMask && ypos+radius > this.bounds.height) {
266				aCircle.position.y = this.bounds.height - radius;
267			} else if(boundsRule & constrainYMask && ypos-radius < this.bounds.y) {
268				aCircle.position.y = this.bounds.y + radius;
269			}
270		},
271
272		/**
273		 * Performs handleBoundaryForCircle on all circles
274		 * @param {Number} boundsRule	A bitmask representing the boundary rules
275		 */
276		handleBoundaryForAllCircles: function( boundsRule ) {
277			if( boundsRule === undefined ) {
278				throw "handleBoundaryForAllCircles - No Bounds Rule defined!";
279			}
280			var len = this.allCircles.length;
281			for(var i = 0; i < len; i++)
282				this.handleBoundaryForCircle( this.allCircles[i], boundsRule)
283		},
284
285
286
287		/**
288		 * Checks if two Circles can collide with one another.
289		 * For example, given the following three objects
290		 *
291		 * someCircleA.collisionMask = 1;
292		 * someCircleA.collisionGroup = 2;
293		 *
294		 * someCircleB.collisionMask = 2;
295		 * someCircleB.collisionGroup = 1;
296		 *
297		 * someCircleC.collisionMask = 2;
298		 * someCircleC.collisionGroup = 1;
299		 *
300		 * A and B will collide, B and C will not collide because B and C only want to collide with group 2
301		 *
302		 * @param {RealtimeMultiplayerGame.modules.circlecollision.PackedCircle}	circleA
303		 * @param {RealtimeMultiplayerGame.modules.circlecollision.PackedCircle}	circleB
304		 */
305		circlesCanCollide: function(circleA, circleB) {
306			if(!circleA || !circleB || circleA === circleB) return false; 					// one is null (will be deleted next loop), or both point to same obj.
307			if(circleA.delegate == null || circleB.delegate == null) return false;			// This circle will be removed next loop, it's entity is already removed
308
309//			both are fixed
310			if(circleA.isFixed & circleB.isFixed) return false;
311
312//			They dont want to collide
313			if((circleA.collisionGroup & circleB.collisionMask) == 0) return false;
314			if((circleB.collisionGroup & circleA.collisionMask) == 0) return false;
315
316			return true;
317		},
318
319		///// ACCESSORS
320		getAllCircles: function() { return this.allCircles },
321		setBounds: function(x, y, w, h) {
322			this.bounds.x = x;
323			this.bounds.y = y;
324			this.bounds.width = w;
325			this.bounds.height = h;
326		},
327		setNumberOfCollisionPasses: function(value) {
328			this.numberOfCollisionPasses = value;
329			return this;
330		},
331		setNumberOfTargetingPasses: function(value) {
332			this.numberOfTargetingPasses = value;
333			return this;
334		},
335		setCallback: function(block, scope) {
336			this.collisionCallback = {'block': block, 'scope': scope};
337		},
338
339		///// MEMORY MANAGEMENT
340		removeExpiredElements: function() {
341			// remove null elements
342			for (var k = this.allCircles.length; k >= 0; k--) {
343				if (this.allCircles[k] === null)
344					this.allCircles.splice(k, 1);
345			}
346		}
347	};
348})();