Commit 5b044e5d authored by ericu@google.com's avatar ericu@google.com

Review URL: http://codereview.chromium.org/155005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19854 0039d316-1c4b-4281-b951-d872f2087c98
parent e720a18e
// @@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();
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment