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