1 /** 2 * The Render Engine 3 * Object2D 4 * 5 * @fileoverview An extension of the <tt>HostObject</tt> which is specifically geared 6 * towards 2d game development. 7 * 8 * @author: Brett Fattori (brettf@renderengine.com) 9 * @author: $Author: bfattori $ 10 * @version: $Revision: 1555 $ 11 * 12 * Copyright (c) 2011 Brett Fattori (brettf@renderengine.com) 13 * 14 * Permission is hereby granted, free of charge, to any person obtaining a copy 15 * of this software and associated documentation files (the "Software"), to deal 16 * in the Software without restriction, including without limitation the rights 17 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 18 * copies of the Software, and to permit persons to whom the Software is 19 * furnished to do so, subject to the following conditions: 20 * 21 * The above copyright notice and this permission notice shall be included in 22 * all copies or substantial portions of the Software. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 25 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 26 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 27 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 28 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 29 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 30 * THE SOFTWARE. 31 * 32 */ 33 34 // The class this file defines and its required classes 35 R.Engine.define({ 36 "class":"R.objects.Object2D", 37 "requires":[ 38 "R.engine.GameObject", 39 "R.collision.OBBHull", 40 "R.math.Circle2D", 41 "R.math.Math2D", 42 "R.components.Transform2D" 43 ] 44 }); 45 46 /** 47 * @class An object for use in a 2d game environment. If no <tt>transformComponent</tt> is provided, 48 * the object will be assigned a {@link R.components.Transform2D Transform2D} component. This class is the recommended 49 * base class for objects used within a 2d game environment, instead of deriving from the base 50 * {@link R.engine.GameObject} class. 51 * 52 * @param name {String} The name of the object 53 * @param [transformComponent] {R.components.Transform2D} The transform component to use, or 54 * <code>null</code>. If the value is <code>null</code>, the object will be assigned a default 55 * {@link R.components.Transform2D Transform2D} component. 56 * @extends R.engine.GameObject 57 * @constructor 58 * @description Create a game object with methods for operating in a 2D context. 59 */ 60 R.objects.Object2D = function () { 61 return R.engine.GameObject.extend(/** @scope R.objects.Object2D.prototype */{ 62 63 /** @private */ 64 zIndex:1, 65 66 /** @private */ 67 bBox:null, 68 AABB:null, 69 wBox:null, 70 wCircle:null, 71 lastPosition:null, 72 origin:null, 73 originNeg:null, 74 originPos:null, 75 collisionHull:null, 76 genHull:null, 77 defaultTxfmComponent:null, 78 79 oldRenderPosition:null, 80 oldBbox:null, 81 oldScale:null, 82 83 // Simple flag indicating object is descendant of Object2D 84 __OBJECT2D:true, 85 86 // Current origin/negative-origin matrices 87 oMtx:null, 88 oMtxN:null, 89 90 /** @private */ 91 constructor:function (name, transformComponent) { 92 this.base(name); 93 this.lastPosition = R.math.Point2D.create(5, 5); 94 this.originPos = R.math.Point2D.create(5, 5); 95 this.oldRenderPosition = R.math.Point2D.create(5, 5); 96 this.oldBbox = R.math.Rectangle2D.create(0, 0, 1, 1); 97 this.oldScale = R.math.Vector2D.create(1, 1); 98 this.bBox = R.math.Rectangle2D.create(0, 0, 1, 1); 99 this.AABB = R.math.Rectangle2D.create(0, 0, 1, 1); 100 this.wBox = R.math.Rectangle2D.create(0, 0, 1, 1); 101 this.wCircle = R.math.Circle2D.create(0, 0, 1); 102 this.zIndex = 0; 103 this.origin = R.math.Point2D.create(0, 0); 104 this.originNeg = R.math.Point2D.create(0, 0); 105 this.collisionHull = null; 106 this.genHull = false; 107 108 // Assign a default 2d transformation component to store position information 109 this.defaultTxfmComponent = transformComponent != null ? transformComponent : R.components.Transform2D.create("dTxfm__"); 110 this.add(this.defaultTxfmComponent); 111 112 // Initialize the matrices 113 this.oMtx = R.math.Math2D.identityMatrix(); 114 this.oMtxN = R.math.Math2D.identityMatrix(); 115 this.__OBJECT2D = true; 116 }, 117 118 /** 119 * Destroy the object. 120 */ 121 destroy:function () { 122 this.bBox.destroy(); 123 this.wBox.destroy(); 124 this.wCircle.destroy(); 125 this.lastPosition.destroy(); 126 this.originPos.destroy(); 127 this.oldRenderPosition.destroy(); 128 this.oldBbox.destroy(); 129 this.oldScale.destroy(); 130 this.AABB.destroy(); 131 this.wCircle.destroy(); 132 this.origin.destroy(); 133 this.originNeg.destroy(); 134 if (this.collisionHull) { 135 this.collisionHull.destroy(); 136 } 137 this.base(); 138 }, 139 140 /** 141 * Release the object back into the pool. 142 */ 143 release:function () { 144 this.base(); 145 this.zIndex = 1; 146 this.bBox = null; 147 this.wBox = null; 148 this.wCircle = null; 149 this.lastPosition = null; 150 this.originPos = null; 151 this.oldRenderPosition = null; 152 this.oldBbox = null; 153 this.oldScale = null; 154 this.AABB = null; 155 this.wCircle = null; 156 this.origin = null; 157 this.originNeg = null; 158 this.collisionHull = null; 159 this.genHull = null; 160 161 // Free the matrices 162 this.oMtx = null; 163 this.oMtxN = null; 164 }, 165 166 /** 167 * Get the transformation matrix for this object 168 * @return {Matrix} 169 */ 170 getTransformationMatrix:function () { 171 // Translation 172 var p = this.getRenderPosition(); 173 var tMtx = $M([ 174 [1, 0, p.x], 175 [0, 1, p.y], 176 [0, 0, 1] 177 ]); 178 tMtx = tMtx.multiply(this.oMtxN); 179 180 // Rotation 181 var a = this.getRotation(); 182 var rMtx; 183 if (a != 0) { 184 // Move the origin 185 rMtx = this.oMtx.dup(); 186 // Rotate 187 rMtx = rMtx.multiply(Matrix.Rotation(R.math.Math2D.degToRad(a), R.objects.Object2D.ROTATION_AXIS)); 188 // Move the origin back 189 rMtx = rMtx.multiply(this.oMtxN); 190 } 191 else { 192 // Set to identity 193 rMtx = R.math.Math2D.identityMatrix(); 194 } 195 196 // Scale 197 var sX = this.getScaleX(), sY = this.getScaleY(), sMtx = $M([ 198 [sX, 0, 0], 199 [0, sY, 0], 200 [0, 0, 1] 201 ]), 202 txfmMtx = tMtx.multiply(rMtx).multiply(sMtx); 203 204 rMtx = null; 205 sMtx = null; 206 return txfmMtx; 207 }, 208 209 /** 210 * Set the render origin of the object. The render origin is where the object will be 211 * centered around when drawing position and rotation. 212 * 213 * @param x {Number|R.math.Point2D} The X coordinate or the render origin (default: 0,0 - top left corner) 214 * @param y {Number} The Y coordinate or <code>null</code> if X is a <code>Point2D</code> 215 */ 216 setOrigin:function (x, y) { 217 this.origin.set(x, y); 218 this.originNeg.set(x, y).neg(); 219 220 var pX = x; 221 var pY = y; 222 223 if (x.__POINT2D) { 224 pX = x.x; 225 pY = x.y; 226 } 227 228 this.oMtx.setElements([ 229 [1, 0, pX], 230 [0, 1, pY], 231 [0, 0, 1] 232 ]); 233 this.oMtxN.setElements([ 234 [1, 0, -pX], 235 [0, 1, -pY], 236 [0, 0, 1] 237 ]); 238 this.markDirty(); 239 }, 240 241 /** 242 * Get the render origin of the object. 243 * @return {R.math.Point2D} 244 */ 245 getOrigin:function () { 246 return this.origin; 247 }, 248 249 /** 250 * Set the bounding box of this object 251 * 252 * @param width {Number|R.math.Rectangle2D} The width, or the rectangle that completely encompasses 253 * this object. 254 * @param height {Number} If width is a number, this is the height 255 */ 256 setBoundingBox:function (width, height) { 257 if (width.__RECTANGLE2D) { 258 this.bBox.set(width); 259 } 260 else { 261 this.bBox.set(0, 0, width, height); 262 } 263 264 if (this.genHull) { 265 // Do this so a new collision hull is generated 266 this.collisionHull = null; 267 this.genHull = false; 268 } 269 270 this.markDirty(); 271 }, 272 273 /** 274 * Get the object's local bounding box. 275 * @return {R.math.Rectangle2D} The object bounding rectangle 276 */ 277 getBoundingBox:function () { 278 return this.bBox; 279 }, 280 281 /** 282 * [ABSTRACT] Get the object's local bounding circle. 283 * @return {R.math.Circle2D} The object bounding circle 284 */ 285 getBoundingCircle:function () { 286 return null; 287 }, 288 289 /** 290 * Get the object's bounding box in world coordinates. 291 * @return {R.math.Rectangle2D} The world bounding rectangle 292 */ 293 getWorldBox:function () { 294 // Only update if the object has moved, changed size, or has been scaled 295 if (this.getRenderPosition().equals(this.oldRenderPosition) && 296 this.bBox.equals(this.oldBbox) && this.getScale().equals(this.oldScale)) { 297 return this.wBox; 298 } 299 300 this.wBox.set(this.getBoundingBox()); 301 302 // Need to apply scaling 303 this.wBox.setWidth(this.wBox.w * this.getScaleX()); 304 this.wBox.setHeight(this.wBox.h * this.getScaleY()); 305 306 var rPos = R.math.Point2D.create(this.getRenderPosition()).add(this.originNeg); 307 this.wBox.offset(rPos); 308 rPos.destroy(); 309 310 // Remember the changes 311 this.oldRenderPosition.set(this.getRenderPosition()); 312 this.oldBbox.set(this.bBox); 313 this.oldScale.set(this.getScale()); 314 return this.wBox; 315 }, 316 317 /** 318 * Get the object's bounding circle in world coordinates. If {@link #getBoundingCircle} returns 319 * null, the bounding circle will be approximated using {@link #getBoundingBox}. 320 * 321 * @return {R.math.Circle2D} The world bounding circle 322 */ 323 getWorldCircle:function () { 324 var c = this.getBoundingCircle(); 325 326 if (c === null) { 327 c = R.math.Circle2D.approximateFromRectangle(this.getBoundingBox()); 328 this.wCircle.set(c); 329 c.destroy(); 330 } else { 331 this.wCircle.set(c); 332 } 333 334 var rPos = R.math.Point2D.create(this.getRenderPosition()).add(this.originNeg); 335 this.wCircle.offset(rPos); 336 rPos.destroy(); 337 return this.wCircle; 338 }, 339 340 /** 341 * Get an axis aligned world bounding box for the object. This bounding box 342 * is ensured to encompass the entire object. 343 * @return {R.math.Rectangle2D} 344 */ 345 getAABB:function () { 346 // Start with the world bounding box and transform it 347 var bb = R.math.Rectangle2D.create(this.getBoundingBox()); 348 349 // Transform the world box 350 var txfm = this.getTransformationMatrix(); 351 352 var p1 = bb.getTopLeft(); 353 var p2 = R.math.Point2D.create(bb.getTopLeft()); 354 p2.x += bb.getDims().x; 355 var p3 = bb.getBottomRight(); 356 var p4 = R.math.Point2D.create(bb.getTopLeft()); 357 p4.y += bb.getDims().y; 358 var pts = [p1.transform(txfm), p2.transform(txfm), p3.transform(txfm), p4.transform(txfm)]; 359 360 // Now find the AABB of the points 361 R.math.Math2D.getBoundingBox(pts, this.AABB); 362 363 bb.destroy(); 364 p2.destroy(); 365 p3.destroy(); 366 367 return this.AABB; 368 }, 369 370 /** 371 * Set the convex hull used for collision. The {@link R.components.ConvexCollider ConvexCollider} component 372 * uses the collision hull to perform the collision testing. 373 * @param convexHull {R.collision.ConvexHull} The convex hull object 374 */ 375 setCollisionHull:function (convexHull) { 376 Assert(convexHull instanceof R.collision.ConvexHull, "setCollisionHull() - not ConvexHull!"); 377 this.collisionHull = convexHull; 378 this.collisionHull.setGameObject(this); 379 this.genHull = false; 380 this.markDirty(); 381 }, 382 383 /** 384 * Get the convex hull used for collision testing with a {@link R.components.ConvexCollider ConvexCollider} 385 * component. If no collision hull has been assigned, a {@link R.collision.OBBHull OBBHull} will 386 * be created and returned. 387 * 388 * @return {R.collision.ConvexHull} 389 */ 390 getCollisionHull:function () { 391 if (this.collisionHull == null) { 392 this.collisionHull = R.collision.OBBHull.create(this.getBoundingBox()); 393 394 // A flag indicating the hull was auto-generated 395 this.genHull = true; 396 } 397 398 return this.collisionHull; 399 }, 400 401 /** 402 * Get the default transform component. 403 * @return {R.components.Transform2D} 404 */ 405 getDefaultTransformComponent:function () { 406 return this.defaultTxfmComponent; 407 }, 408 409 /** 410 * Set, or override, the default transformation component. 411 * @param transformComponent {R.components.Transform2D} 412 */ 413 setDefaultTransformComponent:function (transformComponent) { 414 Assert(transformComponent && transformComponent instanceof R.components.Transform2D, "Default transform component not R.components.Transform2D or subclass"); 415 416 // If this is the component created by the system, we can just destroy it 417 if (this.defaultTxfmComponent && this.defaultTxfmComponent.getName() === "DTXFM__") { 418 this.remove(this.defaultTxfmComponent); 419 this.defaultTxfmComponent.destroy(); 420 } 421 422 this.defaultTxfmComponent = transformComponent; 423 }, 424 425 /** 426 * Set the position of the object 427 * @param point {R.math.Point2D|Number} The position of the object, or a simple X coordinate 428 * @param [y] {Number} A Y coordinate if <tt>point</tt> is a number 429 */ 430 setPosition:function (point, y) { 431 this.getDefaultTransformComponent().getPosition().set(point, y); 432 this.markDirty(); 433 }, 434 435 /** 436 * Get the position of the object. 437 * @return {R.math.Point2D} The position 438 */ 439 getPosition:function () { 440 return this.getDefaultTransformComponent().getPosition(); 441 }, 442 443 /** 444 * Get the position of the object, at its origin. 445 * @return {R.math.Point2D} The position 446 */ 447 getOriginPosition:function () { 448 return this.originPos.set(this.getPosition()).add(this.getOrigin()); 449 }, 450 451 /** 452 * Get the render position of the object. 453 * @return {R.math.Point2D} 454 */ 455 getRenderPosition:function () { 456 return this.getDefaultTransformComponent().getRenderPosition(); 457 }, 458 459 /** 460 * Get the last position the object was rendered at. 461 * @return {R.math.Point2D} 462 */ 463 getLastPosition:function () { 464 return this.getDefaultTransformComponent().getLastPosition(); 465 }, 466 467 /** 468 * Set the rotation of the object 469 * @param angle {Number} The rotation angle 470 */ 471 setRotation:function (angle) { 472 this.getDefaultTransformComponent().setRotation(angle); 473 this.markDirty(); 474 }, 475 476 /** 477 * Get the rotation of the object 478 * @return {Number} Angle in degrees 479 */ 480 getRotation:function () { 481 return this.getDefaultTransformComponent().getRotation(); 482 }, 483 484 /** 485 * Get the world adjusted rotation of the object 486 * @return {Number} Angle in degrees 487 */ 488 getRenderRotation:function () { 489 return this.getDefaultTransformComponent().getRenderRotation(); 490 }, 491 492 /** 493 * Set the scale of the object along the X and Y axis in the scaling matrix 494 * @param scaleX {Number} The scale along the X axis 495 * @param [scaleY] {Number} Optional scale along the Y axis. If no value is provided 496 * <tt>scaleX</tt> will be used to perform a uniform scale. 497 */ 498 setScale:function (scaleX, scaleY) { 499 this.getDefaultTransformComponent().setScale(scaleX, scaleY); 500 this.markDirty(); 501 }, 502 503 /** 504 * Get the scale of the object along both the X and Y axis. 505 * @return {R.math.Vector2D} 506 */ 507 getScale:function () { 508 return this.getDefaultTransformComponent().getScale(); 509 }, 510 511 /** 512 * Get the scale of the object along the X axis 513 * @return {Number} 514 */ 515 getScaleX:function () { 516 return this.getDefaultTransformComponent().getScaleX(); 517 }, 518 519 /** 520 * Get the scale of the object along the Y axis. 521 * @return {Number} 522 */ 523 getScaleY:function () { 524 return this.getDefaultTransformComponent().getScaleY(); 525 }, 526 527 /** 528 * Set the depth at which this object will render to 529 * the context. The lower the z-index, the further 530 * away from the front the object will draw. 531 * 532 * @param zIndex {Number} The z-index of this object 533 */ 534 setZIndex:function (zIndex) { 535 if (this.getRenderContext() && this.getRenderContext().swapBins) { 536 this.getRenderContext().swapBins(this, this.zIndex, zIndex); 537 } 538 this.zIndex = zIndex; 539 if (this.getRenderContext()) { 540 this.getRenderContext().sort(); 541 } 542 this.markDirty(); 543 }, 544 545 /** 546 * Get the depth at which this object will render to 547 * the context. 548 * 549 * @return {Number} 550 */ 551 getZIndex:function () { 552 return this.zIndex; 553 }, 554 555 /** 556 * Returns a bean which represents the read or read/write properties 557 * of the object. 558 * 559 * @return {Object} The properties object 560 */ 561 getProperties:function () { 562 var self = this; 563 var prop = this.base(self); 564 return $.extend(prop, { 565 "ZIndex":[function () { 566 return self.getZIndex(); 567 }, function (i) { 568 self.setZIndex(parseInt(i)); 569 }, true], 570 "BoundingBox":[function () { 571 return self.getBoundingBox().toString(); 572 }, null, false], 573 "WorldBox":[function () { 574 return self.getWorldBox().toString(); 575 }, null, false], 576 "Position":[function () { 577 return self.getPosition().toString(); 578 }, function (i) { 579 var p = i.split(","); 580 self.setPosition(R.math.Point2D.create(parseFloat(p[0]), parseFloat(p[1]))); 581 }, true], 582 "Origin":[function () { 583 return self.getOrigin().toString(); 584 }, function (i) { 585 var p = i.split(","); 586 self.setOrigin(parseFloat(p[0]), parseFloat(p[1])); 587 }, true], 588 "RenderPos":[function () { 589 return self.getRenderPosition().toString() 590 }, null, false], 591 "Rotation":[function () { 592 return self.getRotation(); 593 }, function (i) { 594 self.setRotation(parseFloat(i)); 595 }, true], 596 "ScaleX":[function () { 597 return self.getScaleX(); 598 }, function (i) { 599 self.setScale(parseFloat(i), self.getScaleY()); 600 }, true], 601 "ScaleY":[function () { 602 return self.getScaleY(); 603 }, function (i) { 604 self.setScale(self.getScaleX(), parseFloat(i)); 605 }, true] 606 }); 607 } 608 609 }, /** @scope R.objects.Object2D.prototype */ { 610 /** 611 * Get the class name of this object 612 * 613 * @return {String} "R.objects.Object2D" 614 */ 615 getClassName:function () { 616 return "R.objects.Object2D"; 617 }, 618 619 /** 620 * Get a properties object with values for the given object. 621 * @param obj {R.objects.Object2D} The object to query 622 * @param [defaults] {Object} Default values that don't need to be serialized unless 623 * they are different. 624 * @return {Object} 625 */ 626 serialize:function (obj, defaults) { 627 // Defaults for object properties which can be skipped if no different 628 defaults = defaults || []; 629 $.extend(defaults, { 630 "Position":"0.00,0.00", 631 "Origin":"0.00,0.00", 632 "Rotation":"0", 633 "ScaleX":"1", 634 "ScaleY":"1", 635 "Action":"" 636 }); 637 var propObj = R.engine.PooledObject.serialize(obj, defaults); 638 if (obj.getDefaultTransformComponent().getName() !== "DTXFM__") { 639 // They assigned a different transform component, make sure to export it 640 propObj.DTXFM_COMP = obj.getDefaultTransformComponent().constructor.getClassName(); 641 propObj.DTXFM_NAME = obj.getDefaultTransformComponent().getName(); 642 } 643 return propObj; 644 }, 645 646 /** 647 * Deserialize the object back into a 2d object. 648 * @param obj {Object} The object to deserialize 649 * @param [clazz] {Class} The object class to populate 650 * @return {R.objects.Object2D} The object which was deserialized 651 */ 652 deserialize:function (obj, clazz) { 653 // Is there a special transform component assigned to this object? 654 var txfmComponent; 655 if (obj.DTXFM_COMP) { 656 txfmComponent = R.getClassForName(obj.DTXFM_COMP).create(obj.DTXFM_NAME); 657 delete obj.DTXFM_COMP; 658 delete obj.DTXFM_NAME; 659 } 660 661 // Now we can create the class 662 clazz = clazz || R.objects.Object2D.create(obj.name, txfmComponent); 663 R.engine.PooledObject.deserialize(obj, clazz); 664 665 return clazz; 666 }, 667 668 /** 669 * The axis of rotation 670 * @private 671 */ 672 ROTATION_AXIS:$V([0, 0, 1]) 673 }); 674 675 };