1 /**
  2  * The Render Engine
  3  * CircleColliderComponent
  4  *
  5  * @fileoverview A collision component which determines collisions via
  6  *               bounding circle 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.Circle",
 38     "requires":[
 39         "R.components.Collider",
 40         "R.math.Point2D",
 41         "R.math.Vector2D",
 42         "R.math.Rectangle2D",
 43         "R.math.Circle2D",
 44         "R.struct.CollisionData"
 45     ]
 46 });
 47 
 48 /**
 49  * @class An extension of the {@link ColliderComponent} which will check if the
 50  *        object's are colliding based on their world bounding circles.  If either of the
 51  *          objects does not have the {@link R.objects.Object2D#getWorldCircle} method
 52  *          the test will result in no collision.
 53  *          <p/>
 54  *          By default, this component will perform a simple intersection test which results
 55  *          in a simple <code>true</code> or <code>false</code> test.  A more detailed test
 56  *          can be made by setting the component to perform a circle-to-circle collision test which
 57  *          will result in a collision data structure if a collision occurs.  Setting the testing
 58  *          mode is done by calling the {@link #setTestMode} method.
 59  *
 60  * @param name {String} Name of the component
 61  * @param collisionModel {SpatialCollection} The collision model
 62  * @param priority {Number} Between 0.0 and 1.0, with 1.0 being highest
 63  *
 64  * @extends R.components.Collider
 65  * @constructor
 66  * @description Creates a collider component for circle-circle collision testing.  Each object
 67  *              must implement either the {@link R.objects.Object2D#getWorldBox} or
 68  *              {@link R.objects.Object2D#getCircle} method and return a world-oriented bounding box or
 69  *              circle, respectively.
 70  */
 71 R.components.collision.Circle = function () {
 72     "use strict";
 73     return R.components.Collider.extend(/** @scope R.components.collision.Circle.prototype */{
 74 
 75         hasMethods:null,
 76         cData:null,
 77 
 78         /**
 79          * Releases the component back into the pool for reuse.  See {@link R.engine.PooledObject#release}
 80          * for more information.
 81          */
 82         release:function () {
 83             this.base();
 84             this.hasMethod = null;
 85             this.cData = null;
 86         },
 87 
 88         /**
 89          * Deprecated in favor of {@link #setGameObject}
 90          * @deprecated
 91          */
 92         setHostObject:function (gameObject) {
 93             this.setGameObject(gameObject);
 94         },
 95 
 96         /**
 97          * Establishes the link between this component and its game object.
 98          * When you assign components to a game object, it will call this method
 99          * so that each component can refer to its game object, the same way
100          * a game object can refer to a component with {@link R.engine.GameObject#getComponent}.
101          *
102          * @param gameObject {R.engine.GameObject} The object which hosts this component
103          */
104         setGameObject:function (gameObject) {
105             this.base(gameObject);
106             this.hasMethods = [gameObject.getWorldCircle != undefined]; // getWorldBox
107             /* pragma:DEBUG_START */
108             // Test if the host has getWorldCircle
109             AssertWarn(this.hasMethods[0], "Object " + gameObject.toString() + " does not have getWorldCircle() method");
110             /* pragma:DEBUG_END */
111         },
112 
113         /**
114          * If a collision occurs, calls the game object's <tt>onCollide()</tt> method,
115          * passing the time of the collision, the potential collision object, and the game object
116          * and target's masks.  The return value should either tell the collision tests to continue or stop.
117          *
118          * @param time {Number} The engine time (in milliseconds) when the potential collision occurred
119          * @param dt {Number} The delta between the world time and the last time the world was updated
120          *          in milliseconds.
121          * @param collisionObj {R.engine.GameObject} The game object with which the collision potentially occurs
122          * @param objectMask {Number} The collision mask for the game object
123          * @param targetMask {Number} The collision mask for <tt>collisionObj</tt>
124          * @return {Number} A status indicating whether to continue checking, or to stop
125          */
126         testCollision:function (time, dt, collisionObj, objectMask, targetMask) {
127             if (this.getCollisionData() != null) {
128                 // Clean up old data first
129                 this.getCollisionData().destroy();
130                 this.setCollisionData(null);
131             }
132 
133             // Early out if no method(s)
134             if (!this.hasMethods[0] && !collisionObj.getWorldCircle) {
135                 return R.components.Collider.CONTINUE;	// Can't perform test
136             }
137 
138             // See if a collision will occur
139             var linked = this.getLinkedBody(),
140                 host = this.getGameObject(),
141                 circle1 = host.getWorldCircle(),
142                 circle2 = collisionObj.getWorldCircle();
143 
144             if (this.getTestMode() == R.components.Collider.SIMPLE_TEST &&
145                 circle1.isIntersecting(circle2)) {
146 
147                 // Intersection test passed
148                 return this.base(time, dt, collisionObj, objectMask, targetMask);
149 
150             } else {
151                 var tRad = circle1.getRadius() + circle2.getRadius(),
152                     c1 = circle1.getCenter(), c2 = circle2.getCenter();
153 
154                 var distSqr = (c1.x - c2.x) * (c1.x - c2.x) +
155                     (c1.y - c2.y) * (c1.y - c2.y);
156 
157                 if (distSqr < (tRad * tRad)) {
158                     // Collision occurred, how much to separate circle1 from circle2
159                     var diff = tRad - Math.sqrt(distSqr);
160 
161                     // If we got here, there is a collision
162                     var sep = R.math.Vector2D.create((c2.x - c1.x) * diff, (c2.y - c1.y) * diff);
163                     this.setCollisionData(R.struct.CollisionData.create(sep.len(),
164                         R.math.Vector2D.create(c2.x - c1.x, c2.y - c1.y).normalize(),
165                         host,
166                         collisionObj,
167                         sep,
168                         time,
169                         dt));
170 
171                     return this.base(time, collisionObj, objectMask, targetMask);
172                 }
173             }
174 
175             // No collision
176             return R.components.Collider.CONTINUE;
177         }
178 
179         /* pragma:DEBUG_START */, execute:function (renderContext, time, dt) {
180             this.base(renderContext, time, dt);
181             // Debug the collision box
182             if (R.Engine.getDebugMode() && !this.isDestroyed()) {
183                 var linked = this.getLinkedBody(),
184                     origin = R.math.Point2D.create(linked ? linked.getLocalOrigin() : R.math.Point2D.ZERO),
185                     rect = R.math.Rectangle2D.create(this.getGameObject().getWorldBox());
186 
187                 rect.offset(origin.neg());
188 
189                 renderContext.postRender(function () {
190                     this.setLineStyle("yellow");
191                     this.setLineWidth(1);
192                     this.drawRectangle(rect);
193                 });
194 
195                 origin.destroy();
196             }
197         }
198         /* pragma:DEBUG_END */
199 
200     }, { /** @scope R.components.collision.Circle.prototype */
201 
202         /**
203          * Get the class name of this object
204          *
205          * @return {String} The string "R.components.collision.Circle"
206          */
207         getClassName:function () {
208             return "R.components.collision.Circle";
209         }
210 
211     });
212 };