1 /** 2 * The Render Engine 3 * 4 * PhysicsActor 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.objects.PhysicsActor", 36 "requires":[ 37 "R.physics.Simulation", 38 39 "R.components.physics.CircleBody", 40 "R.components.physics.BoxBody", 41 "R.components.physics.PolyBody", 42 "R.components.physics.DistanceJoint", 43 "R.components.physics.RevoluteJoint", 44 "R.components.physics.WeldJoint", 45 "R.components.physics.PrismaticJoint", 46 "R.components.physics.PulleyJoint", 47 "R.components.physics.MouseJoint", 48 49 "R.math.Math2D", 50 "R.objects.Object2D", 51 "R.resources.loaders.ObjectLoader" 52 ] 53 }); 54 55 /** @private */ 56 var toP2d = function (arr) { 57 return R.math.Point2D.create(arr[0], arr[1]); 58 }; 59 60 /** @private */ 61 var getRelativePosition = function (aV, obj) { 62 if ($.isArray(aV) && aV.length == 2) { 63 // An absolute position 64 return toP2d(aV); 65 } else { 66 // If the array has 3 values, the third is a relative position string 67 // and the first two are an offset from that point. Otherwise, we assume 68 // the value is only the position string 69 var rel = ($.isArray(aV) && aV.length == 3 ? aV[2] : aV); 70 var offs = ($.isArray(aV) && aV.length == 3 ? toP2d(aV) : R.math.Point2D.create(0, 0)); 71 var rPos = R.math.Point2D.create(0, 0); 72 73 // Calculate the anchor, relative to the position of the object provided 74 var bb = obj.getBoundingBox().offset(obj.getPosition()); 75 var c = obj.getCenter(); 76 switch (rel.toLowerCase()) { 77 case "center": 78 rPos.set(obj.getCenter()); 79 break; 80 case "topleft": 81 rPos.set(bb.x, bb.y); 82 break; 83 case "topright": 84 rPos.set(bb.x + bb.w, bb.y); 85 break; 86 case "bottomleft": 87 rPos.set(bb.x, bb.y + bb.h); 88 break; 89 case "bottomright": 90 rPos.set(bb.x + bb.w, bb.y + bb.h); 91 break; 92 case "topcenter": 93 rPos.set(c.x, bb.y); 94 break; 95 case "bottomcenter": 96 rPos.set(c.x, bb.y + bb.h); 97 break; 98 case "leftmiddle": 99 rPos.set(bb.x, c.y); 100 break; 101 case "rightmiddle": 102 rPos.set(bb.x + bb.h, c.y); 103 break; 104 } 105 106 // Perform the offset 107 return rPos.add(offs); 108 } 109 }; 110 111 /** @private */ 112 var makeJoint = function (args) { 113 return args[3].create(args[0], args[1], args[2], arguments[1], arguments[2], arguments[3]) 114 }; 115 116 117 /** 118 * @class A <tt>R.objects.PhysicsActor</tt> is an actor object within a game represented by 119 * a collection of components which can include rigid bodies and joints. 120 * Unlike {@link R.objects.Object2D}, a <code>R.objects.PhysicsActor</code> can associate each rigid 121 * body with its own {@link R.components.Render}. When the rigid body is updated, the 122 * render component is updated with it. That way, a physics actor can be comprised 123 * of multiple bodies, each with their own renderer allowing for a complex object 124 * such as a ragdoll with many parts and joints. A physics actor is used within a 125 * {@link R.physics.Simulation}. 126 * <p/> 127 * A <code>R.objects.PhysicsActor</code> acts just like an {@link R.objects.Object2D}, but it is special 128 * in that it's rigid bodies are animated via a {@link R.physics.Simulation}. Without being added to a 129 * {@link R.physics.Simulation}, none of the physical bodies will be updated. 130 * 131 * @param name {String} The name of the actor object 132 * @extends R.objects.Object2D 133 * @constructor 134 * @description Create a physics actor 135 */ 136 R.objects.PhysicsActor = function () { 137 return R.objects.Object2D.extend(/** @scope R.objects.PhysicsActor.prototype */{ 138 139 simulation:null, 140 rootBody:null, 141 rigidBodies:null, 142 joints:null, 143 rPos:null, 144 145 /** 146 * @private 147 */ 148 constructor:function (name) { 149 this.base(name || "PhysicsActor"); 150 this.rootBody = null; 151 this.rigidBodies = null; 152 this.rPos = R.math.Point2D.create(0, 0); 153 this.simulation = null; 154 }, 155 156 /** 157 * Remove all of the bodies and joints from the simulation before 158 * destroying the object. 159 */ 160 destroy:function () { 161 if (this.simulation) { 162 // Remove bodies and joints from the simulation 163 var bodies = this.getRigidBodies(); 164 for (var b in bodies) { 165 this.simulation.removeBody(bodies[b].getBody()); 166 } 167 } 168 this.rPos.destroy(); 169 this.base(); 170 }, 171 172 /** 173 * Get the collection of rigid body components within the actor. 174 * @return {Array} 175 */ 176 getRigidBodies:function () { 177 if (!this.rigidBodies) { 178 this.rigidBodies = this.getObjects(function (el) { 179 return (el instanceof R.components.physics.BaseBody); 180 }); 181 } 182 return this.rigidBodies; 183 }, 184 185 /** 186 * Get the collection of joint components within the actor. 187 * @return {Array} 188 */ 189 getJoints:function () { 190 if (!this.joints) { 191 this.joints = this.getObjects(function (el) { 192 return (el instanceof R.components.physics.BaseJoint); 193 }); 194 } 195 return this.joints; 196 }, 197 198 /** 199 * Set the rigid body component which is considered to be the root 200 * body. When setting the position of a <tt>R.objects.PhysicsActor</tt>, all positions 201 * are calculated relative to where the root body was originally set. 202 * <p/> 203 * It is not necessary to set the root body if there is only one rigid body 204 * in the actor. 205 * 206 * @param body {R.components.physics.BaseBody} The body to assign as the root 207 */ 208 setRootBody:function (body) { 209 Assert(body instanceof R.components.physics.BaseBody, "Root body is not a BaseBodyComponent"); 210 this.rootBody = body; 211 this.setDefaultTransformComponent(body); 212 }, 213 214 /** 215 * Get the root body of the <tt>R.objects.PhysicsActor</tt>. If no root object has been assigned, 216 * the first rigid body component will be used. 217 * @return {R.components.physics.BaseBody} 218 */ 219 getRootBody:function () { 220 if (!this.rootBody) { 221 // Get all of the bodies and select the first to be the root 222 this.rootBody = this.getRigidBodies()[0]; 223 this.setDefaultTransformComponent(this.rootBody); 224 } 225 return this.rootBody; 226 }, 227 228 /** 229 * Set the position of the actor. If the actor is comprised of multiple rigid bodies, 230 * the position will be set for all rigid bodies and joints, relative to the root body. 231 * 232 * @param x {Number|R.math.Point2D} The X position, or a <tt>R.math.Point2D</tt> 233 * @param y {Number} The Y position, or <tt>null</tt> if X is a <tt>R.math.Point2D</tt> 234 */ 235 setPosition:function (x, y) { 236 var pt = R.math.Point2D.create(x, y); 237 var pos = R.math.Point2D.create(this.getRootBody().getPosition()); 238 pt.sub(pos); 239 240 var bodies = this.getRigidBodies(); 241 for (var b in bodies) { 242 var bPos = bodies[b].getPosition(); 243 bPos.add(pt); 244 bodies[b].setPosition(bPos); 245 } 246 247 var joints = this.getJoints(); 248 for (var j in joints) { 249 joints[j].offset(pt); 250 } 251 252 pt.destroy(); 253 pos.destroy(); 254 }, 255 256 /** 257 * Set the <code>R.physics.Simulation</code> this actor participates within. When a <code>R.objects.PhysicsActor</code> 258 * is part of a running <code>R.physics.Simulation</code>, you must set the simulation so the physics components 259 * can be properly added to the simulated world. 260 * 261 * @param simulation {R.physics.Simulation} The simulation this object is within 262 */ 263 setSimulation:function (simulation) { 264 this.simulation = simulation; 265 }, 266 267 /** 268 * Get the <code>R.physics.Simulation</code> this object participates within. 269 * @return {R.physics.Simulation} 270 */ 271 getSimulation:function () { 272 return this.simulation; 273 }, 274 275 /** 276 * Start simulation of the physical components. 277 */ 278 simulate:function () { 279 // Start simulation on bodies first 280 var bodies = this.getRigidBodies(); 281 for (var b in bodies) { 282 bodies[b].startSimulation(); 283 } 284 285 // Follow up with simulation of joints 286 var joints = this.getJoints(); 287 for (var j in joints) { 288 if (!(joints[j] instanceof R.components.physics.MouseJoint)) { 289 joints[j].startSimulation(); 290 } 291 } 292 }, 293 294 /** 295 * Add a component to the physics actor. The components will be 296 * sorted based on their type then their priority within that type. 297 * Components with a higher priority will be sorted before components 298 * with a lower priority. The sorting order for type is: 299 * <ul> 300 * <li>Input</li> 301 * <li>Transform</li> 302 * <li>Logic</li> 303 * <li>Collision</li> 304 * <li>Rendering</li> 305 * </ul> 306 * 307 * @param component {R.components.Base} A component to add to the host. If the component is a 308 * {@link R.components.physics.BaseBody} then the render component must be specified. 309 * @param [renderComponent] {R.components.Render} The render component if the component is a 310 * {@link R.components.physics.BaseBody} 311 */ 312 add:function (component, renderComponent) { 313 if (component instanceof R.components.physics.BaseBody) { 314 315 // Reset the list of rigid bodies so the list will be rebuilt 316 this.rigidBodies = null; 317 318 // Assure that there's a renderer for the body and then link the two 319 Assert(renderComponent == null || (renderComponent instanceof R.components.Render), "Adding non-render component to rigid body component"); 320 321 // Link the two so that when the body (transform) occurs, the renderer does its thing 322 component.setRenderComponent(renderComponent); 323 } 324 325 if (component instanceof R.components.physics.BaseJoint) { 326 // Reset the list of joints so the list will be rebuilt 327 this.joints = null; 328 } 329 330 // Add the component 331 this.base(component); 332 }, 333 334 /** 335 * Update this object within the render context, at the specified timeslice. 336 * 337 * @param renderContext {R.rendercontexts.AbstractRenderContext} The context the object will be rendered within. 338 * @param time {Number} The global time within the engine. 339 * @param dt {Number} The delta between the world time and the last time the world was updated 340 * in milliseconds. 341 */ 342 update:function (renderContext, time, dt) { 343 // Run the components 344 var components = this.iterator(); 345 346 renderContext.pushTransform(); 347 348 while (components.hasNext()) { 349 var nextComponent = components.next(); 350 var isPhysicsComponent = (nextComponent instanceof R.components.physics.BaseBody); 351 if (isPhysicsComponent) { 352 renderContext.pushTransform(); 353 } 354 nextComponent.execute(renderContext, time, dt); 355 if (isPhysicsComponent && nextComponent.getRenderComponent() != null) { 356 // Make sure to execute the render component immediately following 357 // the body component. 358 var pt = R.clone(nextComponent.getLocalOrigin()).neg(); 359 renderContext.setPosition(pt); 360 361 /* pragma:DEBUG_START */ 362 if (R.Engine.getDebugMode()) { 363 renderContext.drawFilledArc(pt.neg(), 5, 0, 360); 364 } // else { 365 /* pragma:DEBUG_END */ 366 nextComponent.getRenderComponent().execute(renderContext, time, dt); 367 /* pragma:DEBUG_START */ 368 //} 369 /* pragma:DEBUG_END */ 370 371 pt.destroy(); 372 } 373 if (isPhysicsComponent) { 374 renderContext.popTransform(); 375 } 376 } 377 378 renderContext.popTransform(); 379 380 components.destroy(); 381 382 // Special case so we can skip the super class (HostObject) 383 R.struct.HashContainer.prototype.update.call(this, renderContext, time, dt); 384 } 385 386 // TODO: This needs to account for ALL bodies, not just the root body 387 /*,getWorldBox: function() { 388 var wbox = R.clone(this.base()); 389 var pt = R.clone(this.getRootBody().getLocalOrigin()).mul(1/this.getRootBody().getScaleX()).neg(); 390 wbox.offset(pt); 391 pt.destroy(); 392 return wbox; 393 }*/ 394 395 /** @private */, __noop:function () { 396 // This function only serves to make sure that ObjectLoader exists 397 // for the static methods below. 398 var q = R.resourceloaders.Object.create("dummy"); 399 } 400 401 }, /** @scope R.objects.PhysicsActor.prototype */{ // Static 402 403 /** 404 * Get the class name of this object 405 * @return The string <tt>R.objects.PhysicsActor</tt> 406 * @type String 407 */ 408 getClassName:function () { 409 return "R.objects.PhysicsActor"; 410 }, 411 412 /** 413 * Resource loader for physics actor objects 414 * @private 415 */ 416 actorLoader:null, 417 418 /** 419 * @private 420 */ 421 resolved:function () { 422 R.objects.PhysicsActor.actorLoader = R.resources.loaders.ObjectLoader.create("ActorLoader"); 423 }, 424 425 /** 426 * Helper method to load a physics object file which describes the objects 427 * and joints which comprise the object. The format consists of "parts" 428 * which define the types of physical object ("circle", "box") and other 429 * parameters required by each part. Additionally, the format will load 430 * joints which are used to link the parts together. 431 * <p/> 432 * The actor object is loaded asynchronously which means it isn't immediately 433 * available. You get a reference to the object by calling {@link R.objects.PhysicsActor#get}. 434 * <p/> 435 * An example <tt>R.objects.PhysicsActor</tt> file can be found in the "/demos/physics2/" 436 * demo game. 437 * 438 * @param name {String} The unique reference name of the actor object 439 * @param url {String} The URL where the resource is located 440 * @static 441 */ 442 load:function (name, url) { 443 R.objects.PhysicsActor.actorLoader.load(name, url); 444 }, 445 446 /** 447 * Determine the ready state of a physics actor loaded with {@link R.objects.PhysicsActor#load}. 448 * 449 * @param name {String} The unique reference name of the actor object 450 * @return {Boolean} <code>true</code> if the object is ready for use 451 * @static 452 */ 453 isReady:function (name) { 454 return R.objects.PhysicsActor.actorLoader.isReady(name); 455 }, 456 457 /** 458 * Factory method to create an instance of a physics actor by name. 459 * 460 * @param name {String} The unique reference name of the actor object 461 * @param [objName] {String} The name to assign to the instance when created 462 * @return {R.objects.PhysicsActor} A new instance of the actor defined by "name" 463 * @static 464 */ 465 get:function (name, objName) { 466 467 var def = R.objects.PhysicsActor.actorLoader.get(name), 468 actor = R.objects.PhysicsActor.create(objName), jointParts = [], relParts = [], bc, p, part; 469 var props = {"friction":"setFriction", "restitution":"setRestitution", "density":"setDensity", 470 "static":"setStatic", "rotation":"setRotation"}; 471 472 // Loop through the parts and build each component 473 for (p in def.parts) { 474 part = def.parts[p], bc; 475 if (part.type == "circle") { 476 part.radius *= (def.scale ? def.scale : 1); 477 bc = R.components.physics.CircleBody.create(part.name, part.radius); 478 } else if (part.type == "box") { 479 var ext = toP2d(part.extents); 480 ext.mul(def.scale ? def.scale : 1); 481 bc = R.components.physics.BoxBody.create(part.name, ext); 482 ext.destroy(); 483 } else { 484 var points = []; 485 for (var d = 0; d < part.points.length; d++) { 486 points.push(toP2d(parts.points[d])); 487 } 488 bc = R.components.physics.PolyBody.create(part.name, points); 489 } 490 491 // Set friction, restitution, or density properties. Both 492 // defaults and per-part 493 for (p in props) { 494 if (def[p]) { 495 bc[props[p]](def[p]); 496 } 497 498 if (part[p]) { 499 bc[props[p]](part[p]); 500 } 501 } 502 503 // Add the component to the actor. We'll let the developer set the renderer 504 // for each body component. 505 actor.add(bc); 506 507 // Position the parts relative to each other, in world coordinates with the 508 // origin at the top left corner of the world 509 if (R.isArray(part.position) && part.position.length == 2) { 510 // Set the position of the part in absolute coordinates 511 var pt = toP2d(part.position); 512 pt.mul(def.scale ? def.scale : 1); 513 bc.setPosition(pt); 514 pt.destroy(); 515 } else if (part.relativeTo) { 516 // The position is either a string or a 3 element array. In either case 517 // the value contains a relative positioning string and possibly an offset 518 relParts.push(part); 519 } 520 521 // Is there a joint defined? Defer it until later when all the parts are loaded 522 // This way we don't have to worry about invalid body references 523 if (part.joint) { 524 jointParts.push(part); 525 } 526 } 527 528 // Now that all the parts are created we need to perform 2 final steps 529 // 1) Position any parts that are relative to others 530 for (var rp in relParts) { 531 // Get the component it is relative to and calculate it's position 532 part = relParts[rp]; 533 var relTo = actor.getComponent(part.relativeTo); 534 var rPos = part.position; 535 var pos = getRelativePosition(rPos, relTo); 536 bc = actor.getComponent(part.name); 537 pos.mul(def.scale ? def.scale : 1); 538 bc.setPosition(pos); 539 pos.destroy(); 540 } 541 542 // 2) link the parts with any joints that were deferred until now 543 for (var j = 0; j < jointParts.length; j++) { 544 part = jointParts[j]; 545 546 var jc, fromPart = (part.joint.linkFrom ? part.joint.linkFrom : part.name), 547 toPart = (part.joint.linkTo ? part.joint.linkTo : part.name), 548 jointName = fromPart + "_" + toPart, anchor, axis, upLim, lowLim, type, anchor1, anchor2, 549 args = [jointName, actor.getComponent(fromPart), actor.getComponent(toPart)]; 550 551 switch (part.joint.type) { 552 case "distance": 553 args.push(R.components.physics.DistanceJoint); 554 anchor1 = part.joint.anchor ? toP2d(part.joint.anchor1) : null; 555 if (anchor1) 556 anchor1.add(actor.getComponent(fromPart).getPosition()) 557 .mul(def.scale != undefined ? def.scale : 1); 558 559 anchor2 = part.joint.anchor ? toP2d(part.joint.anchor2) : null; 560 if (anchor2) 561 anchor2.add(actor.getComponent(fromPart).getPosition()) 562 .mul(def.scale != undefined ? def.scale : 1); 563 564 jc = makeJoint(args, anchor1, anchor2); 565 break; 566 case "revolute": 567 args.push(R.components.physics.RevoluteJoint); 568 anchor = part.joint.anchor ? toP2d(part.joint.anchor) : toP2d([0, 0]); 569 anchor.add(actor.getComponent(fromPart).getPosition()); 570 anchor.mul(def.scale != undefined ? def.scale : 1); 571 jc = makeJoint(args, anchor); 572 573 // Joint rotational limits 574 if (part.joint.maxLim && part.joint.minLim) { 575 upLim = part.joint.maxAngle; 576 lowLim = part.joint.minAngle; 577 jc.setUpperLimitAngle(upLim ? upLim : 0); 578 jc.setLowerLimitAngle(lowLim ? lowLim : 0); 579 } 580 anchor.destroy(); 581 break; 582 case "prismatic": 583 args.push(R.components.physics.PrismaticJoint); 584 anchor = part.joint.anchor ? toP2d(part.joint.anchor) : toP2d([0, 0]); 585 anchor.add(actor.getComponent(fromPart).getPosition()); 586 anchor.mul(def.scale != undefined ? def.scale : 1); 587 axis = part.joint.axis ? toP2d(part.joint.axis) : R.math.Vector2D.UP; 588 jc = makeJoint(args, anchor, axis); 589 590 // Joint translation limits 591 if (part.joint.maxLim && part.joint.minLim) { 592 upLim = part.joint.maxLim; 593 lowLim = part.joint.minLim; 594 jc.setUpperLimit(upLim ? upLim : 0); 595 jc.setLowerLimit(lowLim ? lowLim : 0); 596 } 597 anchor.destroy(); 598 break; 599 case "weld": 600 args.push(R.components.physics.WeldJoint); 601 anchor = part.joint.anchor ? toP2d(part.joint.anchor) : toP2d([0, 0]); 602 anchor.add(actor.getComponent(fromPart).getPosition()); 603 anchor.mul(def.scale != undefined ? def.scale : 1); 604 jc = makeJoint(args, anchor, axis); 605 anchor.destroy(); 606 break; 607 case "pulley": 608 args.push(R.components.physics.PulleyJoint); 609 anchor1 = part.joint.anchor1 ? toP2d(part.joint.anchor1) : toP2d([0, 0]); 610 anchor1.add(actor.getComponent(fromPart).getPosition()); 611 anchor1.mul(def.scale != undefined ? def.scale : 1); 612 anchor2 = part.joint.anchor2 ? toP2d(part.joint.anchor2) : toP2d([0, 0]); 613 anchor2.add(actor.getComponent(fromPart).getPosition()); 614 anchor2.mul(def.scale != undefined ? def.scale : 1); 615 jc = makeJoint(args, anchor1, anchor2, part.joint.ratio); 616 anchor1.destroy(); 617 anchor2.destroy(); 618 break; 619 } 620 621 // Motor force/torque and speed (applies to revolute & prismatic joints 622 if (part.joint.motorForce) { 623 jc.setMotorForce(part.joint.motorForce); 624 } 625 626 if (part.joint.motorSpeed) { 627 jc.setMotorSpeed(part.joint.motorSpeed); 628 } 629 630 // Add the joint to the actor 631 actor.add(jc); 632 } 633 634 Assert(actor.getComponent(def.root) != null, "'root' of actor definition is not a valid part"); 635 if (def.root) { 636 actor.setRootBody(actor.getComponent(def.root)); 637 } 638 639 // Done, give them their actor 640 return actor; 641 } 642 }); 643 }; 644