1 /** 2 * The Render Engine 3 * BaseBodyComponent 4 * 5 * @fileoverview The base component type for all physical bodies which can be used 6 * in a {@link Simulation}. 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.physics.BaseBody", 38 "requires":[ 39 "R.components.Transform2D", 40 "R.math.Point2D", 41 "R.math.Vector2D", 42 "R.math.Rectangle2D", 43 "R.physics.Simulation" 44 ] 45 }); 46 47 /** 48 * @class The base component which initializes rigid bodies 49 * for use in a {@link R.physics.Simulation}. 50 * 51 * @param name {String} Name of the component 52 * @param fixtureDef {Box2D.Dynamics.b2FixtureDef} The fixture definition. 53 * 54 * @extends R.components.Transform2D 55 * @constructor 56 * @description All physical body components should extend from this component type 57 * to inherit such values as density and friction, and gain access to position and rotation. 58 */ 59 R.components.physics.BaseBody = function () { 60 return R.components.Transform2D.extend(/** @scope R.components.physics.BaseBody.prototype */{ 61 62 bodyDef:null, 63 fixtureDef:null, 64 simulation:null, 65 body:null, 66 rotVec:null, 67 bodyPos:null, 68 renderComponent:null, 69 origin:null, 70 71 scaledPoint:null, 72 _states:null, 73 74 /** 75 * @private 76 */ 77 constructor:function (name, fixtureDef) { 78 this.base(name || "BaseBody"); 79 80 this.bodyDef = new Box2D.Dynamics.b2BodyDef(); 81 this.bodyDef.type = Box2D.Dynamics.b2Body.b2_dynamicBody; 82 83 this.fixtureDef = fixtureDef; 84 this.fixtureDef.restitution = R.components.physics.BaseBody.DEFAULT_RESTITUTION; 85 this.fixtureDef.density = R.components.physics.BaseBody.DEFAULT_DENSITY; 86 this.fixtureDef.friction = R.components.physics.BaseBody.DEFAULT_FRICTION; 87 this.simulation = null; 88 this.rotVec = R.math.Vector2D.create(0, 0); 89 this.bodyPos = R.math.Point2D.create(0, 0); 90 this.origin = R.math.Point2D.create(0, 0); 91 this.scaledPoint = R.math.Point2D.create(0, 0); 92 93 // 0: Active, 1: Sleeping 94 this._states = [false, false]; 95 }, 96 97 /** 98 * Destroy the object 99 */ 100 destroy:function () { 101 if (this.simulation) { 102 this.stopSimulation(); 103 } 104 105 if (this.renderComponent != null) { 106 this.renderComponent.destroy(); 107 } 108 109 this.rotVec.destroy(); 110 this.bodyPos.destroy(); 111 this.origin.destroy(); 112 this.scaledPoint.destroy(); 113 114 this.base(); 115 }, 116 117 /** 118 * Releases the object back into the pool 119 */ 120 release:function () { 121 this.base(); 122 123 this.rotVec = null; 124 this.bodyPos = null; 125 this.origin = null; 126 this._states = null; 127 }, 128 129 /** 130 * Start simulating the body. If the body isn't a part of the simulation, 131 * it is added and simulation occurs. Position and rotation will be updated. 132 */ 133 startSimulation:function () { 134 if (!this.simulation) { 135 this.simulation = this.getGameObject().getSimulation(); 136 this.body = this.simulation.addBody(this.getBodyDef(), this.getFixtureDef()); 137 138 // Add something to the body so we can get back to this object 139 this.body.__$backRef$__ = this; 140 } 141 }, 142 143 /** 144 * Stop simulating the body. If the body is a part of a simulation, 145 * it is removed and simulation stops. The position and rotation of 146 * the body will not be updated. 147 */ 148 stopSimulation:function () { 149 if (this.simulation) { 150 this.simulation.removeBody(this.getBody()); 151 this.simulation = null; 152 } 153 }, 154 155 /** 156 * Set the associated render component for this body. This is typically used by the 157 * {@link PhysicsActor} to link a body to a renderer so that each body can have an 158 * associated renderer applied. 159 * 160 * @param renderComponent {R.components.Render} The render component to associate with this body 161 */ 162 setRenderComponent:function (renderComponent) { 163 this.renderComponent = renderComponent; 164 if (renderComponent != null) { 165 this.renderComponent.setGameObject(this.getGameObject()); 166 } 167 }, 168 169 /** 170 * Get the associated render component for this body. 171 * @return {R.components.Render} or <code>null</code> 172 */ 173 getRenderComponent:function () { 174 return this.renderComponent; 175 }, 176 177 /** 178 * Set the origin of the rigid body. By default, the origin is the top left corner of 179 * the bounding box for the body. Most times the origin should be set to the center 180 * of the body. 181 * 182 * @param x {Number|R.math.Point2D} The X coordinate or a <tt>Point2D</tt> 183 * @param y {Number} The Y coordinate or <tt>null</tt> if X is a <tt>Point2D</tt> 184 */ 185 setLocalOrigin:function (x, y) { 186 this.origin.set(x, y); 187 }, 188 189 /** 190 * Get the local origin of the body. 191 * @return {R.math.Point2D} 192 */ 193 getLocalOrigin:function () { 194 return this.origin; 195 }, 196 197 /** 198 * Get the center of the body. 199 * @return {R.math.Point2D} 200 */ 201 getCenter:function () { 202 return R.clone(this.getPosition()); 203 }, 204 205 /** 206 * [ABSTRACT] Get a box which bounds the body. 207 * @return {R.math.Rectangle2D} 208 */ 209 getBoundingBox:function () { 210 return R.math.Rectangle2D.create(0, 0, 1, 1); 211 }, 212 213 /** 214 * Get the Box2d fixture definition object. 215 * @return {Box2D.Dynamics.b2FixtureDef} 216 */ 217 getFixtureDef:function () { 218 return this.fixtureDef; 219 }, 220 221 /** 222 * Get the Box2d body definition object. 223 * @return {b2BodyDef} 224 */ 225 getBodyDef:function () { 226 return this.bodyDef; 227 }, 228 229 /** 230 * Get the Box2d body object which was added to the simulation. 231 * @return {b2Body} 232 */ 233 getBody:function () { 234 return this.body; 235 }, 236 237 /** 238 * Update the fixture on a simulated body. Doing so may cause a hiccup in simulation 239 * @protected 240 */ 241 updateFixture:function () { 242 if (this.simulation) { 243 // Destroy the current fixture, then recreate it () 244 this.getBody().DestroyFixture(this.fixtureDef); 245 this.getBody().CreateFixture(this.fixtureDef); 246 } 247 }, 248 249 /** 250 * Set the resitution (bounciness) of the body. The value should be between 251 * zero and one. Values higher than one are accepted, but produce objects which 252 * are unrealistically bouncy. 253 * 254 * @param restitution {Number} A value between 0.0 and 1.0 255 */ 256 setRestitution:function (restitution) { 257 this.fixtureDef.restitution = restitution; 258 this.updateFixture(); 259 }, 260 261 /** 262 * Get the resitution (bounciness) value for the body. 263 * @return {Number} 264 */ 265 getRestitution:function () { 266 return this.fixtureDef.restitution; 267 }, 268 269 /** 270 * Set the density of the body. 271 * 272 * @param density {Number} The density of the body 273 */ 274 setDensity:function (density) { 275 this.fixtureDef.density = density; 276 this.updateFixture(); 277 }, 278 279 /** 280 * Get the density of the body. 281 * @return {Number} 282 */ 283 getDensity:function () { 284 return this.fixtureDef.density; 285 }, 286 287 /** 288 * Set the friction of the body. Lower values slide easily across other bodies. 289 * Higher values will cause a body to stop moving as it slides across other bodies. 290 * However, even a body which has high friction will keep sliding across a body 291 * with no friction. 292 * 293 * @param friction {Number} The friction of the body 294 */ 295 setFriction:function (friction) { 296 this.fixtureDef.friction = friction; 297 this.updateFixture(); 298 }, 299 300 /** 301 * Get the friction of the body. 302 * @return {Number} 303 */ 304 getFriction:function () { 305 return this.fixtureDef.friction; 306 }, 307 308 /** 309 * Set the initial position of the body. Once a body is in motion, updating 310 * its position should be avoided since it doesn't fit with physical simulation. 311 * To change an object's position, try applying forces or impulses to the body. 312 * 313 * @param point {R.math.Point2D} The initial position of the body 314 */ 315 setPosition:function (point) { 316 if (!this.simulation) { 317 this.scaledPoint.set(point).div(R.physics.Simulation.WORLD_SIZE); 318 this.getBodyDef().position.Set(this.scaledPoint.x, this.scaledPoint.y); 319 } else { 320 this.scaledPoint.set(point).div(R.physics.Simulation.WORLD_SIZE); 321 var bv = new Box2D.Common.Math.b2Vec2(this.scaledPoint.x, this.scaledPoint.y); 322 this.getBody().SetPosition(bv); 323 } 324 }, 325 326 /** 327 * Get the position of the body during simulation. This value is updated 328 * as the simulation is stepped. 329 * @return {R.math.Point2D} 330 */ 331 getPosition:function () { 332 if (this.simulation) { 333 var bp = this.getBody().GetPosition(); 334 this.scaledPoint.set(bp.x, bp.y).mul(R.physics.Simulation.WORLD_SIZE); 335 this.bodyPos.set(this.scaledPoint); 336 } else { 337 this.scaledPoint.set(this.getBodyDef().position.x, this.getBodyDef().position.y).mul(R.physics.Simulation.WORLD_SIZE); 338 this.bodyPos.set(this.scaledPoint); 339 } 340 return this.bodyPos; 341 }, 342 343 /** 344 * Get the rotation of the body. This value is updated as the simulation is stepped. 345 * @return {Number} 346 */ 347 getRotation:function () { 348 if (this.simulation) { 349 return R.math.Math2D.radToDeg(this.getBody().GetAngle()); 350 } else { 351 return this.getBodyDef().angle; 352 } 353 }, 354 355 /** 356 * Set the angle of rotation for the body, in degrees. 357 * @param angle {Number} The rotation angle in degrees 358 */ 359 setRotation:function (angle) { 360 if (this.simulation) { 361 this.getBody().SetAngle(R.math.Math2D.degToRad(angle)); 362 } else { 363 this.getBodyDef().angle = R.math.Math2D.degToRad(angle); 364 } 365 }, 366 367 /** 368 * Apply a force at a world point. If the force is not applied at the center of mass, 369 * it will generate a torque and affect the angular velocity. This wakes up the body. 370 * Forces are comprised of a force vector and 371 * a position. The force vector is the direction in which the force is 372 * moving, while the position is where on the body the force is acting. 373 * Forces act upon a body from world coordinates. 374 * 375 * @param forceVector {R.math.Vector2D} The force vector 376 * @param position {R.math.Point2D} The position where the force is acting upon the body 377 */ 378 applyForce:function (forceVector, position) { 379 var fv = new Box2D.Common.Math.b2Vec2(forceVector.x, forceVector.y); 380 var dv = new Box2D.Common.Math.b2Vec2(position.x, position.y); 381 this.getBody().ApplyForce(fv, dv); 382 }, 383 384 /** 385 * Apply an impulse at a point. This immediately modifies the velocity. It also modifies 386 * the angular velocity if the point of application is not at the center of mass. This wakes 387 * up the body. Impulses are comprised of an impulse vector and 388 * a position. The impulse vector is the direction of the impulse, while the position 389 * is where on the body the impulse will be applied. 390 * Impulses act upon a body locally, adjusting its velocity. 391 * 392 * @param impulseVector {R.math.Vector2D} The impulse vectory 393 * @param position {R.math.Point2D} the position where the impulse is originating from in the body 394 */ 395 applyImpulse:function (impulseVector, position) { 396 var iv = new Box2D.Common.Math.b2Vec2(impulseVector.x, impulseVector.y); 397 var dv = new Box2D.Common.Math.b2Vec2(position.x, position.y); 398 this.getBody().ApplyImpulse(iv, dv); 399 }, 400 401 /** 402 * Apply torque to the body. This affects the angular velocity without affecting the 403 * linear velocity of the center of mass. 404 * 405 * @param torque {Number} The amount of torque to apply to the body 406 */ 407 applyTorque:function (torque) { 408 this.getBody().ApplyTorque(torque); 409 }, 410 411 /** 412 * Get the total mass of the body. If the body is not simulating, this 413 * returns <code>Infinity</code>. 414 * 415 * @return {Number} The mass of the body, or <code>Infinity</code> 416 */ 417 getMass:function () { 418 if (this.simulation) { 419 return this.getBody().GetMass(); 420 } else { 421 return this.getBodyDef().massData.mass; 422 } 423 }, 424 425 /** 426 * Set the total mass of the body in kilograms. 427 * @param mass {Number} The mass of the body in kg 428 */ 429 setMass:function (mass) { 430 if (this.simulation) { 431 var mData = new Box2D.Dynamics.b2MassData(); 432 this.getBody().GetMassData(mData); 433 mData.mass = mass; 434 this.getBody().SetMassData(mData); 435 mData = null; 436 } else { 437 this.getBodyDef().massData.mass = mass; 438 } 439 }, 440 441 /** 442 * Get the linear damping of the body. 443 * @return {Number} 444 */ 445 getLinearDamping:function () { 446 if (this.simulation) { 447 return this.getBody().GetLinearDamping(); 448 } else { 449 return this.getBodyDef().linearDamping; 450 } 451 }, 452 453 /** 454 * Get the angular damping of the body. 455 * @return {Number} 456 */ 457 getAngularDamping:function () { 458 if (this.simulation) { 459 return this.getBody().GetAngularDamping(); 460 } else { 461 return this.getBodyDef().angularDamping; 462 } 463 }, 464 465 /** 466 * Sets the linear and angular damping of a body. Damping is used to reduce the 467 * world velocity of bodies. Damping differs from friction in that friction 468 * only occurs when two surfaces are in contact. Damping is not a replacement 469 * for friction. A value between 0 and <code>Infinity</code> can be used, but 470 * normally the value is between 0 and 1.0. 471 * 472 * @param linear {Number} The amount of linear damping 473 * @param angular {Number} The amount of angular damping 474 */ 475 setDamping:function (linear, angular) { 476 if (this.simulation) { 477 this.getBody().SetLinearDamping(linear); 478 this.getBody().SetAngularDamping(linear); 479 } else { 480 this.getBodyDef().linearDamping = linear; 481 this.getBodyDef().angularDamping = angular; 482 } 483 }, 484 485 /** 486 * Set a body to be static or dynamic. A static body will not move around. 487 * @param state {Boolean} <code>true</code> to set the body as static, <code>false</code> for 488 * dynamic. 489 */ 490 setStatic:function (state) { 491 this.getBodyDef().type = state ? Box2D.Dynamics.b2Body.b2_staticBody : Box2D.Dynamics.b2Body.b2_dynamicBody; 492 }, 493 494 /** 495 * Returns <code>true</code> if the body is static. A body is static if it 496 * isn't updated part of the simulation during contacts. 497 * @return {Boolean} 498 */ 499 isStatic:function () { 500 if (this.simulation) { 501 return this.getBody().GetType() == Box2D.Dynamics.b2Body.b2_staticBody; 502 } else { 503 return this.getBodyDef().type == Box2D.Dynamics.b2Body.b2_staticBody; 504 } 505 }, 506 507 /** 508 * Returns <code>true</code> if the body is sleeping. A body is sleeping if it 509 * has settled to the point where no movement is being calculated. If you want 510 * to perform an action upon a body, other than applying force, torque, or impulses, 511 * you must call {@link #wakeUp}. 512 * @return {Boolean} 513 */ 514 isSleeping:function () { 515 if (this.simulation) { 516 return !this.getBody().IsAwake(); 517 } else { 518 return !this.getBodyDef().awake; 519 } 520 }, 521 522 /** 523 * Returns <code>true</code> if the body is active. An active body is updated during 524 * the simulation and can be collided with. 525 * @return {Boolean} 526 */ 527 isActive:function () { 528 if (this.simulation) { 529 return this.getBody().IsActive(); 530 } else { 531 return this.getBodyDef().active; 532 } 533 }, 534 535 /** 536 * Wake up a body, adding it back into the collection of bodies being simulated. 537 * If the body is not being simulated, this does nothing. 538 */ 539 wakeUp:function () { 540 if (this.simulation) { 541 this.getBody().SetAwake(true); 542 } 543 }, 544 545 /** 546 * Sets the active state of a body. Setting the active flag to <tt>false</tt> will 547 * remove the object from simulation. Setting it to true will add it back into the 548 * simulation. 549 * @param active {Boolean} The activity flag 550 */ 551 setActive:function (active) { 552 if (this.simulation) { 553 this.getBody().SetActive(active); 554 } else { 555 this.getBodyDef().active = active; 556 } 557 }, 558 559 /** 560 * Checks a couple of flags on the body and triggers events when they change. Fires 561 * the <code>active</code> event on the game object when this body changes its "active" state. 562 * Fires the <code>sleeping</code> event on the game object when this body changes its 563 * "sleeping" state. Both events are passed the body which changed state, and a flag 564 * indicating the current state. 565 * 566 * @param renderContext {R.rendercontexts.AbstractRenderContext} The rendering context 567 * @param time {Number} The engine time in milliseconds 568 * @param dt {Number} The delta between the world time and the last time the world was updated 569 * in milliseconds. 570 */ 571 execute:function (renderContext, time, dt) { 572 this.base(renderContext, time, dt); 573 574 // Check the sleeping and active states so we can trigger events 575 var activeChange = false, sleepChange = false; 576 if (!this._states[0] && this.isActive()) { 577 this._states[0] = true; 578 activeChange = true; 579 } else if (this._states[0] && !this.isActive()) { 580 this._states[0] = false; 581 activeChange = true; 582 } 583 584 if (!this._states[1] && this.isSleeping()) { 585 this._states[1] = true; 586 sleepChange = true; 587 } else if (this._states[1] && !this.isSleeping()) { 588 this._states[1] = false; 589 sleepChange = true; 590 } 591 592 if (activeChange) 593 this.getGameObject().triggerEvent("active", [this, this._states[0]]); 594 595 if (sleepChange) 596 this.getGameObject().triggerEvent("sleeping", [this, this._states[1]]); 597 } 598 599 }, { /** @scope R.components.physics.BaseBody.prototype */ 600 601 /** 602 * Get the class name of this object 603 * 604 * @return {String} "R.components.physics.BaseBody" 605 */ 606 getClassName:function () { 607 return "R.components.physics.BaseBody"; 608 }, 609 610 /** 611 * The default restitution (bounciness) of a body 612 * @type {Number} 613 */ 614 DEFAULT_RESTITUTION:0.48, 615 616 /** 617 * The default density of a body 618 */ 619 DEFAULT_DENSITY:1.0, 620 621 /** 622 * The default friction of a body 623 * @type {Number} 624 */ 625 DEFAULT_FRICTION:0.5 626 627 }); 628 };