OLD | NEW |
(Empty) | |
| 1 // @@REWRITE(insert js-copyright) |
| 2 // @@REWRITE(delete-start) |
| 3 // Copyright 2009 Google Inc. All Rights Reserved |
| 4 // @@REWRITE(delete-end) |
| 5 |
| 6 /** |
| 7 * This file contains the animation-management code for the siteswap animator. |
| 8 * This is encapsulated in the EventQueue and QueueEvent classes, the event |
| 9 * handler, and startAnimation, the main external interface to the animation. |
| 10 */ |
| 11 |
| 12 /** |
| 13 * A record, held in the EventQueue, that describes the curve that should be |
| 14 * given to a shape at a time. |
| 15 * @constructor |
| 16 * @param {!number} time base time at which the event occurs/the curve starts. |
| 17 * @param {!o3d.Shape} shape the shape to be updated. |
| 18 * @param {Curve} curve the path for the shape to follow. |
| 19 */ |
| 20 function QueueEvent(time, shape, curve) { |
| 21 this.time = time; |
| 22 this.shape = shape; |
| 23 this.curve = curve; |
| 24 return this; |
| 25 } |
| 26 |
| 27 /** |
| 28 * A circular queue of events that will happen during the course of an animation |
| 29 * that's duration beats long. The queue is ordered by the time each curve |
| 30 * starts. Note that a curve may start after it ends, since time loops |
| 31 * endlessly. The nextEvent field is the index of the next event to occur; it |
| 32 * keeps track of how far we've gotten in the queue. |
| 33 * @constructor |
| 34 * @param {!number} duration the length of the animation in beats. |
| 35 */ |
| 36 function EventQueue(duration) { |
| 37 this.events = []; |
| 38 this.nextEvent = 0; |
| 39 this.duration = duration; |
| 40 this.timeCorrection = 0; // Corrects from queue entry time to real time. |
| 41 return this; |
| 42 } |
| 43 |
| 44 /** |
| 45 * Add an event to the queue, inserting into order by its time field. |
| 46 * A heap-based priority queue would be faster, but likely overkill, as this |
| 47 * won't ever contain that many items, and isn't likely to be speed-critical. |
| 48 * @param {!QueueEvent} event the event to add. |
| 49 */ |
| 50 EventQueue.prototype.addEvent = function(event) { |
| 51 var i = 0; |
| 52 while (i < this.events.length && event.time > this.events[i].time) { |
| 53 ++i; |
| 54 } |
| 55 this.events.splice(i, 0, event); |
| 56 }; |
| 57 |
| 58 /** |
| 59 * Pull the next event off the queue. |
| 60 * @return {!QueueEvent} the event. |
| 61 */ |
| 62 EventQueue.prototype.shift = function() { |
| 63 var e = this.events[this.nextEvent]; |
| 64 if (++this.nextEvent >= this.events.length) { |
| 65 assert(this.nextEvent > 0); |
| 66 this.nextEvent = 0; |
| 67 this.timeCorrection += this.duration; |
| 68 } |
| 69 return e; |
| 70 }; |
| 71 |
| 72 /** |
| 73 * Process all current events, updating all animated objects with their new |
| 74 * curves, until we find that the next event in the queue is in the future. |
| 75 * @param {!number} time the current time in beats. This number is an absolute, |
| 76 * not locked to the range of the duration of the pattern, so getNextTime() |
| 77 * returns a doctored number to add in the offset from in-pattern time to real |
| 78 * time. |
| 79 * @return {!number} the time of the next future event. |
| 80 */ |
| 81 EventQueue.prototype.processEvents = function(time) { |
| 82 while (this.getNextTime() <= time) { |
| 83 var e = this.shift(); |
| 84 setParamCurveInfo(e.curve, e.shape, time); |
| 85 } |
| 86 return this.getNextTime(); // In case you want to set a callback. |
| 87 }; |
| 88 |
| 89 /** |
| 90 * Look up the initial curve for a shape [the curve that it'll be starting or in |
| 91 * the middle of at time 0]. |
| 92 * @param {!CurveSet} curveSet the complete set of curves for a Shape. |
| 93 * @return {!Object} the curve and the time at which it would have started. |
| 94 */ |
| 95 function getInitialCurveInfo(curveSet) { |
| 96 var curve = curveSet.getCurveForUnsafeTime(0); |
| 97 var curveBaseTime; |
| 98 if (!curve.startTime) { |
| 99 curveBaseTime = 0; |
| 100 } else { |
| 101 // If the curve isn't starting now, it must have wrapped around. |
| 102 assert(curve.startTime + curve.duration > curveSet.duration); |
| 103 // So subtract off one wrap so that its startTime is in the right space of |
| 104 // numbers. We assume here that no curve duration is longer than the |
| 105 // pattern, which must be guaranteed by the code that generates patterns. |
| 106 assert(curve.duration <= curveSet.duration); |
| 107 curveBaseTime = curve.startTime - curveSet.duration; |
| 108 } |
| 109 return { curve: curve, curveBaseTime: curveBaseTime }; |
| 110 } |
| 111 |
| 112 /** |
| 113 * Set up the event queue with a complete pattern starting at time 0. |
| 114 * @param {!Array.CurveSet} curveSets the curve sets for all shapes. |
| 115 * @param {!Array.o3d.Shape} shapes all the shapes to animate. |
| 116 */ |
| 117 EventQueue.prototype.setUp = function(curveSets, shapes) { |
| 118 assert(curveSets.length == shapes.length); |
| 119 for (var i = 0; i < shapes.length; ++i) { |
| 120 var curveSet = curveSets[i]; |
| 121 assert(this.duration % curveSet.duration == 0); |
| 122 var shape = shapes[i]; |
| 123 var record = getInitialCurveInfo(curveSet); |
| 124 var curveBaseTime = record.curveBaseTime; |
| 125 var curve = record.curve; |
| 126 setParamCurveInfo(curve, shape, curveBaseTime); |
| 127 do { |
| 128 curveBaseTime += curve.duration; |
| 129 curve = curveSet.getCurveForTime(curveBaseTime); |
| 130 var e = new QueueEvent(curveBaseTime % this.duration, shape, curve); |
| 131 this.addEvent(e); |
| 132 } while (curveBaseTime + curve.duration <= this.duration); |
| 133 } |
| 134 }; |
| 135 |
| 136 /** |
| 137 * Return the time of the next future event. |
| 138 * @return {!number} the time. |
| 139 */ |
| 140 EventQueue.prototype.getNextTime = function() { |
| 141 return this.events[this.nextEvent].time + this.timeCorrection; |
| 142 }; |
| 143 |
| 144 /** |
| 145 * This is the event handler that runs the whole animation. When triggered by |
| 146 * the counter, it updates the curves on all objects whose curves have expired. |
| 147 * |
| 148 * The current time will be some time around when we wanted to be called. It |
| 149 * might be exact, but it might be a bit off due to floating point error, or a |
| 150 * lot off due to the system getting bogged down somewhere for a second or |
| 151 * two. e.g. if we wanted to get a call at time 7, it's likely to be |
| 152 * something like 7.04, but might even be 11. We then use 7, not 7.04, as the |
| 153 * start time for each of the curves set, so as to remove clock drift. Since |
| 154 * the time we wanted to be called is stored in the next item in the queue, we |
| 155 * can just pull that out and use it. However, if we then find that we're |
| 156 * setting our callback in the past, we repeat the process until our callback |
| 157 * is set safely in the future. We may get some visual artifacts, but at |
| 158 * least we won't drop any events [leading to stuff drifting endlessly off |
| 159 * into the distance]. |
| 160 */ |
| 161 function handler() { |
| 162 var eventTime = g.eventQueue.getNextTime(); |
| 163 var trueCurrentTime; |
| 164 do { |
| 165 g.counter.removeCallback(eventTime); |
| 166 eventTime = g.eventQueue.processEvents(eventTime); |
| 167 g.counter.addCallback(eventTime, handler); |
| 168 trueCurrentTime = g.counter.count; |
| 169 } while (eventTime < trueCurrentTime); |
| 170 } |
| 171 |
| 172 /** |
| 173 * Given a precomputed juggling pattern, this sets up the O3D objects, |
| 174 * EventQueue, and callback necessary to start an animation, then calls |
| 175 * updateAnimating to kick it off if enabled. |
| 176 * |
| 177 * @param {!number} numBalls the number of balls in the animation. |
| 178 * @param {!number} numHands the number of hands in the animation. |
| 179 * @param {!number} duration the length of the full animation cycle in beats. |
| 180 * @param {!Array.CurveSet} ballCurveSets one CurveSet per ball. |
| 181 * @param {!Array.CurveSet} handCurveSets one CurveSet per hand. |
| 182 */ |
| 183 function startAnimation(numBalls, numHands, duration, ballCurveSets, |
| 184 handCurveSets) { |
| 185 g.counter.running = false; |
| 186 g.counter.reset(); |
| 187 |
| 188 setNumBalls(numBalls); |
| 189 setNumHands(numHands); |
| 190 |
| 191 g.eventQueue = new EventQueue(duration); |
| 192 g.eventQueue.setUp(handCurveSets, g.handShapes); |
| 193 g.eventQueue.setUp(ballCurveSets, g.ballShapes); |
| 194 g.counter.addCallback(g.eventQueue.getNextTime(), handler); |
| 195 |
| 196 updateAnimating(); |
| 197 } |
| 198 |
OLD | NEW |