/src/away3d/containers/ObjectContainer3D.as
ActionScript | 776 lines | 470 code | 132 blank | 174 comment | 73 complexity | 75fe9f2b42b83d1c37b4f967d6d09517 MD5 | raw file
1package away3d.containers 2{ 3 import away3d.arcane; 4 import away3d.core.base.Object3D; 5 import away3d.core.partition.Partition3D; 6 import away3d.events.Object3DEvent; 7 import away3d.events.Scene3DEvent; 8 import away3d.library.assets.AssetType; 9 import away3d.library.assets.IAsset; 10 11 import flash.events.Event; 12 import flash.geom.Matrix3D; 13 import flash.geom.Vector3D; 14 15 use namespace arcane; 16 17 /** 18 * Dispatched when the scene transform matrix of the 3d object changes. 19 * 20 * @eventType away3d.events.Object3DEvent 21 * @see #sceneTransform 22 */ 23 [Event(name="scenetransformChanged", type="away3d.events.Object3DEvent")] 24 25 /** 26 * Dispatched when the parent scene of the 3d object changes. 27 * 28 * @eventType away3d.events.Object3DEvent 29 * @see #scene 30 */ 31 [Event(name="sceneChanged", type="away3d.events.Object3DEvent")] 32 33 /** 34 * Dispatched when a user moves the cursor while it is over the 3d object. 35 * 36 * @eventType away3d.events.MouseEvent3D 37 */ 38 [Event(name="mouseMove3d", type="away3d.events.MouseEvent3D")] 39 40 /** 41 * Dispatched when a user presses the left hand mouse button while the cursor is over the 3d object. 42 * 43 * @eventType away3d.events.MouseEvent3D 44 */ 45 [Event(name="mouseDown3d", type="away3d.events.MouseEvent3D")] 46 47 /** 48 * Dispatched when a user releases the left hand mouse button while the cursor is over the 3d object. 49 * 50 * @eventType away3d.events.MouseEvent3D 51 */ 52 [Event(name="mouseUp3d", type="away3d.events.MouseEvent3D")] 53 54 /** 55 * Dispatched when a user moves the cursor over the 3d object. 56 * 57 * @eventType away3d.events.MouseEvent3D 58 */ 59 [Event(name="mouseOver3d", type="away3d.events.MouseEvent3D")] 60 61 /** 62 * Dispatched when a user moves the cursor away from the 3d object. 63 * 64 * @eventType away3d.events.MouseEvent3D 65 */ 66 [Event(name="mouseOut3d", type="away3d.events.MouseEvent3D")] 67 68 /** 69 * ObjectContainer3D is the most basic scene graph node. It can contain other ObjectContainer3Ds. 70 * 71 * ObjectContainer3D can have its own scene partition assigned. However, when assigned to a different scene, 72 * it will loose any partition information, since partitions are tied to a scene. 73 */ 74 public class ObjectContainer3D extends Object3D implements IAsset 75 { 76 /** @private */ 77 arcane var _ancestorsAllowMouseEnabled:Boolean; 78 arcane var _isRoot:Boolean; 79 80 protected var _scene:Scene3D; 81 protected var _parent:ObjectContainer3D; 82 protected var _sceneTransform:Matrix3D = new Matrix3D(); 83 protected var _sceneTransformDirty:Boolean = true; 84 // these vars allow not having to traverse the scene graph to figure out what partition is set 85 protected var _explicitPartition:Partition3D; // what the user explicitly set as the partition 86 protected var _implicitPartition:Partition3D; // what is inherited from the parents if it doesn't have its own explicitPartition 87 protected var _mouseEnabled:Boolean; 88 private var _sceneTransformChanged:Object3DEvent; 89 private var _scenechanged:Object3DEvent; 90 private var _children:Vector.<ObjectContainer3D> = new Vector.<ObjectContainer3D>(); 91 private var _mouseChildren:Boolean = true; 92 private var _oldScene:Scene3D; 93 private var _inverseSceneTransform:Matrix3D = new Matrix3D(); 94 private var _inverseSceneTransformDirty:Boolean = true; 95 private var _scenePosition:Vector3D = new Vector3D(); 96 private var _scenePositionDirty:Boolean = true; 97 private var _explicitVisibility:Boolean = true; 98 private var _implicitVisibility:Boolean = true; 99 private var _listenToSceneTransformChanged:Boolean; 100 private var _listenToSceneChanged:Boolean; 101 // visibility passed on from parents 102 103 protected var _ignoreTransform:Boolean = false; 104 105 /** 106 * Does not apply any transformations to this object. Allows static objects to be described in world coordinates without any matrix calculations. 107 */ 108 public function get ignoreTransform():Boolean 109 { 110 return _ignoreTransform; 111 } 112 113 public function set ignoreTransform(value:Boolean):void 114 { 115 _ignoreTransform = value; 116 _sceneTransformDirty = !value; 117 _inverseSceneTransformDirty = !value; 118 _scenePositionDirty = !value; 119 120 if (!value) { 121 _sceneTransform.identity(); 122 _scenePosition.setTo(0, 0, 0); 123 } 124 } 125 126 /** 127 * @private 128 * The space partition used for this object, possibly inherited from its parent. 129 */ 130 arcane function get implicitPartition():Partition3D 131 { 132 return _implicitPartition; 133 } 134 135 arcane function set implicitPartition(value:Partition3D):void 136 { 137 if (value == _implicitPartition) 138 return; 139 140 var i:uint; 141 var len:uint = _children.length; 142 var child:ObjectContainer3D; 143 144 _implicitPartition = value; 145 146 while (i < len) { 147 child = _children[i++]; 148 149 // assign implicit partition if no explicit one is given 150 if (!child._explicitPartition) 151 child.implicitPartition = value; 152 } 153 } 154 155 /** @private */ 156 arcane function get isVisible():Boolean 157 { 158 return _implicitVisibility && _explicitVisibility; 159 } 160 161 /** @private */ 162 arcane function setParent(value:ObjectContainer3D):void 163 { 164 _parent = value; 165 166 updateMouseChildren(); 167 168 if (value == null) { 169 scene = null; 170 return; 171 } 172 173 notifySceneTransformChange(); 174 notifySceneChange(); 175 } 176 177 private function notifySceneTransformChange():void 178 { 179 if (_sceneTransformDirty || _ignoreTransform) 180 return; 181 182 invalidateSceneTransform(); 183 184 var i:uint; 185 var len:uint = _children.length; 186 187 //act recursively on child objects 188 while (i < len) 189 _children[i++].notifySceneTransformChange(); 190 191 //trigger event if listener exists 192 if (_listenToSceneTransformChanged) { 193 if (!_sceneTransformChanged) 194 _sceneTransformChanged = new Object3DEvent(Object3DEvent.SCENETRANSFORM_CHANGED, this); 195 dispatchEvent(_sceneTransformChanged); 196 } 197 } 198 199 private function notifySceneChange():void 200 { 201 notifySceneTransformChange(); 202 203 var i:uint; 204 var len:uint = _children.length; 205 206 //act recursively on child objects 207 while (i < len) 208 _children[i++].notifySceneChange(); 209 210 if (_listenToSceneChanged) { 211 if (!_scenechanged) 212 _scenechanged = new Object3DEvent(Object3DEvent.SCENE_CHANGED, this); 213 214 dispatchEvent(_scenechanged); 215 } 216 } 217 218 protected function updateMouseChildren():void 219 { 220 if (_parent && !_parent._isRoot) { 221 // Set implicit mouse enabled if parent its children to be so. 222 _ancestorsAllowMouseEnabled = parent._ancestorsAllowMouseEnabled && _parent.mouseChildren; 223 } else 224 _ancestorsAllowMouseEnabled = mouseChildren; 225 226 // Sweep children. 227 var len:uint = _children.length; 228 for (var i:uint = 0; i < len; ++i) 229 _children[i].updateMouseChildren(); 230 } 231 232 /** 233 * Indicates whether the IRenderable should trigger mouse events, and hence should be rendered for hit testing. 234 */ 235 public function get mouseEnabled():Boolean 236 { 237 return _mouseEnabled; 238 } 239 240 public function set mouseEnabled(value:Boolean):void 241 { 242 _mouseEnabled = value; 243 updateMouseChildren(); 244 } 245 246 /** 247 * @inheritDoc 248 */ 249 override arcane function invalidateTransform():void 250 { 251 super.invalidateTransform(); 252 253 notifySceneTransformChange(); 254 } 255 256 /** 257 * Invalidates the scene transformation matrix, causing it to be updated the next time it's requested. 258 */ 259 protected function invalidateSceneTransform():void 260 { 261 _sceneTransformDirty = !_ignoreTransform; 262 _inverseSceneTransformDirty = !_ignoreTransform; 263 _scenePositionDirty = !_ignoreTransform; 264 } 265 266 /** 267 * Updates the scene transformation matrix. 268 */ 269 protected function updateSceneTransform():void 270 { 271 if (_parent && !_parent._isRoot) { 272 _sceneTransform.copyFrom(_parent.sceneTransform); 273 _sceneTransform.prepend(transform); 274 } else 275 _sceneTransform.copyFrom(transform); 276 277 _sceneTransformDirty = false; 278 } 279 280 /** 281 * 282 */ 283 public function get mouseChildren():Boolean 284 { 285 return _mouseChildren; 286 } 287 288 public function set mouseChildren(value:Boolean):void 289 { 290 _mouseChildren = value; 291 updateMouseChildren(); 292 } 293 294 /** 295 * 296 */ 297 public function get visible():Boolean 298 { 299 return _explicitVisibility; 300 } 301 302 public function set visible(value:Boolean):void 303 { 304 var len:uint = _children.length; 305 306 _explicitVisibility = value; 307 308 for (var i:uint = 0; i < len; ++i) 309 _children[i].updateImplicitVisibility(); 310 } 311 312 public function get assetType():String 313 { 314 return AssetType.CONTAINER; 315 } 316 317 /** 318 * The global position of the ObjectContainer3D in the scene. The value of the return object should not be changed. 319 */ 320 public function get scenePosition():Vector3D 321 { 322 if (_scenePositionDirty) { 323 sceneTransform.copyColumnTo(3, _scenePosition); 324 _scenePositionDirty = false; 325 } 326 327 return _scenePosition; 328 } 329 330 /** 331 * The minimum extremum of the object along the X-axis. 332 */ 333 public function get minX():Number 334 { 335 var i:uint; 336 var len:uint = _children.length; 337 var min:Number = Number.POSITIVE_INFINITY; 338 var m:Number; 339 340 while (i < len) { 341 var child:ObjectContainer3D = _children[i++]; 342 m = child.minX + child.x; 343 if (m < min) 344 min = m; 345 } 346 347 return min; 348 } 349 350 /** 351 * The minimum extremum of the object along the Y-axis. 352 */ 353 public function get minY():Number 354 { 355 var i:uint; 356 var len:uint = _children.length; 357 var min:Number = Number.POSITIVE_INFINITY; 358 var m:Number; 359 360 while (i < len) { 361 var child:ObjectContainer3D = _children[i++]; 362 m = child.minY + child.y; 363 if (m < min) 364 min = m; 365 } 366 367 return min; 368 } 369 370 /** 371 * The minimum extremum of the object along the Z-axis. 372 */ 373 public function get minZ():Number 374 { 375 var i:uint; 376 var len:uint = _children.length; 377 var min:Number = Number.POSITIVE_INFINITY; 378 var m:Number; 379 380 while (i < len) { 381 var child:ObjectContainer3D = _children[i++]; 382 m = child.minZ + child.z; 383 if (m < min) 384 min = m; 385 } 386 387 return min; 388 } 389 390 /** 391 * The maximum extremum of the object along the X-axis. 392 */ 393 public function get maxX():Number 394 { 395 // todo: this isn't right, doesn't take into account transforms 396 var i:uint; 397 var len:uint = _children.length; 398 var max:Number = Number.NEGATIVE_INFINITY; 399 var m:Number; 400 401 while (i < len) { 402 var child:ObjectContainer3D = _children[i++]; 403 m = child.maxX + child.x; 404 if (m > max) 405 max = m; 406 } 407 408 return max; 409 } 410 411 /** 412 * The maximum extremum of the object along the Y-axis. 413 */ 414 public function get maxY():Number 415 { 416 var i:uint; 417 var len:uint = _children.length; 418 var max:Number = Number.NEGATIVE_INFINITY; 419 var m:Number; 420 421 while (i < len) { 422 var child:ObjectContainer3D = _children[i++]; 423 m = child.maxY + child.y; 424 if (m > max) 425 max = m; 426 } 427 428 return max; 429 } 430 431 /** 432 * The maximum extremum of the object along the Z-axis. 433 */ 434 public function get maxZ():Number 435 { 436 var i:uint; 437 var len:uint = _children.length; 438 var max:Number = Number.NEGATIVE_INFINITY; 439 var m:Number; 440 441 while (i < len) { 442 var child:ObjectContainer3D = _children[i++]; 443 m = child.maxZ + child.z; 444 if (m > max) 445 max = m; 446 } 447 448 return max; 449 } 450 451 /** 452 * The space partition to be used by the object container and all its recursive children, unless it has its own 453 * space partition assigned. 454 */ 455 public function get partition():Partition3D 456 { 457 return _explicitPartition; 458 } 459 460 public function set partition(value:Partition3D):void 461 { 462 _explicitPartition = value; 463 464 implicitPartition = value? value : (_parent? _parent.implicitPartition : null); 465 } 466 467 /** 468 * The transformation matrix that transforms from model to world space. 469 */ 470 public function get sceneTransform():Matrix3D 471 { 472 if (_sceneTransformDirty) 473 updateSceneTransform(); 474 475 return _sceneTransform; 476 } 477 478 /** 479 * A reference to the Scene3D object to which this object belongs. 480 */ 481 public function get scene():Scene3D 482 { 483 return _scene; 484 } 485 486 public function set scene(value:Scene3D):void 487 { 488 var i:uint; 489 var len:uint = _children.length; 490 491 while (i < len) 492 _children[i++].scene = value; 493 494 if (_scene == value) 495 return; 496 497 // test to see if we're switching roots while we're already using a scene partition 498 if (value == null) 499 _oldScene = _scene; 500 501 if (_explicitPartition && _oldScene && _oldScene != _scene) 502 partition = null; 503 504 if (value) 505 _oldScene = null; 506 // end of stupid partition test code 507 508 _scene = value; 509 510 if (_scene) 511 _scene.dispatchEvent(new Scene3DEvent(Scene3DEvent.ADDED_TO_SCENE, this)); 512 else if (_oldScene) 513 _oldScene.dispatchEvent(new Scene3DEvent(Scene3DEvent.REMOVED_FROM_SCENE, this)); 514 } 515 516 /** 517 * The inverse scene transform object that transforms from world to model space. 518 */ 519 public function get inverseSceneTransform():Matrix3D 520 { 521 if (_inverseSceneTransformDirty) { 522 _inverseSceneTransform.copyFrom(sceneTransform); 523 _inverseSceneTransform.invert(); 524 _inverseSceneTransformDirty = false; 525 } 526 527 return _inverseSceneTransform; 528 } 529 530 /** 531 * The parent ObjectContainer3D to which this object's transformation is relative. 532 */ 533 public function get parent():ObjectContainer3D 534 { 535 return _parent; 536 } 537 538 /** 539 * Creates a new ObjectContainer3D object. 540 */ 541 public function ObjectContainer3D() 542 { 543 super(); 544 } 545 546 public function contains(child:ObjectContainer3D):Boolean 547 { 548 return _children.indexOf(child) >= 0; 549 } 550 551 /** 552 * Adds a child ObjectContainer3D to the current object. The child's transformation will become relative to the 553 * current object's transformation. 554 * @param child The object to be added as a child. 555 * @return A reference to the added child object. 556 */ 557 public function addChild(child:ObjectContainer3D):ObjectContainer3D 558 { 559 if (child == null) 560 throw new Error("Parameter child cannot be null."); 561 562 if (child._parent) 563 child._parent.removeChild(child); 564 565 if (!child._explicitPartition) 566 child.implicitPartition = _implicitPartition; 567 568 child.setParent(this); 569 child.scene = _scene; 570 child.notifySceneTransformChange(); 571 child.updateMouseChildren(); 572 child.updateImplicitVisibility(); 573 574 _children.push(child); 575 576 return child; 577 } 578 579 /** 580 * Adds an array of 3d objects to the scene as children of the container 581 * 582 * @param ...childarray An array of 3d objects to be added 583 */ 584 public function addChildren(...childarray):void 585 { 586 for each (var child:ObjectContainer3D in childarray) 587 addChild(child); 588 } 589 590 /** 591 * Removes a 3d object from the child array of the container 592 * 593 * @param child The 3d object to be removed 594 * @throws Error ObjectContainer3D.removeChild(null) 595 */ 596 public function removeChild(child:ObjectContainer3D):void 597 { 598 if (child == null) 599 throw new Error("Parameter child cannot be null"); 600 601 var childIndex:int = _children.indexOf(child); 602 603 if (childIndex == -1) 604 throw new Error("Parameter is not a child of the caller"); 605 606 removeChildInternal(childIndex, child); 607 } 608 609 /** 610 * Removes a 3d object from the child array of the container 611 * 612 * @param index Index of 3d object to be removed 613 */ 614 public function removeChildAt(index:uint):void 615 { 616 var child:ObjectContainer3D = _children[index]; 617 618 removeChildInternal(index, child); 619 } 620 621 private function removeChildInternal(childIndex:uint, child:ObjectContainer3D):void 622 { 623 // index is important because getChildAt needs to be regular. 624 _children.splice(childIndex, 1); 625 626 // this needs to be nullified before the callbacks! 627 child.setParent(null); 628 629 if (!child._explicitPartition) 630 child.implicitPartition = null; 631 } 632 633 /** 634 * Retrieves the child object at the given index. 635 * @param index The index of the object to be retrieved. 636 * @return The child object at the given index. 637 */ 638 public function getChildAt(index:uint):ObjectContainer3D 639 { 640 return _children[index]; 641 } 642 643 /** 644 * The amount of child objects of the ObjectContainer3D. 645 */ 646 public function get numChildren():uint 647 { 648 return _children.length; 649 } 650 651 /** 652 * @inheritDoc 653 */ 654 override public function lookAt(target:Vector3D, upAxis:Vector3D = null):void 655 { 656 super.lookAt(target, upAxis); 657 658 notifySceneTransformChange(); 659 } 660 661 override public function translateLocal(axis:Vector3D, distance:Number):void 662 { 663 super.translateLocal(axis, distance); 664 665 notifySceneTransformChange(); 666 } 667 668 /** 669 * @inheritDoc 670 */ 671 override public function dispose():void 672 { 673 if (parent) 674 parent.removeChild(this); 675 } 676 677 /** 678 * Disposes the current ObjectContainer3D including all of its children. This is a merely a convenience method. 679 */ 680 public function disposeWithChildren():void 681 { 682 dispose(); 683 684 while (numChildren > 0) 685 getChildAt(0).dispose(); 686 } 687 688 /** 689 * Clones this ObjectContainer3D instance along with all it's children, and 690 * returns the result (which will be a copy of this container, containing copies 691 * of all it's children.) 692 */ 693 override public function clone():Object3D 694 { 695 var clone:ObjectContainer3D = new ObjectContainer3D(); 696 clone.pivotPoint = pivotPoint; 697 clone.transform = transform; 698 clone.partition = partition; 699 clone.name = name; 700 701 var len:uint = _children.length; 702 703 for (var i:uint = 0; i < len; ++i) 704 clone.addChild(ObjectContainer3D(_children[i].clone())); 705 706 // todo: implement for all subtypes 707 return clone; 708 } 709 710 override public function rotate(axis:Vector3D, angle:Number):void 711 { 712 super.rotate(axis, angle); 713 714 notifySceneTransformChange(); 715 } 716 717 /** 718 * @inheritDoc 719 */ 720 override public function dispatchEvent(event:Event):Boolean 721 { 722 // maybe not the best way to fake bubbling? 723 var ret:Boolean = super.dispatchEvent(event); 724 725 if (event.bubbles) { 726 if (_parent) 727 _parent.dispatchEvent(event); 728 // if it's scene root 729 else if (_scene) 730 _scene.dispatchEvent(event); 731 } 732 733 return ret; 734 } 735 736 public function updateImplicitVisibility():void 737 { 738 var len:uint = _children.length; 739 740 _implicitVisibility = _parent._explicitVisibility && _parent._implicitVisibility; 741 742 for (var i:uint = 0; i < len; ++i) 743 _children[i].updateImplicitVisibility(); 744 } 745 746 override public function addEventListener(type:String, listener:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void 747 { 748 super.addEventListener(type, listener, useCapture, priority, useWeakReference); 749 switch (type) { 750 case Object3DEvent.SCENETRANSFORM_CHANGED: 751 _listenToSceneTransformChanged = true; 752 break; 753 case Object3DEvent.SCENE_CHANGED: 754 _listenToSceneChanged = true; 755 break; 756 } 757 } 758 759 override public function removeEventListener(type:String, listener:Function, useCapture:Boolean = false):void 760 { 761 super.removeEventListener(type, listener, useCapture); 762 763 if (hasEventListener(type)) 764 return; 765 766 switch (type) { 767 case Object3DEvent.SCENETRANSFORM_CHANGED: 768 _listenToSceneTransformChanged = false; 769 break; 770 case Object3DEvent.SCENE_CHANGED: 771 _listenToSceneChanged = false; 772 break; 773 } 774 } 775 } 776}