1 /**
  2  * The Render Engine
  3  * ColliderComponent
  4  *
  5  * @fileoverview The base collision component.
  6  *
  7  * @author: Brett Fattori (brettf@renderengine.com)
  8  *
  9  * @author: $Author: bfattori $
 10  * @version: $Revision: 1555 $
 11  *
 12  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 13  *
 14  * Permission is hereby granted, free of charge, to any person obtaining a copy
 15  * of this software and associated documentation files (the "Software"), to deal
 16  * in the Software without restriction, including without limitation the rights
 17  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 18  * copies of the Software, and to permit persons to whom the Software is
 19  * furnished to do so, subject to the following conditions:
 20  *
 21  * The above copyright notice and this permission notice shall be included in
 22  * all copies or substantial portions of the Software.
 23  *
 24  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 25  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 26  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 27  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 28  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 29  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 30  * THE SOFTWARE.
 31  *
 32  */
 33 
 34 // The class this file defines and its required classes
 35 R.Engine.define({
 36     "class":"R.components.Collider",
 37     "requires":[
 38         "R.components.Base"
 39     ]
 40 });
 41 
 42 /**
 43  * @class Creates a collider component which tests the collision model for
 44  *              potential collisions. Each frame, the component will update a potential
 45  *              collision list (PCL) using the game objects current position
 46  *              obtained from {@link R.objects.Object2D#getPosition}. Each object which meets
 47  *              certain criteria will be passed to an <tt>onCollide()</tt> method which
 48  *              must be implemented by the game object.  By design, an object cannot
 49  *              collide with itself.  However, this can be changed with the {@link #setCollideSame}
 50  *              method.
 51  *              <p/>
 52  *              The event handler will be passed the target object
 53  *              as its first argument, the time the collision was detected and the time
 54  *              since the last frame was generated as the second and third,
 55  *              finally the target's collision mask as the fourth argument.  Example:
 56  *              <pre>
 57  *                 onCollide: function(obj, time, dt, targetMask) {
 58  *                   if (targetMask == SomeObject.COLLISION_MASK) {
 59  *                      obj.explode();
 60  *                      return R.components.Collider.STOP;
 61  *                   }
 62  *
 63  *                   return R.components.Collider.CONTINUE;
 64  *                 }
 65  *              </pre>
 66  *              The game object must determine if the collision is valid for itself, and then
 67  *              return a value which indicates whether the component should contine to
 68  *              check for collisions, or if it should stop.
 69  *              <p/>
 70  *              If the <tt>onCollide()</tt> method is not implemented on the game object, no
 71  *              collision events will be passed to the game object.
 72  *              <p/>
 73  *              Additionally, a game object can implement <tt>onCollideEnd()</tt> to be notified
 74  *              when collisions have stopped.  The time the collisions stopped and the time since
 75  *              the last frame was generated will be the only arguments.
 76  *
 77  * @param name {String} Name of the component
 78  * @param collisionModel {SpatialCollection} The collision model
 79  * @param priority {Number} Between 0.0 and 1.0, with 1.0 being highest
 80  *
 81  * @extends R.components.Base
 82  * @constructor
 83  * @description A collider component is responsible for handling potential collisions by
 84  *        updating the associated collision model and checking for possible collisions.
 85  */
 86 R.components.Collider = function () {
 87     "use strict";
 88     return R.components.Base.extend(/** @scope R.components.Collider.prototype */{
 89 
 90         collisionModel:null,
 91         collideSame:false,
 92         hasCollideMethods:null,
 93         didCollide:false,
 94         testMode:null,
 95         cData:null,
 96         physicalBody:null,
 97 
 98         /**
 99          * @private
100          */
101         constructor:function (name, collisionModel, priority) {
102             this.base(name, R.components.Base.TYPE_COLLIDER, priority || 1.0);
103             this.collisionModel = collisionModel;
104             this.collideSame = false;
105             this.hasCollideMethods = [false, false];	// onCollide, onCollideEnd
106             this.didCollide = false;
107             this.testMode = R.components.Collider.SIMPLE_TEST;
108             this.cData = null;
109             this.physicalBody = null;
110         },
111 
112         /**
113          * Destroy the component instance.
114          */
115         destroy:function () {
116             if (this.cData != null) {
117                 this.cData.destroy();
118             }
119             this.base();
120         },
121 
122         /**
123          * Deprecated in favor of {@link #setGameObject}.
124          * @deprecated
125          */
126         setHostObject:function (hostObj) {
127             this.setGameObject(hostObj);
128         },
129 
130         /**
131          * Establishes the link between this component and its game object.
132          * When you assign components to a game object, it will call this method
133          * so that each component can refer to its game object, the same way
134          * a game object can refer to a component with {@link R.engine.GameObject#getComponent}.
135          *
136          * @param gameObject {R.engine.GameObject} The object which hosts this component
137          */
138         setGameObject:function (gameObject) {
139             this.base(gameObject);
140             this.setCollisionMask(0x7FFFFFFF);
141             this.hasCollideMethods = [gameObject.onCollide != undefined, gameObject.onCollideEnd != undefined];
142         },
143 
144         // TODO: Should destroy() remove the object from the collision model??
145 
146         /**
147          * Releases the component back into the pool for reuse.  See {@link R.engine.PooledObject#release}
148          * for more information.
149          */
150         release:function () {
151             this.base();
152             this.collisionModel = null;
153             this.collideSame = false;
154             this.hasCollideMethods = null;
155             this.didCollide = false;
156             this.testMode = null;
157             this.cData = null;
158             this.physicalBody = null;
159         },
160 
161         /**
162          * Set the type of testing to perform when determining collisions.  You can specify
163          * either {@link R.components.Collider#SIMPLE_TEST} or {@link R.components.Collider#DETAILED_TEST}.
164          *
165          * @param mode {Number} The testing mode to use
166          */
167         setTestMode:function (mode) {
168             this.testMode = mode;
169         },
170 
171         /**
172          * Determine the type of testing this component will perform. either {@link #SIMPLE_TEST}
173          * or {@link #DETAILED_TEST}
174          * @return {Number}
175          */
176         getTestMode:function () {
177             return this.testMode;
178         },
179 
180         /**
181          * Returns the collision data object, or <code>null</code>.
182          * @return {R.struct.CollisionData}
183          */
184         getCollisionData:function () {
185             return this.cData;
186         },
187 
188         /**
189          * Set the collision data object
190          * @param cData {R.struct.CollisionData} The collision data, or <code>null</code> to clear it
191          */
192         setCollisionData:function (cData) {
193             this.cData = cData;
194         },
195 
196         /**
197          * Get the collision model being used by this component.
198          * @return {R.collision.broadphase.AbstractCollisionModel} The collision model
199          */
200         getCollisionModel:function () {
201             return this.collisionModel;
202         },
203 
204         /**
205          * Set the collision model which the host object participates in.
206          * @param collisionModel {R.collision.broadphase.AbstractCollisionModel} The collision model, or <tt>null</tt> for none
207          */
208         setCollisionModel:function (collisionModel) {
209             this.collisionModel = collisionModel;
210         },
211 
212         /**
213          * Set whether or not an object can collide with an object with the same mask.  By default,
214          * this is <tt>false</tt>.  If you have objects which should collide, and they have the
215          * same mask, this should be set to <tt>true</tt>.
216          * @param state {Boolean} <tt>true</tt> if an object can collide with objects with the same mask
217          */
218         setCollideSame:function (state) {
219             this.collideSame = state;
220         },
221 
222         /**
223          * Returns <tt>true</tt> if an object can collide with an object with the same mask.
224          * Default: <tt>false</tt>
225          * @return {Boolean}
226          */
227         getCollideSame:function () {
228             return this.collideSame;
229         },
230 
231         /**
232          * Returns a set of flags which can result in a collision between two objects.
233          * The flags are bits within a 31-bit Integer that correspond to possible collisions.
234          * @return {Number}
235          */
236         getCollisionMask:function () {
237             return this.collisionModel ? this.collisionModel.getObjectSpatialData(this.getGameObject(), "collisionMask") :
238                 0;
239         },
240 
241         /**
242          * Get the object type that this collider component will respond to.  If
243          * the value is <tt>null</tt>, all objects are potential collision objects.
244          * @return {BaseObject} The only object type to collide with, or <tt>null</tt> for any object
245          * @deprecated see {@link #getCollisionFlags}
246          */
247         getObjectType:function () {
248             return null;
249         },
250 
251         /**
252          * Collision masks allow objects to be considered colliding or not depending on ANDing
253          * the results.  The flags occupy the lowest 31 bits, so there can be a number of
254          * combinations which result in a collision.
255          *
256          * @param collisionMask {Number} A 31-bit integer
257          */
258         setCollisionMask:function (collisionMask) {
259             if (this.collisionModel) {
260                 this.collisionModel.setObjectSpatialData(this.getGameObject(), "collisionMask", collisionMask);
261             }
262         },
263 
264         /**
265          * Set the object type that this component will respond to.  Setting this to <tt>null</tt>
266          * will trigger a potential collision when <i>any object</i> comes into possible contact
267          * with the component's host based on the collision model.  If the object isn't of this type,
268          * no collision tests will be performed.  This allows the developer to fine tune which
269          * object the collision component is responsible for.  As such, multiple collision components
270          * could be used to handle different types of collisions.
271          *
272          * @param objType {BaseObject} The object type to check for
273          * @deprecated see {@link #setCollisionFlags}
274          */
275         setObjectType:function (objType) {
276         },
277 
278         /**
279          * Link a rigid physical body to the collision component.  When using a physical body to represent
280          * a component, it is oftentimes useful to use the body shape as the collision shape.
281          * @param physicalBody {R.components.physics.BaseBody}
282          */
283         linkPhysicalBody:function (physicalBody) {
284             this.physicalBody = physicalBody;
285         },
286 
287         /**
288          * Get the linked physical body.
289          * @return {R.components.physics.BaseBody}
290          */
291         getLinkedBody:function () {
292             return this.physicalBody;
293         },
294 
295         /**
296          * Update the collision model that this component was initialized with.
297          * As objects move about the world, the objects will move to different
298          * areas (or nodes) within the collision model.  It is necessary to
299          * update this model frequently so collisions can be determined.
300          */
301         updateModel:function () {
302             var obj = this.getGameObject();
303             this.getCollisionModel().addObject(obj, obj.getPosition());
304         },
305 
306         /**
307          * Get the collision node the host object is within, or <tt>null</tt> if it
308          * is not within a node.
309          * @return {R.spatial.AbstractSpatialNode}
310          */
311         getSpatialNode:function () {
312             return this.collisionModel.getObjectSpatialData(this.getGameObject(), "lastNode");
313         },
314 
315         /**
316          * Updates the object within the collision model and determines if
317          * the game object should to be alerted whenever a potential collision
318          * has occurred.  If a potential collision occurs, an array (referred to
319          * as a Potential Collision List, or PCL) will be created which
320          * contains objects that might be colliding with the game object.  It
321          * is up to the game object to make the final determination that a
322          * collision has occurred.  If no collisions have occurred, that will be reported
323          * as well.
324          * <p/>
325          * Each object within the PCL will be tested and, if a collision occurs, is
326          * passed to the <tt>onCollide()</tt> method (if declared) of the game object.
327          * If a collision occurred and was handled, the <tt>onCollide()</tt> method should return
328          * {@link CollisionComponent#STOP}, otherwise, it should return {@link CollisionComponent#CONTINUE} to continue
329          * checking objects from the PCL against the game object.
330          *
331          * @param renderContext {R.rendercontexts.AbstractRenderContext} The render context for the component
332          * @param time {Number} The current engine time in milliseconds
333          * @param dt {Number} The delta between the world time and the last time the world was updated
334          *          in milliseconds.
335          */
336         execute:function (renderContext, time, dt) {
337             if (!this.collisionModel) {
338                 return;
339             }
340 
341             var host = this.getGameObject();
342 
343             // Update the collision model
344             this.updateModel();
345 
346             // If the host object needs to know about collisions...
347             var pclNodes = null;
348 
349             // onCollide
350             if (this.hasCollideMethods[0]) {
351                 // Get the host's collision mask once
352                 var hostMask = this.collisionModel.getObjectSpatialData(host, "collisionMask");
353 
354                 // Get the PCL and check for collisions
355                 pclNodes = this.getCollisionModel().getPCL(host.getPosition(), time, dt);
356                 var status = R.components.Collider.CONTINUE;
357                 var collisionsReported = 0;
358 
359                 pclNodes.forEach(function (node) {
360                     for (var itr = node.getObjects().iterator(); itr.hasNext();) {
361                         if (this.isDestroyed()) {
362                             // If the object is destroyed while we're checking collisions against it,
363                             // get outta here
364                             break;
365                         }
366 
367                         var obj = itr.next(),
368                             targetMask = this.collisionModel.getObjectSpatialData(obj, "collisionMask");
369 
370                         if (obj !== this.getGameObject() && // Cannot collide with itself
371                             (hostMask & targetMask) <= hostMask &&
372                             status == R.components.Collider.CONTINUE ||
373                             status == R.components.Collider.COLLIDE_AND_CONTINUE) {
374 
375                             // Test for a collision
376                             status = this.testCollision(time, dt, obj, hostMask, targetMask);
377 
378                             // If they don't return  any value, assume CONTINUE
379                             status = (status == undefined ? R.components.Collider.CONTINUE : status);
380 
381                             // Count actual collisions
382                             collisionsReported += (status == R.components.Collider.STOP ||
383                                 status == R.components.Collider.COLLIDE_AND_CONTINUE ? 1 : 0);
384                         }
385                     }
386                     itr.destroy();
387                 }, this);
388             }
389 
390             // onCollideEnd
391             if (!this.isDestroyed() && this.didCollide && collisionsReported == 0) {
392                 if (this.hasCollideMethods[1]) {
393                     host.onCollideEnd(time, dt);
394                 }
395                 this.didCollide = false;
396             }
397         },
398 
399         /**
400          * Call the host object's <tt>onCollide()</tt> method, passing the time of the collision,
401          * the delta since the last time the world was updated,
402          * the potential collision object, and the game object and target's masks.  The return value should
403          * indicate if the collision tests should continue or stop.
404          * <p/>
405          *
406          * For <tt>R.components.Collider</tt> the collision test is up to the game object to determine.
407          *
408          * @param time {Number} The engine time (in milliseconds) when the potential collision occurred
409          * @param dt {Number} The delta between the world time and the last time the world was updated
410          *          in milliseconds.
411          * @param collisionObj {R.engine.GameObject} The game object with which the collision potentially occurs
412          * @param hostMask {Number} The collision mask for the host object
413          * @param targetMask {Number} The collision mask for <tt>collisionObj</tt>
414          * @return {Number} A status indicating whether to continue checking, or to stop
415          */
416         testCollision:function (time, dt, collisionObj, hostMask, targetMask) {
417             if (hostMask == targetMask && !this.collideSame) {
418                 return R.components.Collider.CONTINUE;
419             }
420 
421             var test = this.getGameObject().onCollide(collisionObj, time, dt, targetMask);
422             this.didCollide |= (test == R.components.Collider.STOP || R.components.Collider.COLLIDE_AND_CONTINUE);
423             return test;
424         }
425 
426     }, /** @scope R.components.Collider.prototype */{ // Statics
427 
428         /**
429          * Get the class name of this object
430          *
431          * @return {String} "R.components.Collider"
432          */
433         getClassName:function () {
434             return "R.components.Collider";
435         },
436 
437         /**
438          * When <tt>onCollide()</tt> is called on the host object, it should
439          * return this value if there was no collision and the host
440          * wishes to be notified about other potential collisions.
441          * @type {Number}
442          */
443         CONTINUE:0,
444 
445         /**
446          * When <tt>onCollide()</tt> is called on the host object, it should
447          * return this if a collision occurred and no more collisions should be reported.
448          * @type {Number}
449          */
450         STOP:1,
451 
452         /**
453          * When <tt>onCollide()</tt> is called on the host object, it should
454          * return this value if a collision occurred and the host wishes to be notified
455          * about other potential collisions.
456          * @type {Number}
457          */
458         COLLIDE_AND_CONTINUE:2,
459 
460         /**
461          * For box and circle collider components, this will perform a simple
462          * intersection test.
463          * @type {Number}
464          */
465         SIMPLE_TEST:1,
466 
467         /**
468          * For box and circle collider components, this will perform a more complex
469          * test which will result in a {@link R.struct.CollisionData} structure.
470          * @type {Number}
471          */
472         DETAILED_TEST:2
473 
474     });
475 };