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