1 /** 2 * The Render Engine 3 * BoxColliderComponent 4 * 5 * @fileoverview A collision component which determines collisions via 6 * bounding box comparisons. 7 * 8 * @author: Brett Fattori (brettf@renderengine.com) 9 * 10 * @author: $Author: bfattori $ 11 * @version: $Revision: 1555 $ 12 * 13 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 14 * 15 * Permission is hereby granted, free of charge, to any person obtaining a copy 16 * of this software and associated documentation files (the "Software"), to deal 17 * in the Software without restriction, including without limitation the rights 18 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 * copies of the Software, and to permit persons to whom the Software is 20 * furnished to do so, subject to the following conditions: 21 * 22 * The above copyright notice and this permission notice shall be included in 23 * all copies or substantial portions of the Software. 24 * 25 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 * THE SOFTWARE. 32 * 33 */ 34 35 // The class this file defines and its required classes 36 R.Engine.define({ 37 "class":"R.components.collision.Box", 38 "requires":[ 39 "R.components.Collider", 40 "R.math.Point2D", 41 "R.math.Rectangle2D" 42 ] 43 }); 44 45 /** 46 * @class An extension of the {@link R.components.Collider} which will check the 47 * object's axis-aligned bounding boxes for collision. 48 * <p/> 49 * By default, this component will perform a simple intersection test which results 50 * in a simple <code>true</code> or <code>false</code> test. A more detailed test 51 * can be made by setting the component to perform a longest axis circle-to-circle collision 52 * test which will result in a collision data structure if a collision occurs. Setting 53 * the testing mode is done by calling the {@link #setTestMode} method. 54 * 55 * @param name {String} Name of the component 56 * @param collisionModel {R.spatial.AbstractSpatialContainer} The collision model 57 * @param priority {Number} Between 0.0 and 1.0, with 1.0 being highest 58 * 59 * @extends R.components.Collider 60 * @constructor 61 * @description Creates a collider component for box-box collision testing. Each object 62 * must implement the {@link R.objects.Object2D#getWorldBox} method and return a 63 * world-oriented bounding box. 64 */ 65 R.components.collision.Box = function () { 66 "use strict"; 67 return R.components.Collider.extend(/** @scope R.components.collision.Box.prototype */{ 68 69 hasMethod:false, 70 71 /** 72 * Releases the component back into the pool for reuse. See {@link PooledObject#release} 73 * for more information. 74 */ 75 release:function () { 76 this.base(); 77 this.hasMethod = false; 78 }, 79 80 /** 81 * Deprecated in favor of {@link #setGameObject} 82 * @deprecated 83 */ 84 setHostObject:function (gameObject) { 85 this.setGameObject(gameObject); 86 }, 87 88 /** 89 * Establishes the link between this component and its game object. 90 * When you assign components to a game object, it will call this method 91 * so that each component can refer to its game object, the same way 92 * a game object can refer to a component with {@link R.engine.GameObject#getComponent}. 93 * 94 * @param gameObject {R.engine.GameObject} The object which hosts this component 95 */ 96 setGameObject:function (gameObject) { 97 this.base(gameObject); 98 this.hasMethod = (gameObject.getWorldBox != undefined); 99 /* pragma:DEBUG_START */ 100 // Test if the host has getWorldBox 101 AssertWarn(this.hasMethod, "Object " + gameObject.toString() + " does not have getWorldBox() method"); 102 /* pragma:DEBUG_END */ 103 }, 104 105 /** 106 * If a collision occurs, calls the host object's <tt>onCollide()</tt> method, 107 * passing the time of the collision, the potential collision object, and the host 108 * and target masks. The return value should either tell the collision tests to continue or stop. 109 * 110 * @param time {Number} The engine time (in milliseconds) when the potential collision occurred 111 * @param dt {Number} The delta between the world time and the last time the world was updated 112 * in milliseconds. 113 * @param collisionObj {R.engine.GameObject} The game object with which the collision potentially occurs 114 * @param objectMask {Number} The collision mask for the game object 115 * @param targetMask {Number} The collision mask for <tt>collisionObj</tt> 116 * @return {Number} A status indicating whether to continue checking, or to stop 117 */ 118 testCollision:function (time, dt, collisionObj, objectMask, targetMask) { 119 if (this.getCollisionData() != null) { 120 // Clean up old data first 121 this.getCollisionData().destroy(); 122 this.setCollisionData(null); 123 } 124 125 // Early out if no method(s) 126 if (!this.hasMethods && !collisionObj.getWorldBox) { 127 return R.components.Collider.CONTINUE; // Can't perform test 128 } 129 130 // See if a collision will occur 131 var linked = this.getLinkedBody(), 132 host = this.getGameObject(), 133 box1 = linked ? R.math.Rectangle2D.create(host.getWorldBox()).offset(R.math.Point2D.create(linked.getLocalOrigin()).neg()) : host.getWorldBox(), 134 box2 = collisionObj.getWorldBox(); 135 136 if (this.getTestMode() == R.components.Collider.SIMPLE_TEST) { 137 if (box1.isIntersecting(box2)) { 138 // Intersection test passed 139 return this.base(time, dt, collisionObj, objectMask, targetMask); 140 } 141 } else { 142 // We'll approximate using the separating circles method, using the 143 // longest axis between width & height 144 var tRad = Math.max(box1.getHalfWidth(), box1.getHalfHeight()) + Math.max(box2.getHalfWidth(), box2.getHalfHeight()), 145 c1 = box1.getCenter(), c2 = box2.getCenter(); 146 147 var distSqr = (c1.x - c2.x) * (c1.x - c2.x) + 148 (c1.y - c2.y) * (c1.y - c2.y); 149 150 if (distSqr < (tRad * tRad)) { 151 // Collision occurred, how much to separate box1 from box2 152 var diff = tRad - Math.sqrt(distSqr); 153 154 // If we got here, there is a collision 155 var sep = R.math.Vector2D.create((c2.x - c1.x) * diff, (c2.y - c1.y) * diff); 156 this.setCollisionData(R.struct.CollisionData.create(sep.len(), 157 R.math.Vector2D.create(c2.x - c1.x, c2.y - c1.y).normalize(), 158 host, 159 collisionObj, 160 sep, 161 time, 162 dt)); 163 164 return this.base(time, dt, collisionObj, objectMask, targetMask); 165 } 166 } 167 168 return R.components.Collider.CONTINUE; 169 } 170 171 /* pragma:DEBUG_START */, execute:function (renderContext, time, dt) { 172 this.base(renderContext, time, dt); 173 // Debug the collision box 174 if (R.Engine.getDebugMode() && !this.isDestroyed()) { 175 var linked = this.getLinkedBody(), 176 origin = R.math.Point2D.create(linked ? linked.getLocalOrigin() : R.math.Point2D.ZERO), 177 rect = R.math.Rectangle2D.create(this.getGameObject().getWorldBox()); 178 179 rect.offset(origin.neg()); 180 181 renderContext.postRender(function () { 182 this.setLineStyle("yellow"); 183 this.setLineWidth(1); 184 this.drawRectangle(rect); 185 }); 186 187 origin.destroy(); 188 } 189 } 190 /* pragma:DEBUG_END */ 191 192 193 }, { /** @scope R.components.collision.Box.prototype */ 194 195 /** 196 * Get the class name of this object 197 * @return {String} "R.components.collision.Box" 198 */ 199 getClassName:function () { 200 return "R.components.collision.Box"; 201 } 202 203 }); 204 }