1 /**
  2  * The Render Engine
  3  *
  4  * Simulation object
  5  *
  6  * @author: Brett Fattori (brettf@renderengine.com)
  7  *
  8  * @author: $Author: bfattori $
  9  * @version: $Revision: 1555 $
 10  *
 11  * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com)
 12  *
 13  * Permission is hereby granted, free of charge, to any person obtaining a copy
 14  * of this software and associated documentation files (the "Software"), to deal
 15  * in the Software without restriction, including without limitation the rights
 16  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 17  * copies of the Software, and to permit persons to whom the Software is
 18  * furnished to do so, subject to the following conditions:
 19  *
 20  * The above copyright notice and this permission notice shall be included in
 21  * all copies or substantial portions of the Software.
 22  *
 23  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 24  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 25  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 26  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 27  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 28  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 29  * THE SOFTWARE.
 30  *
 31  */
 32 
 33 // The class this file defines and its required classes
 34 R.Engine.define({
 35     "class":"R.physics.Simulation",
 36     "requires":[
 37         "R.engine.BaseObject",
 38         "R.math.Math2D",
 39         "R.components.physics.BaseBody"
 40     ],
 41     "includes":[
 42         "/libs/Box2dWeb-2.1.a.3.js"
 43     ]
 44 });
 45 
 46 /**
 47  * @class A representation of a physical world.  This object is used to
 48  *        introduce Box2dWeb physics into your game by creating a world
 49  *        which supports the physical structures provided by Box2dWeb.  You
 50  *        will need to create an <tt>R.physics.Simulation</tt> before you can utilize
 51  *        physics in a game.
 52  *        <p/>
 53  *        See either "/demos/physics/" or "/demos/physics2" for examples
 54  *        of utilizing the <tt>R.physics.Simulation</tt> object with rigid body components.
 55  *
 56  * @param name {String} The name of the object
 57  * @param viewport {R.math.Rectangle2D} Your rendering context's viewport
 58  * @param [gravity] {R.math.Vector2D} The world's gravity vector. default: [0, 650]
 59  * @extends R.engine.BaseObject
 60  * @constructor
 61  * @description Create a physical world for Box2dJS
 62  */
 63 R.physics.Simulation = function () {
 64     return R.engine.BaseObject.extend(/** @scope R.physics.Simulation.prototype */{
 65 
 66         world:null,
 67         gravity:null,
 68         doSleep:true,
 69         integrations:0,
 70         _groundBody:null,
 71 
 72         /** @private */
 73         constructor:function (name, gravity) {
 74             this.base(name);
 75             this.gravity = gravity || R.math.Vector2D.create(0, 10);
 76 
 77             this.doSleep = true;
 78             this.integrations = R.physics.Simulation.DEFAULT_INTEGRATIONS;
 79             var b2Gravity = new Box2D.Common.Math.b2Vec2(this.gravity.x, this.gravity.y);
 80 
 81             // Create the world and get the ground body
 82             this.world = new Box2D.Dynamics.b2World(b2Gravity, this.doSleep);
 83             this._groundBody = R.components.physics.BaseBody.create("WORLD_GROUND", new Box2D.Dynamics.b2FixtureDef());
 84             this._groundBody.body = this.world.GetGroundBody();
 85         },
 86 
 87         destroy:function () {
 88             this.gravity.destroy();
 89             this.base();
 90         },
 91 
 92         release:function () {
 93             this.worldAABB = null;
 94             this.gravity = null,
 95                 this.world = null;
 96             this.base();
 97         },
 98 
 99         update:function (renderContext, time, dt) {
100             this.world.Step(R.physics.Simulation.FIXED_TIMESTEP, this.integrations, this.integrations);
101             this.world.ClearForces();
102         },
103 
104         /**
105          * Support method to get the ground body for the world.
106          * @return {R.components.physics.BaseBody} The world's ground body
107          * @private
108          */
109         getGroundBody:function () {
110             return this._groundBody;
111         },
112 
113         /**
114          * Query the world within the given rectangle returning all of the
115          * bodies found.
116          * @param rect {R.math.Rectangle2D} The area to query
117          * @return {Array} An array of <tt>Box2D.Dynamics.b2Body</tt> objects
118          */
119         getBodiesInArea:function (rect) {
120             var aabb = new Box2D.Collision.b2AABB(), bodies = [];
121             aabb.lowerBound.Set(rect.x, rect.y);
122             aabb.upperBound.Set(rect.w, rect.h);
123 
124             // Query the world
125             this.world.QueryAABB(function (fixture) {
126                 if (fixture.GetBody().GetType() != Box2D.Dynamics.b2Body.b2_staticBody) {
127                     bodies.push(fixture.GetBody());
128                 }
129                 return true;
130             }, aabb);
131 
132             return bodies;
133         },
134 
135         /**
136          * Query the world for the body that lies at the given point.
137          * @param point {R.math.Point2D} The point to query
138          * @return {Box2D.Dynamics.b2Body} The body found, or <tt>null</tt>
139          */
140         getBodyAtPoint:function (point) {
141             var aabb = new Box2D.Collision.b2AABB(), body = null,
142                 qP = R.clone(point).div(R.physics.Simulation.WORLD_SIZE),
143                 b2P = new Box2D.Common.Math.b2Vec2(qP.x, qP.y);
144 
145             aabb.lowerBound.Set(qP.x - 0.001, qP.y - 0.001);
146             aabb.upperBound.Set(qP.x + 0.001, qP.y + 0.001);
147 
148             qP.destroy();
149 
150             // Query the world
151             this.world.QueryAABB(function (fixture) {
152                 if (fixture.GetBody().GetType() != Box2D.Dynamics.b2Body.b2_staticBody &&
153                     fixture.GetShape().TestPoint(fixture.GetBody().GetTransform(), b2P)) {
154                     body = fixture.GetBody();
155                     return false;
156                 }
157                 return true;
158             }, aabb);
159 
160             return body;
161         },
162 
163         /**
164          * Support method to add a body to the simulation.  The body must be one of the
165          * box2d-js body types.  This method is intended to be used by {@link R.components.BaseBody}.
166          *
167          * @param b2jsBodyDef {Box2D.Dynamics.b2BodyDef} A box2d-js Body definition object
168          * @param b2jsFixtureDef {Box2D.Dynamics.b2FixtureDef} A box2d-js fixture definition object
169          * @private
170          */
171         addBody:function (b2jsBodyDef, b2jsFixtureDef) {
172             var b = this.world.CreateBody(b2jsBodyDef);
173             b.CreateFixture(b2jsFixtureDef);
174             return b;
175         },
176 
177         /**
178          * Support method to add a joint to the simulation.  The joint must be one of the
179          * box2d-js joint types.  This method is intended to be used by {@link R.components.BaseJoint}.
180          *
181          * @param b2jsJointDef {Box2D.Dynamics.b2JointDef} A box2d-js Joint definition object
182          * @private
183          */
184         addJoint:function (b2jsJointDef) {
185             return this.world.CreateJoint(b2jsJointDef);
186         },
187 
188         /**
189          * Support method to remove a body from the simulation.  The body must be one of the
190          * box2d-js body types.  This method is intended to be used by {@link R.components.BaseBody}.
191          *
192          * @param b2jsBody {Box2D.Dynamics.b2Body} A box2d-js Body object
193          * @private
194          */
195         removeBody:function (b2jsBody) {
196             this.world.DestroyBody(b2jsBody);
197         },
198 
199         /**
200          * Support method to remove a joint from the simulation.  The joint must be one of the
201          * box2d-js joint types.  This method is intended to be used by {@link R.components.BaseJoint}.
202          *
203          * @param b2jsJoint {Box2D.Dynamics.b2Joint} A box2d-js Joint object
204          * @private
205          */
206         removeJoint:function (b2jsJoint) {
207             this.world.DestroyJoint(b2jsJoint);
208         },
209 
210         /**
211          * Set the number of integrations per frame.  A higher number will result
212          * in more accurate collisions, but will result in slower performance.
213          *
214          * @param integrations {Number} The number of integrations per frame
215          */
216         setIntegrations:function (integrations) {
217             this.integrations = integrations || R.physics.Simulation.DEFAULT_INTEGRATIONS;
218         },
219 
220         /**
221          * Get the number of integrations per frame.
222          * @return {Number}
223          */
224         getIntegrations:function () {
225             return this.integrations;
226         },
227 
228         /**
229          * Add a simple box body to the simulation.  The body doesn't have a visual
230          * representation, but exists in the simulation and can be interacted with.
231          *
232          * @param pos {R.math.Point2D} The position where the body's top/left is located
233          * @param extents {R.math.Point2D} The width and height of the body
234          * @param properties {Object} An object with up to three properties: <ul>
235          *       <li>restitution - The bounciness of the body</li>
236          *         <li>friction - Friction against this body</li>
237          *         <li>density - The density of the object (default: 0)</li>
238          *         <li>isStatic - <tt>false</tt> for a dynamic body (default: <tt>true</tt>)</li></ul>
239          *
240          * @return {Box2D.Dynamics.b2Body} A Box2dWeb body definition object representing the box
241          */
242         addSimpleBoxBody:function (pos, extents, properties) {
243             properties = $.extend({ isStatic:true }, properties);
244 
245             var bodyDef = R.physics.Simulation.BODY_DEF,
246                 fixDef = R.physics.Simulation.FIXTURE_DEF;
247 
248             bodyDef.type = properties.isStatic ? Box2D.Dynamics.b2Body.b2_staticBody : Box2D.Dynamics.b2Body.b2_dynamicBody;
249 
250             fixDef.shape = new Box2D.Collision.Shapes.b2PolygonShape();
251             extents.div(R.physics.Simulation.WORLD_SIZE);
252             fixDef.shape.SetAsBox(extents.x / 2, extents.y / 2);	// Half-width and height
253 
254             // Set the properties
255             fixDef.restitution = properties.restitution || R.components.physics.BaseBody.DEFAULT_RESTITUTION;
256             fixDef.friction = properties.friction || R.components.physics.BaseBody.DEFAULT_FRICTION;
257             fixDef.density = properties.density || 1.0;
258 
259             var scaled = R.math.Point2D.create(pos.x, pos.y).div(R.physics.Simulation.WORLD_SIZE);
260 
261             bodyDef.position.x = scaled.x;
262             bodyDef.position.y = scaled.y;
263             scaled.destroy();
264             return this.addBody(bodyDef, fixDef);
265         },
266 
267         /**
268          * Add a simple circle body to the simulation.  The body doesn't have a visual
269          * representation, but exists in the simulation and can be interacted with.
270          *
271          * @param pos {Point2D} The position where the body's center is located
272          * @param radius {Point2D} The radius of the circle body
273          * @param properties {Object} An object with up to three properties: <ul>
274          *       <li>restitution - The bounciness of the body</li>
275          *         <li>friction - Friction against this body</li>
276          *         <li>density - The density of the object (default: 0)</li>
277          *         <li>isStatic - <tt>false</tt> for a dynamic body (default: <tt>true</tt>)</li></ul>
278          *
279          * @return {b2BodyDef} A Box2D-JS body definition object representing the circle
280          */
281         addSimpleCircleBody:function (pos, radius, properties) {
282             properties = $.extend({ isStatic:true }, properties);
283 
284             var bodyDef = R.physics.Simulation.BODY_DEF,
285                 fixDef = R.physics.Simulation.FIXTURE_DEF;
286 
287             bodyDef.type = properties.isStatic ? Box2D.Dynamics.b2Body.b2_staticBody : Box2D.Dynamics.b2Body.b2_dynamicBody;
288 
289             radius /= R.physics.Simulation.WORLD_SIZE;
290 
291             fixDef.shape = new Box2D.Collision.Shapes.b2CircleShape(radius);
292 
293             // Set the properties
294             fixDef.restitution = properties.restitution || R.components.physics.BaseBody.DEFAULT_RESTITUTION;
295             fixDef.friction = properties.friction || R.components.physics.BaseBody.DEFAULT_FRICTION;
296             fixDef.density = properties.density || 1.0;
297 
298             var scaled = R.math.Point2D.create(pos.x, pos.y).div(R.physics.Simulation.WORLD_SIZE);
299 
300             bodyDef.position.x = scaled.x;
301             bodyDef.position.y = scaled.y;
302             scaled.destroy();
303             return this.addBody(bodyDef, fixDef);
304         }
305 
306     }, /** @scope R.physics.Simulation.prototype */{
307 
308         /**
309          * Get the class name as a string.
310          * @return {String} "R.physics.Simulation"
311          */
312         getClassName:function () {
313             return "R.physics.Simulation";
314         },
315 
316         /**
317          * Reusable definition for fixtures
318          * @private
319          */
320         FIXTURE_DEF:null,
321 
322         /**
323          * Reusable definition for bodies
324          * @private
325          */
326         BODY_DEF:null,
327 
328         /**
329          * @private
330          */
331         resolved:function () {
332             // These are reusable, according to Box2d docs
333             R.physics.Simulation.FIXTURE_DEF = new Box2D.Dynamics.b2FixtureDef();
334             R.physics.Simulation.BODY_DEF = new Box2D.Dynamics.b2BodyDef();
335         },
336 
337         /**
338          * The default number of integrations per frame
339          * @type {Number}
340          */
341         DEFAULT_INTEGRATIONS:10,
342 
343         /**
344          * The size of the world in meters
345          */
346         WORLD_SIZE:20,
347 
348         /**
349          * The world is updated at 60Hz, or 1/60th of a second.  This time step is
350          * ideal so that objects do not jitter.  Changing this value can result in
351          * some truly odd behavior in the simulation.
352          * @type {Number}
353          */
354         FIXED_TIMESTEP:1 / 60
355 
356     });
357 };
358