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