Index: samples/siteswap/animation.js |
=================================================================== |
--- samples/siteswap/animation.js (revision 0) |
+++ samples/siteswap/animation.js (revision 0) |
@@ -0,0 +1,198 @@ |
+// @@REWRITE(insert js-copyright) |
+// @@REWRITE(delete-start) |
+// Copyright 2009 Google Inc. All Rights Reserved |
+// @@REWRITE(delete-end) |
+ |
+/** |
+ * This file contains the animation-management code for the siteswap animator. |
+ * This is encapsulated in the EventQueue and QueueEvent classes, the event |
+ * handler, and startAnimation, the main external interface to the animation. |
+ */ |
+ |
+/** |
+ * A record, held in the EventQueue, that describes the curve that should be |
+ * given to a shape at a time. |
+ * @constructor |
+ * @param {!number} time base time at which the event occurs/the curve starts. |
+ * @param {!o3d.Shape} shape the shape to be updated. |
+ * @param {Curve} curve the path for the shape to follow. |
+ */ |
+function QueueEvent(time, shape, curve) { |
+ this.time = time; |
+ this.shape = shape; |
+ this.curve = curve; |
+ return this; |
+} |
+ |
+/** |
+ * A circular queue of events that will happen during the course of an animation |
+ * that's duration beats long. The queue is ordered by the time each curve |
+ * starts. Note that a curve may start after it ends, since time loops |
+ * endlessly. The nextEvent field is the index of the next event to occur; it |
+ * keeps track of how far we've gotten in the queue. |
+ * @constructor |
+ * @param {!number} duration the length of the animation in beats. |
+ */ |
+function EventQueue(duration) { |
+ this.events = []; |
+ this.nextEvent = 0; |
+ this.duration = duration; |
+ this.timeCorrection = 0; // Corrects from queue entry time to real time. |
+ return this; |
+} |
+ |
+/** |
+ * Add an event to the queue, inserting into order by its time field. |
+ * A heap-based priority queue would be faster, but likely overkill, as this |
+ * won't ever contain that many items, and isn't likely to be speed-critical. |
+ * @param {!QueueEvent} event the event to add. |
+ */ |
+EventQueue.prototype.addEvent = function(event) { |
+ var i = 0; |
+ while (i < this.events.length && event.time > this.events[i].time) { |
+ ++i; |
+ } |
+ this.events.splice(i, 0, event); |
+}; |
+ |
+/** |
+ * Pull the next event off the queue. |
+ * @return {!QueueEvent} the event. |
+ */ |
+EventQueue.prototype.shift = function() { |
+ var e = this.events[this.nextEvent]; |
+ if (++this.nextEvent >= this.events.length) { |
+ assert(this.nextEvent > 0); |
+ this.nextEvent = 0; |
+ this.timeCorrection += this.duration; |
+ } |
+ return e; |
+}; |
+ |
+/** |
+ * Process all current events, updating all animated objects with their new |
+ * curves, until we find that the next event in the queue is in the future. |
+ * @param {!number} time the current time in beats. This number is an absolute, |
+ * not locked to the range of the duration of the pattern, so getNextTime() |
+ * returns a doctored number to add in the offset from in-pattern time to real |
+ * time. |
+ * @return {!number} the time of the next future event. |
+ */ |
+EventQueue.prototype.processEvents = function(time) { |
+ while (this.getNextTime() <= time) { |
+ var e = this.shift(); |
+ setParamCurveInfo(e.curve, e.shape, time); |
+ } |
+ return this.getNextTime(); // In case you want to set a callback. |
+}; |
+ |
+/** |
+ * Look up the initial curve for a shape [the curve that it'll be starting or in |
+ * the middle of at time 0]. |
+ * @param {!CurveSet} curveSet the complete set of curves for a Shape. |
+ * @return {!Object} the curve and the time at which it would have started. |
+ */ |
+function getInitialCurveInfo(curveSet) { |
+ var curve = curveSet.getCurveForUnsafeTime(0); |
+ var curveBaseTime; |
+ if (!curve.startTime) { |
+ curveBaseTime = 0; |
+ } else { |
+ // If the curve isn't starting now, it must have wrapped around. |
+ assert(curve.startTime + curve.duration > curveSet.duration); |
+ // So subtract off one wrap so that its startTime is in the right space of |
+ // numbers. We assume here that no curve duration is longer than the |
+ // pattern, which must be guaranteed by the code that generates patterns. |
+ assert(curve.duration <= curveSet.duration); |
+ curveBaseTime = curve.startTime - curveSet.duration; |
+ } |
+ return { curve: curve, curveBaseTime: curveBaseTime }; |
+} |
+ |
+/** |
+ * Set up the event queue with a complete pattern starting at time 0. |
+ * @param {!Array.CurveSet} curveSets the curve sets for all shapes. |
+ * @param {!Array.o3d.Shape} shapes all the shapes to animate. |
+ */ |
+EventQueue.prototype.setUp = function(curveSets, shapes) { |
+ assert(curveSets.length == shapes.length); |
+ for (var i = 0; i < shapes.length; ++i) { |
+ var curveSet = curveSets[i]; |
+ assert(this.duration % curveSet.duration == 0); |
+ var shape = shapes[i]; |
+ var record = getInitialCurveInfo(curveSet); |
+ var curveBaseTime = record.curveBaseTime; |
+ var curve = record.curve; |
+ setParamCurveInfo(curve, shape, curveBaseTime); |
+ do { |
+ curveBaseTime += curve.duration; |
+ curve = curveSet.getCurveForTime(curveBaseTime); |
+ var e = new QueueEvent(curveBaseTime % this.duration, shape, curve); |
+ this.addEvent(e); |
+ } while (curveBaseTime + curve.duration <= this.duration); |
+ } |
+}; |
+ |
+/** |
+ * Return the time of the next future event. |
+ * @return {!number} the time. |
+ */ |
+EventQueue.prototype.getNextTime = function() { |
+ return this.events[this.nextEvent].time + this.timeCorrection; |
+}; |
+ |
+/** |
+ * This is the event handler that runs the whole animation. When triggered by |
+ * the counter, it updates the curves on all objects whose curves have expired. |
+ * |
+ * The current time will be some time around when we wanted to be called. It |
+ * might be exact, but it might be a bit off due to floating point error, or a |
+ * lot off due to the system getting bogged down somewhere for a second or |
+ * two. e.g. if we wanted to get a call at time 7, it's likely to be |
+ * something like 7.04, but might even be 11. We then use 7, not 7.04, as the |
+ * start time for each of the curves set, so as to remove clock drift. Since |
+ * the time we wanted to be called is stored in the next item in the queue, we |
+ * can just pull that out and use it. However, if we then find that we're |
+ * setting our callback in the past, we repeat the process until our callback |
+ * is set safely in the future. We may get some visual artifacts, but at |
+ * least we won't drop any events [leading to stuff drifting endlessly off |
+ * into the distance]. |
+ */ |
+function handler() { |
+ var eventTime = g.eventQueue.getNextTime(); |
+ var trueCurrentTime; |
+ do { |
+ g.counter.removeCallback(eventTime); |
+ eventTime = g.eventQueue.processEvents(eventTime); |
+ g.counter.addCallback(eventTime, handler); |
+ trueCurrentTime = g.counter.count; |
+ } while (eventTime < trueCurrentTime); |
+} |
+ |
+/** |
+ * Given a precomputed juggling pattern, this sets up the O3D objects, |
+ * EventQueue, and callback necessary to start an animation, then calls |
+ * updateAnimating to kick it off if enabled. |
+ * |
+ * @param {!number} numBalls the number of balls in the animation. |
+ * @param {!number} numHands the number of hands in the animation. |
+ * @param {!number} duration the length of the full animation cycle in beats. |
+ * @param {!Array.CurveSet} ballCurveSets one CurveSet per ball. |
+ * @param {!Array.CurveSet} handCurveSets one CurveSet per hand. |
+ */ |
+function startAnimation(numBalls, numHands, duration, ballCurveSets, |
+ handCurveSets) { |
+ g.counter.running = false; |
+ g.counter.reset(); |
+ |
+ setNumBalls(numBalls); |
+ setNumHands(numHands); |
+ |
+ g.eventQueue = new EventQueue(duration); |
+ g.eventQueue.setUp(handCurveSets, g.handShapes); |
+ g.eventQueue.setUp(ballCurveSets, g.ballShapes); |
+ g.counter.addCallback(g.eventQueue.getNextTime(), handler); |
+ |
+ updateAnimating(); |
+} |
+ |