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