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