1 /**
  2  * Copyright (C) 2009-2012 Klaus Reimer <k@ailis.de>
  3  * See LICENSE.txt for licensing information
  4  * 
  5  * @require threedee.js
  6  * @use threedee/Matrix.js
  7  */
  8 
  9 /**
 10  * @constructor
 11  * Constructs a new scene node.
 12  *            
 13  * @class
 14  * A scene node. Can be used directly to create invisible group nodes or can be
 15  * extended to implement other node types.
 16  */
 17 
 18 threedee.SceneNode = function()
 19 {
 20     this.transform = new threedee.Matrix();
 21     this.updaters = [];
 22 };
 23 
 24 /** 
 25  * The registered updaters. 
 26  * @private 
 27  * @type {!Array.<!threedee.NodeUpdater>} 
 28  */
 29 threedee.SceneNode.prototype.updaters;
 30 
 31 /** 
 32  * The parent node. Can be null if there is none. 
 33  * @private 
 34  * @type {?threedee.SceneNode} 
 35  */
 36 threedee.SceneNode.prototype.parentNode = null;
 37 
 38 /** 
 39  * The next sibling node. Can be null if there is none. 
 40  * @private 
 41  * @type {?threedee.SceneNode} 
 42  */
 43 threedee.SceneNode.prototype.nextSibling = null;
 44 
 45 /** 
 46  * The previous sibling node. Can be null if there is none. 
 47  * @private 
 48  * @type {?threedee.SceneNode} 
 49  */
 50 threedee.SceneNode.prototype.previousSibling = null;
 51 
 52 /** 
 53  * The first child node. Can be null if there is none. 
 54  * @private 
 55  * @type {?threedee.SceneNode} 
 56  */
 57 threedee.SceneNode.prototype.firstChild = null;
 58 
 59 /** 
 60  * The last child node. Can be null if there is none. 
 61  * @private @type {?threedee.SceneNode} 
 62  */
 63 threedee.SceneNode.prototype.lastChild = null;
 64 
 65 /** 
 66  * The transformation of this node. 
 67  * @private 
 68  * @type {!threedee.Matrix} 
 69  */
 70 threedee.SceneNode.prototype.transform;
 71 
 72 /**
 73  * Appends the specified node as a child node to this node. If the node was
 74  * previously connected to a different parent node then it is first
 75  * disconnected from this parent.
 76  * 
 77  * @param {!threedee.SceneNode} node
 78  *            The node to append to this node
 79  */
 80 threedee.SceneNode.prototype.appendChild = function(node)
 81 {    
 82     var oldParent;
 83     
 84     if (!node) throw new Error("node must not be null");
 85     if (node == this) throw new Error("node can not be a child of itself");
 86 
 87     // Remove from old parent if there is one
 88     oldParent = node.parentNode;
 89     if (oldParent) oldParent.removeChild(node);
 90 
 91     // Append the child
 92     node.previousSibling = this.lastChild;
 93     if (this.lastChild) this.lastChild.nextSibling = node;
 94     this.lastChild = node;
 95     if (!this.firstChild) this.firstChild = node;
 96     node.parentNode = this;
 97 };
 98 
 99 /**
100  * Returns the first child node of this node. If the node has no child nodes
101  * then null is returned.
102  * 
103  * @return {?threedee.SceneNode}
104  *            The first child node or null if there are no child nodes
105  */
106 threedee.SceneNode.prototype.getFirstChild = function()
107 {
108     return this.firstChild;
109 };
110 
111 /**
112  * Returns the last child node of this node. If the node has no child nodes
113  * then null is returned.
114  * 
115  * @return {?threedee.SceneNode}
116  *             The last child node or null if there are no child nodes
117  */
118 threedee.SceneNode.prototype.getLastChild = function()
119 {
120     return this.lastChild;
121 };
122 
123 /**
124  * Returns the next sibling of this node. If the node is the last child node
125  * of its parent then null is returned because there can't be a next
126  * sibling.
127  * 
128  * @return {?threedee.SceneNode}
129  *            The next sibling of this node or null if there is no next sibling
130  */
131 threedee.SceneNode.prototype.getNextSibling = function()
132 {
133     return this.nextSibling;
134 };
135 
136 /**
137  * Returns the parent node. If the node has no root node yet then null is
138  * returned.
139  * 
140  * @return {?threedee.SceneNode}
141  *            The parent node or null if there is none
142  */
143 threedee.SceneNode.prototype.getParentNode = function()
144 {
145     return this.parentNode;
146 };
147 
148 /**
149  * Returns the previous sibling of this node. If the node is the first child
150  * node of its parent then null is returned because there can't be a
151  * previous sibling.
152  * 
153  * @return {?threedee.SceneNode}
154  *            The previous sibling of this node or null if there is no previous
155  *            sibling
156  */
157 threedee.SceneNode.prototype.getPreviousSibling = function()
158 {
159     return this.previousSibling;
160 };
161 
162 /**
163  * Checks if this node has child nodes.
164  * 
165  * @return {boolean}
166  *            True if this node has child nodes, false if not
167  */
168 threedee.SceneNode.prototype.hasChildNodes = function()
169 {
170     return !!this.firstChild;
171 };
172 
173 /**
174  * Inserts a new child node before the specified reference node. If the new
175  * node was already connected to a parent then it is disconnected from this
176  * parent first.
177  * 
178  * @param {!threedee.SceneNode} insertNode
179  *            The new node to insert.
180  * @param {!threedee.SceneNode} referenceNode
181  *            The reference node.
182  */
183 threedee.SceneNode.prototype.insertBefore = function(insertNode, referenceNode)
184 {
185     var oldParent, oldPrevious;
186     
187     if (!insertNode) throw new Error("newNode must be set");
188     if (!referenceNode) throw new Error("referenceNode must be set");
189     if (insertNode == this)
190         throw new Error("newNode can not be a child of itself");
191 
192     // Verify that reference node is our child
193     if (referenceNode.parentNode != this)
194         throw new Error("Reference node is not my child node");
195 
196     // Remove from old parent if there is one
197     oldParent = insertNode.parentNode;
198     if (oldParent) oldParent.removeChild(insertNode);
199 
200     // Insert the node
201     oldPrevious = referenceNode.previousSibling;
202     if (oldPrevious)
203         oldPrevious.nextSibling = insertNode;
204     else
205         this.firstChild = insertNode;
206     referenceNode.previousSibling = insertNode;
207     insertNode.previousSibling = oldPrevious;
208     insertNode.nextSibling = referenceNode;
209     insertNode.parentNode = this;
210 };
211 
212 /**
213  * Removes the specified child node from this node.
214  * 
215  * @param {!threedee.SceneNode} node
216  *            The node to remove
217  */
218 threedee.SceneNode.prototype.removeChild = function(node)
219 {
220     var next, prev;
221     
222     if (!node) throw new Error("node must be set");
223 
224     // Verify that node is our child
225     if (node.parentNode != this) throw new Error("node is not my child node");
226 
227     // Remove node from linked list
228     next = node.nextSibling;
229     prev = node.previousSibling;
230     if (next) next.previousSibling = prev;
231     if (prev) prev.nextSibling = next;
232 
233     // Correct first/last reference
234     if (node == this.firstChild) this.firstChild = next;
235     if (node == this.lastChild) this.lastChild = prev;
236 
237     // Remove all references from node
238     node.parentNode = null;
239     node.nextSibling = null;
240     node.previousSibling = null;
241 };
242 
243 /**
244  * Replaces the specified old node with the specified new node. If the new
245  * node was already connected to a parent then it is disconnected from this
246  * parent first.
247  * 
248  * @param {!threedee.SceneNode} oldNode
249  *            The old node to be replaced by the new one
250  * @param {!threedee.SceneNode} newNode
251  *            The new node to replace the old one
252  */
253 threedee.SceneNode.prototype.replaceChild = function(oldNode, newNode)
254 {
255     var next;
256     
257     if (!oldNode) throw new Error("oldNode must be set");
258     if (!newNode) throw new Error("newNode must be set");
259     if (newNode == this)
260         throw new Error("node can not be a child of itself");
261 
262     // Verify that old node is our child
263     if (oldNode.parentNode != this)
264         throw new Error("node is not my child node");
265 
266     // New node is the same as the old node then do nothing
267     if (newNode != oldNode)
268     {
269         next = oldNode.nextSibling;
270         this.removeChild(oldNode);
271         if (next == null)
272             this.appendChild(newNode);
273         else
274             this.insertBefore(newNode, next);
275     }
276 };
277 
278 /**
279  * Updates the node with the specified time delta. Default implementation is
280  * executing the connected node updaters and calling the update method of
281  * all child nodes.
282  * 
283  * @param {number} delta
284  *            The time elapsed since the last scene update (in nanoseconds)
285  */
286 threedee.SceneNode.prototype.update = function(delta)
287 {
288     var childNode, i, max;
289     
290     for (i = 0, max = this.updaters.length; i < max; i++)
291     {
292         this.updaters[i].update(this, delta);
293     }
294 
295     childNode = this.firstChild;
296     while (childNode)
297     {
298         childNode.update(delta);
299         childNode = childNode.getNextSibling();
300     }
301 };
302 
303 /**
304  * Renders the node. Default implementation is doing nothing except calling
305  * the render method of all child nodes.
306  * 
307  * @param {!threedee.PolygonBuffer} buffer
308  *            The polygon buffer
309  * @param {!threedee.Matrix} transform
310  *            The current transformation
311  */
312 threedee.SceneNode.prototype.render = function(buffer, transform)
313 {
314     var childNode;
315     
316     childNode = this.firstChild;
317     while (childNode)
318     {
319         childNode.render(buffer, transform.copy().transform(childNode
320             .getTransform()));
321         childNode = childNode.getNextSibling();
322     }
323 };
324 
325 /**
326  * Returns the effective transformation of this node by recursively
327  * traversing the path up the root node and multiplying all found
328  * transformations. Depending on the complexity of your scene graph this is
329  * time-consuming so thing twice if you really need it.
330  * 
331  * @return {!threedee.Matrix} 
332  *            The effective transformation.
333  */
334 threedee.SceneNode.prototype.getEffectiveTransform = function()
335 {
336     if (this.parentNode != null)
337         return this.parentNode.getEffectiveTransform().copy().transform(
338             this.transform);
339     else
340         return this.transform.copy();
341 };
342 
343 /**
344  * Returns the current transformation matrix.
345  * 
346  * @return {!threedee.Matrix}
347  *             The current transformation matrix
348  */
349 threedee.SceneNode.prototype.getTransform = function()
350 {
351     return this.transform;
352 };
353 
354 /**
355  * Sets the transformation matrix.
356  * 
357  * @param {!threedee.Matrix} transform
358  *            The transformation matrix to set
359  */
360 threedee.SceneNode.prototype.setTransform = function(transform)
361 {
362     if (!transform) throw new Error("transform must be set");
363     this.transform = transform;
364 };
365 
366 /**
367  * Adds the specified node updater to this node.
368  * 
369  * @param {!threedee.NodeUpdater} updater
370  *            The updater to add
371  */
372 threedee.SceneNode.prototype.addUpdater = function(updater)
373 {
374     this.updaters.push(updater);
375 };
376 
377 /**
378  * Removes the specified node updater from this node.
379  * 
380  * @param {!threedee.NodeUpdater} updater
381  *            The updater to remove
382  */
383 threedee.SceneNode.prototype.removeUpdater = function(updater)
384 {
385     var i;
386     
387     for (i = this.updaters.length - 1; i >= 0; i--)
388     {
389         if (this.updaters[i] == updater)
390         {
391             this.updaters.splice(i, 1);
392             break;
393         }
394     }
395 };
396