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 }