1 /** 2 * Copyright (C) 2009-2011 Klaus Reimer <k@ailis.de> 3 * See LICENSE.txt for licensing information. 4 * 5 * @require twodee.js 6 * @require twodee/Object.js 7 */ 8 9 /** 10 * Constructs a new scene node. 11 * 12 * @constructor 13 * @extends {twodee.Object} 14 * @class 15 * A scene node. Can be used directly to create invisible group nodes or can 16 * be extended to implement other node types. 17 */ 18 twodee.SceneNode = function() 19 { 20 twodee.Object.call(this); 21 this.transform = new twodee.Matrix(); 22 this.collisions = {}; 23 this.previousCollisions = {}; 24 25 this.id = twodee.SceneNode.counter++; 26 }; 27 twodee.inherit(twodee.SceneNode, twodee.Object); 28 29 /** 30 * Instance counter. 31 * 32 * @type {number} 33 */ 34 twodee.SceneNode.counter = 0; 35 36 /** 37 * The node polygon ID. 38 * 39 * @private 40 * @type {number} 41 */ 42 twodee.SceneNode.prototype.id = 0; 43 44 /** 45 * The parent node. Can be null if there is none. 46 * 47 * @private 48 * @type {?twodee.SceneNode} 49 */ 50 twodee.SceneNode.prototype.parentNode = null; 51 52 /** 53 * The next sibling node. Can be null if there is none. 54 * 55 * @private 56 * @type {?twodee.SceneNode} 57 */ 58 twodee.SceneNode.prototype.nextSibling = null; 59 60 /** 61 * The previous sibling node. Can be null if there is none. 62 * 63 * @private 64 * @type {?twodee.SceneNode} 65 */ 66 twodee.SceneNode.prototype.previousSibling = null; 67 68 /** 69 * The first child node. Can be null if there is none. 70 * 71 * @private 72 * @type {?twodee.SceneNode} 73 */ 74 twodee.SceneNode.prototype.firstChild = null; 75 76 /** 77 * The last child node. Can be null if there is none. 78 * 79 * @private 80 * @type {?twodee.SceneNode} 81 */ 82 twodee.SceneNode.prototype.lastChild = null; 83 84 /** 85 * The transformation of this node. 86 * 87 * @private 88 * @type {?twodee.Matrix} 89 */ 90 twodee.SceneNode.prototype.transform = null; 91 92 /** 93 * The effective transformation of this node. 94 * 95 * @private 96 * @type {?twodee.Matrix} 97 */ 98 twodee.SceneNode.prototype.effectiveTransform = null; 99 100 /** 101 * The node bounds. 102 * 103 * @private 104 * @type {?twodee.Polygon} 105 */ 106 twodee.SceneNode.prototype.bounds = null; 107 108 /** 109 * The collision type. 0 = None. 110 * 111 * @private 112 * @type {number} 113 */ 114 twodee.SceneNode.prototype.collisionType = 0; 115 116 /** 117 * The collision mask. 118 * 119 * @private 120 * @type {number} 121 */ 122 twodee.SceneNode.prototype.collisionMask = 0; 123 124 /** 125 * Map with nodes which collided with this one. 126 * 127 * @private 128 * @type {!Object.<string, twodee.SceneNode>} 129 */ 130 twodee.SceneNode.prototype.collisions; 131 132 /** 133 * Map with nodes which collided previously with this one. 134 * 135 * @private 136 * @type {!Object.<string, twodee.SceneNode>} 137 */ 138 twodee.SceneNode.prototype.previousCollisions; 139 140 /** 141 * The physics model. 142 * 143 * @private 144 * @type {?twodee.Physics} 145 */ 146 twodee.SceneNode.prototype.physics = null; 147 148 /** 149 * If node is enabled or not. 150 * 151 * @private 152 * @type {boolean} 153 */ 154 twodee.SceneNode.prototype.enabled = true; 155 156 /** 157 * The opacity. 158 * 159 * @private 160 * @type {number} 161 */ 162 twodee.SceneNode.prototype.opacity = 1; 163 164 /** 165 * Returns the node id. 166 * 167 * @return {number} The node id 168 */ 169 twodee.SceneNode.prototype.getId = function() 170 { 171 return this.id; 172 }; 173 174 /** 175 * Returns the first child node of this node. If the node has no child nodes 176 * then null is returned. 177 * 178 * @return {twodee.SceneNode} 179 * The first child node or null if there are no child nodes 180 */ 181 twodee.SceneNode.prototype.getFirstChild = function() 182 { 183 return this.firstChild; 184 }; 185 186 /** 187 * Returns the last child node of this node. If the node has no child nodes 188 * then null is returned. 189 * 190 * @return {twodee.SceneNode} 191 * The last child node or null if there are no child nodes 192 */ 193 twodee.SceneNode.prototype.getLastChild = function() 194 { 195 return this.lastChild; 196 }; 197 198 /** 199 * Returns the next sibling of this node. If the node is the last child node 200 * of its parent then null is returned because there can't be a next 201 * sibling. 202 * 203 * @return {twodee.SceneNode} 204 * The next sibling of this node or null if there is no next sibling 205 */ 206 twodee.SceneNode.prototype.getNextSibling = function() 207 { 208 return this.nextSibling; 209 }; 210 211 /** 212 * Returns the parent node. If the node has no root node yet then null is 213 * returned. 214 * 215 * @return {twodee.SceneNode} 216 * The parent node or null if there is none 217 */ 218 twodee.SceneNode.prototype.getParentNode = function() 219 { 220 return this.parentNode; 221 }; 222 223 /** 224 * Returns the previous sibling of this node. If the node is the first child 225 * node of its parent then null is returned because there can't be a 226 * previous sibling. 227 * 228 * @return {twodee.SceneNode} 229 * The previous sibling of this node or null if there is no previous 230 * sibling 231 */ 232 twodee.SceneNode.prototype.getPreviousSibling = function() 233 { 234 return this.previousSibling; 235 }; 236 237 /** 238 * Inserts a new child node before the specified reference node. If the new 239 * node was already connected to a parent then it is disconnected from this 240 * parent first. 241 * 242 * @param {twodee.SceneNode} insertNode 243 * The new node to insert 244 * @param {twodee.SceneNode} referenceNode 245 * The reference node 246 */ 247 twodee.SceneNode.prototype.insertBefore = function(insertNode, referenceNode) 248 { 249 var oldParent, oldPrevious; 250 251 if (!insertNode) throw new Error("newNode must be set"); 252 if (!referenceNode) throw new Error("referenceNode must be set"); 253 if (insertNode == this) 254 throw new Error("newNode can not be a child of itself"); 255 256 // Verify that reference node is our child 257 if (referenceNode.parentNode != this) 258 throw new Error("Reference node is not my child node"); 259 260 // Remove from old parent if there is one 261 oldParent = insertNode.parentNode; 262 if (oldParent) oldParent.removeChild(insertNode); 263 264 // Insert the node 265 oldPrevious = referenceNode.previousSibling; 266 if (oldPrevious) 267 oldPrevious.nextSibling = insertNode; 268 else 269 this.firstChild = insertNode; 270 referenceNode.previousSibling = insertNode; 271 insertNode.previousSibling = oldPrevious; 272 insertNode.nextSibling = referenceNode; 273 insertNode.parentNode = this; 274 }; 275 276 /** 277 * Checks if this node has child nodes. 278 * 279 * @return {boolean} 280 * True if this node has child nodes, false if not 281 */ 282 twodee.SceneNode.prototype.hasChildNodes = function() 283 { 284 return !!this.firstChild; 285 }; 286 287 /** 288 * Removes this node from its parent. 289 */ 290 twodee.SceneNode.prototype.remove = function() 291 { 292 var parentNode; 293 294 parentNode = this.parentNode; 295 if (parentNode) parentNode.removeChild(this); 296 }; 297 298 /** 299 * Removes all child nodes. 300 */ 301 twodee.SceneNode.prototype.removeChildren = function() 302 { 303 while (this.firstChild) this.removeChild(this.firstChild); 304 }; 305 306 /** 307 * Removes the specified child node from this node. 308 * 309 * @param {twodee.SceneNode} node 310 * The node to remove 311 */ 312 twodee.SceneNode.prototype.removeChild = function(node) 313 { 314 var next, prev; 315 316 if (!node) throw new Error("node must be set"); 317 318 // Verify that node is our child 319 if (node.parentNode != this) throw new Error("node is not my child node"); 320 321 // Remove node from linked list 322 next = node.nextSibling; 323 prev = node.previousSibling; 324 if (next) next.previousSibling = prev; 325 if (prev) prev.nextSibling = next; 326 327 // Correct first/last reference 328 if (node == this.firstChild) this.firstChild = next; 329 if (node == this.lastChild) this.lastChild = prev; 330 331 // Remove all references from node 332 node.parentNode = null; 333 node.nextSibling = null; 334 node.previousSibling = null; 335 }; 336 337 /** 338 * Appends the specified node as a child node to this node. If the node was 339 * previously connected to a different parent node then it is first 340 * disconnected from this parent. 341 * 342 * @param {twodee.SceneNode} node 343 * The node to append to this node 344 */ 345 twodee.SceneNode.prototype.appendChild = function(node) 346 { 347 var oldParent, previousSibling; 348 349 if (!node) throw new Error("node must not be null"); 350 if (node == this) throw new Error("node can not be a child of itself"); 351 352 // Remove from old parent if there is one 353 oldParent = node.parentNode; 354 if (oldParent) oldParent.removeChild(node); 355 356 // Append the child 357 previousSibling = node.previousSibling = this.lastChild; 358 if (previousSibling) previousSibling.nextSibling = node; 359 this.lastChild = node; 360 if (!this.firstChild) this.firstChild = node; 361 node.parentNode = this; 362 }; 363 364 /** 365 * Replaces the specified old node with the specified new node. If the new 366 * node was already connected to a parent then it is disconnected from this 367 * parent first. 368 * 369 * @param {twodee.SceneNode} oldNode 370 * The old node to be replaced by the new one 371 * @param {twodee.SceneNode} newNode 372 * The new node to replace the old one 373 */ 374 twodee.SceneNode.prototype.replaceChild = function(oldNode, newNode) 375 { 376 var next; 377 378 if (!oldNode) throw new Error("oldNode must be set"); 379 if (!newNode) throw new Error("newNode must be set"); 380 if (newNode == this) 381 throw new Error("node can not be a child of itself"); 382 383 // Verify that old node is our child 384 if (oldNode.parentNode != this) 385 throw new Error("node is not my child node"); 386 387 // New node is the same as the old node then do nothing 388 if (newNode != oldNode) 389 { 390 next = oldNode.nextSibling; 391 this.removeChild(oldNode); 392 if (next == null) 393 this.appendChild(newNode); 394 else 395 this.insertBefore(newNode, next); 396 } 397 }; 398 399 /** 400 * Transforms the node according to its effective transformation (Calculated 401 * from its local transformation and the effective transformation of its 402 * parent). 403 * 404 * Do not call this method yourself. It is triggered by the scene. 405 * 406 * @param {twodee.Matrix} baseTransform 407 * The base transformation to apply to nodes without a parent. 408 * @return {twodee.Matrix} The calculated effetive transformation of this node 409 */ 410 twodee.SceneNode.prototype.updateTransformation = function(baseTransform) 411 { 412 var parentNode, transform, bounds; 413 414 // If node has a parent node then apply the effective parent transformation 415 // to effective node transformation 416 parentNode = this.parentNode; 417 if (parentNode) 418 { 419 this.effectiveTransform = parentNode.effectiveTransform.copy(this.effectiveTransform); 420 transform = this.effectiveTransform.transform(this.transform); 421 } 422 else 423 { 424 // If no parent node is present then effective transform is 425 // the local transform 426 this.effectiveTransform = baseTransform.copy(this.effectiveTransform); 427 transform = this.effectiveTransform.transform(this.transform); 428 } 429 430 // Transform the bounds if this node has bounds 431 bounds = this.bounds; 432 if (bounds) bounds.setTransform(transform); 433 434 // Return the effective transformation 435 return transform; 436 }; 437 438 /** 439 * Returns the current transformation matrix. 440 * 441 * @return {twodee.Matrix} 442 * The current transformation matrix 443 */ 444 twodee.SceneNode.prototype.getTransform = function() 445 { 446 return this.transform; 447 }; 448 449 /** 450 * Sets the bounds of this node. Specify null to use no bounds. 451 * 452 * @param {twodee.Polygon} bounds 453 * The node bounds to set 454 */ 455 twodee.SceneNode.prototype.setBounds = function(bounds) 456 { 457 this.bounds = bounds.copy(); 458 }; 459 460 /** 461 * Returns the bounds of this node. 462 * 463 * @return {twodee.Polygon} The node bounds 464 */ 465 twodee.SceneNode.prototype.getBounds = function() 466 { 467 return this.bounds; 468 }; 469 470 /** 471 * Sets the collision type of the node. 472 * 473 * @param {number} collisionType 474 * The collision type to set. 0 means that the node is not 475 * collidable at all. 476 */ 477 twodee.SceneNode.prototype.setCollisionType = function(collisionType) 478 { 479 this.collisionType = collisionType; 480 }; 481 482 /** 483 * Returns the collisiton type of the node. May return 0 of node is 484 * not collidable. 485 * 486 * @return {number} The collision type of the node 487 */ 488 twodee.SceneNode.prototype.getCollisionType = function() 489 { 490 return this.collisionType; 491 }; 492 493 /** 494 * Sets the collision bit mask of the node. 495 * 496 * @param {number} collisionMask 497 * The collision bit mask to set. 498 */ 499 twodee.SceneNode.prototype.setCollisionMask = function(collisionMask) 500 { 501 this.collisionMask = collisionMask; 502 }; 503 504 /** 505 * Returns the collisiton bit mask of the node. 506 * 507 * @return {number} The collision bit mask 508 */ 509 twodee.SceneNode.prototype.getCollisionMask = function() 510 { 511 return this.collisionMask; 512 }; 513 514 /** 515 * Checks if the node is collidable. It is only collidable when it has 516 * a collision type and bounds set. 517 * 518 * @return {boolean} True if node is collidable, false if not 519 */ 520 twodee.SceneNode.prototype.isCollidable = function() 521 { 522 return !!this.collisionType && !!this.bounds; 523 }; 524 525 /** 526 * Checks if this node collides with the specified node. 527 * 528 * Do not call this method yourself, it's called by the scene. 529 * 530 * @param {twodee.SceneNode} other 531 * The other scene node 532 * @return {boolean} True if nodes collide, false if not 533 */ 534 twodee.SceneNode.prototype.collidesWith = function(other) 535 { 536 // Can't collide if one of the nodes is not in scene 537 if (!this.parentNode) return false; 538 if (!other.getParentNode()) return false; 539 540 // Check if nodes can collide according to the collision type and 541 // the collision mask. 542 if (!(this.collisionMask & other.collisionType)) return false; 543 544 // Check if bounds collide 545 return this.bounds.collidesWith(other.bounds); 546 }; 547 548 /** 549 * Informs the node that it has collided with the specified node. 550 * 551 * Do not call this method yourself, it is called by the scene. 552 * 553 * @param {twodee.SceneNode} other 554 * The node this one has collided with 555 */ 556 twodee.SceneNode.prototype.collide = function(other) 557 { 558 this.collisions["" + other.getId()] = other; 559 }; 560 561 /** 562 * Finishes the collision detection. This must be called after rendering of 563 * the scene is complete. It checks which collisions are new or obsolete and 564 * sends the required signals. 565 * 566 * Don't call this method yourself, it is done by the Scene. 567 */ 568 twodee.SceneNode.prototype.processCollisions = function() 569 { 570 var /** @type {string} */ id, collisions, previousCollisions; 571 572 // Create shorthand variables 573 collisions = this.collisions; 574 previousCollisions = this.previousCollisions; 575 576 // Check for new collisions 577 if (this.parentNode) 578 { 579 for (id in collisions) 580 { 581 if (!(id in previousCollisions)) 582 { 583 if (collisions[id].parentNode) 584 this.sendSignal("collisionStarted", this, collisions[id]); 585 586 // Process no more collisions if we were removed from the scene 587 if (!this.parentNode) break; 588 } 589 } 590 } 591 592 // Check for old collisions 593 for (id in previousCollisions) 594 { 595 if (!(id in collisions)) 596 { 597 this.sendSignal("collisionStopped", this, previousCollisions[id]); 598 } 599 delete previousCollisions[id]; 600 } 601 602 // Remember current collisions for next run 603 for (id in collisions) 604 { 605 previousCollisions[id] = this.collisions[id]; 606 delete collisions[id]; 607 } 608 }; 609 610 /** 611 * Sets the physics model. Specify null to remove the existing physics model. 612 * 613 * @param {twodee.Physics} physics 614 * The physics model to set 615 */ 616 twodee.SceneNode.prototype.setPhysics = function(physics) 617 { 618 this.physics = physics; 619 }; 620 621 /** 622 * Returns the physics model. May return null if node has no physics model. 623 * 624 * @return {twodee.Physics} The physics model or null if not present 625 */ 626 twodee.SceneNode.prototype.getPhysics = function() 627 { 628 return this.physics; 629 }; 630 631 /** 632 * Updates the node with the specified time delta. Default implementation is 633 * processing the physics model if present. 634 * 635 * @param {number} delta 636 * The time elapsed since the last scene update (in milliseconds) 637 */ 638 twodee.SceneNode.prototype.update = function(delta) 639 { 640 var physics; 641 642 // Process the physics model if present 643 physics = this.physics; 644 if (physics) physics.process(this, delta); 645 }; 646 647 /** 648 * Renders the node. Default implementation does nothing. 649 * 650 * @param {CanvasRenderingContext2D} g 651 * The graphics context 652 * @param {twodee.Matrix} transform 653 * The effective transformation. This is already applied to the 654 * node bounds. 655 */ 656 twodee.SceneNode.prototype.render = function(g, transform) 657 { 658 // Empty 659 }; 660 661 /** 662 * Enables the node. 663 */ 664 twodee.SceneNode.prototype.enable = function() 665 { 666 this.setEnabled(true); 667 }; 668 669 /** 670 * Disables the node. 671 */ 672 twodee.SceneNode.prototype.disable = function() 673 { 674 this.setEnabled(false); 675 }; 676 677 /** 678 * Enables or disables the node. 679 * 680 * @param {boolean} enabled 681 * True to enable the node, false to disable it 682 */ 683 twodee.SceneNode.prototype.setEnabled = function(enabled) 684 { 685 this.enabled = enabled; 686 }; 687 688 /** 689 * Checks if node is enabled or not. 690 * 691 * @return {boolean} True if node is enabled, false if not 692 */ 693 twodee.SceneNode.prototype.isEnabled = function() 694 { 695 return this.enabled; 696 }; 697 698 /** 699 * Sets the opacity of the node. 700 * 701 * @param {number} opacity 702 * The opacity to set. 1.0 = Fully solid, 0.0 = fully transparent 703 */ 704 twodee.SceneNode.prototype.setOpacity = function(opacity) 705 { 706 this.opacity = opacity; 707 }; 708 709 /** 710 * Returns the current opacity of the node. 711 * 712 * @return {number} 713 * The node opacity. 1.0 = Fully solid, 0.0 = fully transparent 714 */ 715 twodee.SceneNode.prototype.getOpacity = function() 716 { 717 return this.opacity; 718 }; 719