Posted by chet
on January 8, 2008 at 4:17 PM PST
And now, for the stunning conclusion of my previous blog entry. In this week's exciting climax, we'll go over some of the new, cool elements in the scene graph animation engine and end with a discussion of what it all means (life, animation - all of that).
(This is the conclusion of a two-parter that was begun
last week and split in half
for no particularly good reason. If you didn't read
last week's entry yet, please
do. I'll wait. ... Now, are you ready? Then let's get started.)
Most of the changes between TimingFramework and the scene graph animation library
are tweaks on existing
functionality, as described above. But there is also new functionality in the
scene graph animation engine. Some of it is functionality that
we have wanted in
the TimingFramework for some time - we just happened to get around to it in this
animation engine first.
Timeline class is probably the biggest new thing since the
Timing Framework. It adds a crucial missing piece of functionality from the
original library: the ability to group animations, schedule them in a
coordinated fashion, and use one, single heartbeat for all animations.
The previous approach of daisy-chaining animations one-by-one with TimingTrigger
was quite useful for individual sequences of animations. And the new
addBeginAnimation()/addEndAnimation() methods of Clip enhance this capability
significantly. But it's still not what you want for larger systems with entire
groups of animations that need to be coordinated. Instead, you want some
way to create groups of animations
that operate on a single timeline relative to
each other and then schedule that group appropriately
with other animations or other
animation groups. This functionality makes it much easier to build up
more complex and interdependent models of animations.
Timeline enables this capability by allowing you to schedule animations on a given
Timeline relative to when the Timeline itself starts. So, for example, if you
want to fire off animations a, b, and c 100, 200, and 300 ms after some even
occurs, then you can schedule these animations on a single Timeline and start
the Timeline when that event occurs:
// Create the animation group
Timeline timeline = new Timeline();
// Later, when the event occurs
One of the biggest recurring constraints that I ran up against with the
TimingFramework was the fact that each Animator started its own Swing Timer by
default. You could change this behavior, with the late-addition class
TimingSource, but it wasn't a convenient way to get what you really
wanted: a single timer for the whole system. You could also get a single-timer kind of behavior by adding multiple
TimingTargets to a single Animator, but this approach only works easily in
situations where you want similar timing and behavior characteristics from all
of these targets; for example, animations of different durations or interpolation
are difficult to
synchronize in this way.
What the system really needed was a single timing source that sent a heartbeat
pulse to all animations. Each animation could then turn that pulse into an
appropriate timing fraction, just as Animator does with its internal
Swing Timer events. But there are a couple of excellent reasons why the
single-pulse-generator model is superior to the multiple-Swing-Timer approach:
Synchronized animations: If you have several animations in the
system that are affecting the GUI, or are otherwise related in some way,
you probably want them all to receive their timing events at the same time
instead of at slightly different times because their internal Timers are
kicking off at different intervals. Coordinating
rendering changes in the single GUI display is a Good Thing; each element animating
separately from everything else around it could contribute to a more chaotic user
Resolution and frame rate: Anyone that worked through the gorey
details in the Resolution section of chapter 12 of Filthy
Rich Clients (or anyone that's
just worked closely with the Swing Timer) knows that the performance of that
Timer is often gated by constraints on the native platform. For example, on
Windows XP, the Swing Timer typically has an inter-event rate of about 16
milliseconds. This is because that's the highest rate achievable by the native
low-resolution timer upon which the Swing Timer depends (through its use of
Object.wait()). This problem is compounded when there are several Timers firing
off at the same time, because the timing events are all gated by that
wait() resolution, and cannot actually process the
wait()'s in parallel.
For example, say you have one Animator that you'd like to set up with a resolution of
10 ms. On Windows XP, you'd actually get timing events at a resolution of 16 ms
instead. Now, suppose you create a second Animator, also with a resolution of 10
ms. Since the underlying Swing Timer processes the timing events one by one,
and since the gating resolution of the timer is what it is, you'll actually
end up with an effective resolution of 32 ms for each of these Animators.
Now consider the model of the new animation engine, where there is just one single
timer running, sending out timing pulses to all animations in the system.
This is more like
the multiple-TimingTargets-per-Animator model where the only gating factor in
resolution is that of the core timer itself, not how many Animators are
waiting for the timing events.
Both of these capabilities of Timeline, grouping and the system-wide heartbeat,
are managed through the various
schedule() methods of Timeline. You schedule an
animation to run with some offset from the beginning of a Timeline, and the
Timeline ensures that that animation will start when it needs to and thereafter
the master heartbeat events from the system. You can schedule other animations
all on that same Timeline or on other Timelines and then schedule
the Timelines themselves to start when appropriate.
Timeline t1 = new Timeline();
// ... schedule animations on Timeline t1 ...
Timeline t2 = new Timeline();
// ... schedule animations on Timeline t2 ...
// Timeline t3 = new Timeline();
// schedule t1 to start 100 ms after t3 starts
// schedule t2 to start 200 ms after g3 starts
// start t3
An important point to note here is that Timelines and Clips may both be
scheduled on a Timeline. The
schedule() methods actually
parameter, where Animation is a superclass of both Clip and Timeline. So a
Timeline itself can be started relative to some point in another
Timeline. In this way, we can create hierarchical groups of animations
that are automatically triggered according to how we scheduled them together.
One constraint of the TimingFramework is that while time could be interpolated
non-linearly (using a non-linear Interpolator object), space was always interpolated linearly.
For example, if you set up an animation between points (x0, y0) and (x1, y1), then
the system would interpolate intermediate (x, y) points linearly between
these points; all points calculated
by the system (by the old Evaluator class) would lie along a straight line drawn between
the two endpoints.
MotionPath class makes it possible to create keyframes, and an Evaluator
to interpolate between them, for curved paths.
What Now? Whither TimingFramework?
A logical question to ask now is, what about the Timing Framework? Is there a future
in that project? Or should I start using the scene graph animation library instead?
I think the answer to these questions is still being figured out (since the scene
graph library itself is still very much in-development), but here are a
couple of reasonable ways to think about it, depending on your timeframe:
The TimingFramework is in good shape in general. There was a reason that I declared
it 1.0 (I didn't just randomly decide to add .44 to the previous release numbered
0.56). So please continue to use it as you see fit in your work for now. There are
some minor issues that crop up occasionally that should probably be addressed in
that library (although I admit I have been a bit preoccupied on Scene Graph and
other things for a while and haven't been as responsive to issues as I'd like).
Scene Graph animation, on the other hand, is very much in flux right now. We're
reasonably happy with the functionality of the library, but I wouldn't be shocked
to see some more refactoring take place as we continue working on the Scene Graph
project in general. So while I encourage you to take a look at it and play around
with it, I wouldn't bet on the current implementation of it quite yet.
I think (and this is where it gets fuzzy, because we're a bit busy focusing on the
short-term right now and just trying to finish up the Scene Graph library in general)
that the scene graph animation engine, or something very like it, will probably
be the single library for animation eventually. It just doesn't make sense to have
two such libraries, at least not
coming out of the same group at Sun and not when
one is essentially a subset of the other. When we get there and what the eventual,
single library looks like when we're there is still a mystery. But long term, I
see these libraries converging, and probably looking more like the Scene Graph version
than Timing Framework.
But in the meantime, please use the Timing Framework while you investigate and starting
playing with the Scene Graph animation engine.
Send us feedback on what we could improve to make sure that the
library we eventually end up with supports what you need from it.
By the Way
With all of this power to do cool, whizzy animations in Desktop Java
applications, I'm thinking that "Scene Graph" isn't really a good enough name. Here's a possible
alternative that I'm proposing, as of now:
But it still feels like something's missing. Maybe it's just not graphic enough.