1 /** 2 * Copyright (C) 2009-2011 Klaus Reimer <k@ailis.de> 3 * See LICENSE.txt for licensing information. 4 * 5 * @require twodee.js 6 */ 7 8 /** 9 * Constructs a new scene. 10 * 11 * @constructor 12 * @class 13 * A scene. 14 */ 15 twodee.Scene = function() 16 { 17 this.collidables = []; 18 this.baseTransform = new twodee.Matrix(); 19 }; 20 21 /** 22 * The root node of the scene. 23 * 24 * @private 25 * @type {twodee.SceneNode} */ 26 twodee.Scene.prototype.rootNode = null; 27 28 /** 29 * The last update timestamp. 30 * 31 * @private 32 * @type {number} 33 */ 34 twodee.Scene.prototype.lastUpdate = 0; 35 36 /** 37 * Temporary array with collidables during rendering. 38 * 39 * @private 40 * @type {Array.<twodee.SceneNode>} 41 */ 42 twodee.Scene.prototype.collidables = null; 43 44 /** 45 * If scene is paused or not. 46 * 47 * @private 48 * @type {boolean} 49 */ 50 twodee.Scene.prototype.paused = false; 51 52 /** 53 * The base transformation. 54 * 55 * @private 56 * @type {!twodee.Matrix} 57 */ 58 twodee.Scene.prototype.baseTransform; 59 60 /** 61 * Sets the root node. 62 * 63 * @param {twodee.SceneNode} rootNode 64 * The root node to set 65 */ 66 twodee.Scene.prototype.setRootNode = function(rootNode) 67 { 68 this.rootNode = rootNode; 69 }; 70 71 /** 72 * Returns the current root node. 73 * 74 * @return {twodee.SceneNode} 75 * The current root node 76 */ 77 twodee.Scene.prototype.getRootNode = function() 78 { 79 return this.rootNode; 80 }; 81 82 /** 83 * Updates the scene with the specified time delta (milliseconds); 84 * 85 * @param {number} delta 86 * The time elapsed since the last call to this method measured 87 * in milliseconds. This is optional. If not specified 88 * then it is calculated automatically. If negative then the 89 * absolute value is used as the maximum time delta. This means 90 * that a automatically calculated time delta which is larger then 91 * this maximum value is trimmed down to the maximum time delta. 92 * The default maximum value is 1000. 93 */ 94 twodee.Scene.prototype.update = function(delta) 95 { 96 var now, node, maxDelta; 97 98 if (this.paused || !(node = this.rootNode)) return; 99 100 if (!delta || delta < 0) 101 { 102 if (delta < 0) 103 maxDelta = -delta; 104 else 105 maxDelta = 1000; 106 107 now = new Date().getTime(); 108 delta = Math.max(0, Math.min(maxDelta, now - this.lastUpdate)); 109 this.lastUpdate = now; 110 } 111 112 // Update the root node 113 this.updateNode(node, delta); 114 }; 115 116 /** 117 * Updates the specified node and its children (recursively). 118 * 119 * @param {twodee.SceneNode} node 120 * The node to update 121 * @param {number} delta 122 * The time elapsed since the last call to this method measured 123 * in milliseconds. 124 * @private 125 */ 126 twodee.Scene.prototype.updateNode = function(node, delta) 127 { 128 var child, next; 129 130 node.update(delta); 131 child = node.getFirstChild(); 132 while (child) 133 { 134 next = child.getNextSibling(); 135 this.updateNode(child, delta); 136 child = next; 137 } 138 }; 139 140 /** 141 * Renders the scene. 142 * 143 * @param {CanvasRenderingContext2D} g 144 * The graphics context 145 * @param {number} width 146 * The output width in pixels 147 * @param {number} height 148 * The output height in pixels 149 */ 150 twodee.Scene.prototype.render = function(g, width, height) 151 { 152 var node, i, collidables, baseTransform; 153 154 // If no root node is set yet then do nothing 155 if (!(node = this.rootNode)) return; 156 157 // Prepare the canvas 158 g.save(); 159 160 // Reset base transform to screen center 161 this.baseTransform.setTranslate(width / 2, height / 2); 162 163 // Render the root node 164 this.renderNode(node, g); 165 166 // Restore the canvas 167 g.restore(); 168 169 // Finish collision detection 170 collidables = this.collidables; 171 for (i = collidables.length - 1; i >= 0; i--) 172 collidables[i].processCollisions(); 173 collidables.length = 0; 174 }; 175 176 /** 177 * Renders the specified node and its children (recursively). 178 * 179 * @param {twodee.SceneNode} node 180 * The node to render 181 * @param {CanvasRenderingContext2D} g 182 * The graphics context 183 * @private 184 */ 185 twodee.Scene.prototype.renderNode = function(node, g) 186 { 187 var transform, child, i, other, collidables, next; 188 189 // Do nothing if node is disabled 190 if (!node.isEnabled()) return; 191 192 // Update the effective transformation of the node 193 transform = node.updateTransformation(this.baseTransform); 194 195 // Update collision state 196 if (node.isCollidable()) 197 { 198 collidables = this.collidables; 199 for (i = collidables.length - 1; i >= 0; i--) 200 { 201 other = collidables[i]; 202 203 if (node.collidesWith(other)) 204 node.collide(other); 205 206 if (other.collidesWith(node)) 207 other.collide(node); 208 } 209 collidables.push(node); 210 } 211 212 // Remember current context configuration 213 g.save(); 214 215 // Calculate the global alpha for this node and its child nodes 216 g.globalAlpha = g.globalAlpha * node.getOpacity(); 217 218 // Render the node itself 219 node.render(g, transform); 220 221 // Process all the child nodes (recursively) 222 child = node.getFirstChild(); 223 while (child) 224 { 225 next = child.getNextSibling(); 226 this.renderNode(child, g); 227 child = next; 228 } 229 230 // Restore old context configuration 231 g.restore(); 232 }; 233 234 /** 235 * Pause the scene. No more updates are done, but rendering continues. 236 */ 237 twodee.Scene.prototype.pause = function() 238 { 239 this.paused = true; 240 this.lastUpdate = 0; 241 }; 242 243 /** 244 * Resumes the scene. 245 */ 246 twodee.Scene.prototype.resume = function() 247 { 248 this.paused = false; 249 }; 250