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 };