|Issue 57 September 26 2008|
Predicting the future with Mystic Mark – Part Two
In the first part of this series, I introduced the RunRev Crystal Ball stack - a simple application of gradient fills and animation techniques producing a pleasing swirling effect. Having already given an overview and a few ways to customize the stack without doing any coding, in this part I'll be delving deeper and explaining how the animation effect is achieved.
Rather than describe the original Crystal Ball stack, this article will instead be looking at a simplified version which more clearly illustrates the use of a parametric path to describe the animation - the focus of this article.
The simplified stack can be fetched by executing the following from the Message Box :
The first card of this stack is a vastly simplified version of the crystal ball - it consists of a single moving gradient whose animation is controlled by clicking the Start/Stop button. The second card is of slightly more interest - it contains a simple interactive diagram illustrating two kinds of parameterized path.
Going in circles
Looking at it another way, for each moment in time we are assigning a different location to the center of the radial gradient in such a way that the path of a circle is traced out.
Indeed, this is precisely how the movement is implemented - we have a function circularPath that returns a point for each value in time. This function is found in the card script.
This is more clearly illustrated on the second card Parametric Paths . Here the time slider allows the setting of the 'time' between 0 and 2000 and things have been engineered so this corresponds to the little sprite doing two full rotations. The following picture shows the motion at three different times:
Underlying this movement is a little bit of geometry - the parametric form of a circle . For a circle centered at ( cx , cy ) and of radius r , this is formulated as:
x( t ) := cx + r * cos( t )
y( t ) := cy + r * sin( t )
Or in Revolution code:
function parameterizedCircle pT, pCx, pCy, pR return pCx + r * cos (pT), pCy + r * sin (pT) end parameterizedCircle
The above is pretty much what the function circularPath does. The difference is that it rescales and adjusts t so that the point will move at the given speed and start at the appropriate speed - it also uses some vector utility functions (located in the stack script) to make the code clearer (well, hopefully clearer).
Getting things moving
Having a way of getting a new point for each moment in time is all well and good, but for an animation its necessary to actually change something as time moves by. This turns out to be very straightforward and uses a standard Revolution technique - a pending message .
Returning to the first card of the example stack, when the Start button is pressed it eventually causes the scheduleSwirl handler in the card script to be invoked. All this handler does is compute when the animation should be next updated and then uses:
send ... in ... milliseconds
To tell Revolution to issue an updateSwirl message at the appropriate time. Similarly, when the updateSwirl handler is actually called and finishes execution it again schedules an update - causing a perpetual loop: schedule, wait, update, schedule, wait, update, ... Importantly, this results in a sequence of calls to updateSwirl at appropriate intervals that (hopefully) don't tax the processor too much!
The updateSwirl handler is the work-horse of the animation loop - it uses the various properties set on the Glow (see the previous part of this series for details of these) together with the circularPath function to work out where the glow should be and then updates the gradient appropriately. This is all quite straightforward, but one important note deserves mention - the time used to calculate the location of the gradient is based on the system clock.
When startSwirl is invoked it stores the current time (in milliseconds) in the variable sSwirlTime . This value is used at the start of updateSwirl to work out how far along the animation is:
put the millseconds sSwirlTime into tAnimationTime
It is this time, tAnimationTime, that is passed to circularPath . i.e. We base how far we are along in the animation on physical time and not synthetic.
Basing the update to the animation on the amount of physical time passed rather than the number of updates that have occurred (or some synthetic notion of frame number) is the key to smooth animation. Indeed, the parametric representation of any movement is perfect for this since we have (by definition) a value for our animated properties for any time value!
Of course, it isn't just circular motion we can obtain by this method - we can make things move along any path for which we can come up with a function that returns a point for each moment in time. I've implemented a second example path on the second card of the example - rectangularPath . Like the code for circularPath this can be found in the card script although its a little more complex then that for a circle!
Another example of a parametric path you might want to try is a simple linear one. To model the movement of a point along a straight line between two points ( ax , ay ) and ( bx , by ) you can use:
x( t ) := t * ax + (1 - t ) * bx
y( t ) : = t * ay + (1 - t ) * by
Coding this in a similar vein to the rectangular and circular ones would result in something like:
function linearPath pTime, pStart, pFinish, pSpeed local tScaledTime put pTime * pSpeed / 1000 into tScaledTime put tScaledTime - trunc (tScaledTime) into tScaledTime return vectorScale (pStart, tScaledTime) + vectorScale \ (pFinish, 1 tScaledTime) end linearPath
Although as it stands this version will result in a movement that goes from start to finish and then bounces back to the start again...
In the next (and final) part of this series I'll finish the more in depth look into the functioning of the crystal ball stack by showing how the easing in and easing out of the movements is achieved using a simple state based technique.