Home Labs Galleries PetitOn

Using Sandy 3D Flash Library

Part 5, Using Interpolators

For automated and smooth animations, we can use one or more of the interpolators in the Sandy library. The interpolators calculate intermediate points between a start and an end value of any translation, rotation or scaling. You can even make object groups move along a curved path, if you like. All animations can use easing methods to make the animation linear or non linear. You can download the Actionscript code for this part here. The Actionscript is included in frame one of the fla file. Just create a suitable Stage and ClipScreen

Smooth animations

Last time we looked at a animated cube, rotating around any of the global axes.
We used the World3D render event, and let the event handler update the position of the cube. We did this by calling the rot() function of the rotation Transform3D with new values. The onRenderEvent is fired every time the world is rendered, i.e. for every frame.

function rotate(){
	if( running ){
		xRot += xDel; yRot += yDel; zRot += zDel;
		rotation.rot( xRot, yRot, zRot );
	}
}

That's fine, if you accept your bodies have no mass, and moves with the same velocity from start to destination. You have to admit, this behavior is a bit limited though.

We could do a lot more interesting stuff, if we use the interpolators and the Ease class,  offered by Sandy. The purpose of the interpolators is to calculate intermediate values for a given number of frames between a start value and an end value.
The most straight forward interpolators are

PositionInterpolator	used for translations
RotationInterpolator	used for rotations
ScaleInterpolator	used for scaling

You pass an easing function, the duration as a number of frames and the min and max values for the interval to interpolate over. The easing function makes it possible to change the linearity of the interpolation, more on that later. The min and max values are the start and end angles for rotation, start and end Vector's for position and scale ( you can actually scale differently for the x, y and z dimensions ).

Let's have a look at some translations!

We are using the same structure of the ActionScript, as in the earlier example.

In the init() function we set the camera slightly off side, to get a more interesting view of the world, and tell it to point to the origin.

	// Position the camera
	cam.setPosition(-100,100,-500);
	// Look at the origin
	cam.lookAt(0,0,0);

We also add an event handler for the button, so we are able to repeat the movement.

startButton.onRelease = doMove;

Here is the event handler

function doMove(){
	translation.redo();
}

The interpolator, once the animation is finished, will start over, when the redo() function is called.

In the createScene function, instead of having a Transform3D to take care of the translation, we introduce a PostionInterpolator.

	// First we create a TransformationGroup to fascilitate the translation
	var tTrans:TransformGroup = new TransformGroup();
	var easing = new Ease();
	// We create an interpolator to carry out the transformation
	translation = new PositionInterpolator( easing.create(),100, 
				new Vector(-100,0,0), new Vector(100,0,0));
	// The interpolator is set in the transform group holding our pyramide
	tTrans.setTransform( translation );
	// Add the pyramid to the translation
	tTrans.addChild( pyramid );
	// And finally the transformgroup to the rootGroup
	bg.addChild( tTrans );

This interpolator takes an easing function created by calling create on an Ease object, the duration of the animation in number of frames, and two vectors setting the start and end points of the animation.

As you can see, we set this interpolator as the transform to carry out by the transform group tTrans. For every frame, the interpolator calculates a new position for the transform group. It does this by means of the easing function.

But hey, shouldn't we always have a Transform3D to carry out the transform?
Well, yes, but the interpolators knows this, and kindly creates one for us behind the scenes. Nifty?

Take it with ease

So what is easing? If you have worked with the Flash time line in the old fashioned manner, you have possibly used tweening, and you may have come across easing in and easing out. What it does, is to make the animation non linear, so that easing in, it will start slowly and picking up speed towards the end. In Actionscript you can do the same, using the Tween class and the easing package. To get a clue on how it works, you can read my tutorial "Using the Tween Class".

Sandy has its own 3D tweening, using the interpolators and the Ease class. An Ease object can be set to provide all kinds of functions for calculating the intermediate points in an animation. We set the type and method by calling functions on the Ease object.
Simply put, the method controls the mathematical function used to calculate values, and the type controls the variation of strength of the easing over the interval.

var easing = new Ease();
easing.easingIn( strenght );    // Apply easing at the start of the animation
easing.exponential();           // Use an exponential function for the calculations 

All functions used to set up the easing, returns the Easing object, so you can chain the function calls to give the same result.

var easing = new Ease();
easing.easingIn( strenght ).exponential();

Here are some of the methods and types.

Methods
linear()	// This is the default method, if method is set
circular()
sin()
cubicBezier()
exponential()
Types
easingIn( a )          // Easing applied to the start of the animation
easingOut( a )	      // Easing applied to the end of the animation
easingInToIn( a, c )   // Two successive easings of type In
easingInToOut( a, c )  // Two successive easings, one type In and one Out
easingOutToIn( a, c )  // The opposite to the above
easingOutToOut( a, c ) // Two successive easings of type Out

bounce( b, r, e )      // Gives a bouncing effect ( Think Ball )
elastic(b, r, e )      // Gives an elastic effect ( Think Bungy jump )

All of them can take an optional argument a for the strength of the ease. The default value is 1.

The doubly easing type functions takes another optional value 0 - 1, to set the fraction of the of the interpolated interval, where the easing changes from the first type to the second.

The special bounce and elastic types takes three optional arguments, where b is the number of bounces, default value is 1, r is the amplitude reductions from one bounce to the next and e is an optional ease function to replace the default exponential function.

Was that a mouthful or not!

A nice applet to show off what easing can do is presented by Robert Penner, the expert.

Let's see how it actually works in a few simple examples. We'll keep the whole setup as before but set the type to easingOut and the method to exponential.

	// Create a new Ease
	var easing = new Ease();
	// Set the easing type and method
	easing.easingOut(2.5).exponential();

 

Setting the vales to get the behavior we want is a bit tricky, so you'll have to experiment to get it right. Let's look at the easingIn version too.
We set the new easing charteristics by

	easing.easingIn(2.5).exponential();

Ball and Bungyjump

The bounce and elastic easing methods uses the exponential sub method for the easing.
Let's let gravity work horizontally for the moment and show them too.

	easing.easingOut().bounce(3,0.4);

This is the ball type of animation.
The amplitude is reduced by a factor 0.4 for each bounce.

	easing.easingOut().elastic(3,0.4);

This is the bungyjump type of animation with the same amplitude factor.

Listen to the interpolator!

As it works hard to effectuate the animation we ordered, the interpolator fires events. to let us know what's going on. You can pause and resume the animation by calling the interpolator's pause() and resume() functions. When you resume the interpolator fires an onResumeEVENT. As it is running it fires onProgressEVENT's and when the animation ends, it fires an onEndEVENT.

We can listen in and take actions on these events. If we, for example, listen for the onEndEVENT, we can tell the interpolator to do it again, by calling its redo() function, like we did in the button event handler in the above examples. This is good if we want an automatic looping animation. We can also use the yoyo() function to revert the animation.

Let's make a looping rotating cube with a bounce!

In the createScene function we first create a cube, using the Box primitive, to replace the pyramid. We also change the height and width of the stage and the ClipScreen.

We use a RotationInterpolator with an Ease object set to the easingOut type using the bounce method.

	var easing = new Ease();
	// Set the easing type and method
	easing.easingOut().bounce(2,0.3);
	// We create an interpolator to carry out the transformation
	rotation = new RotationInterpolator( easing.create(),100, -90, 90);

In the bounce method we request 2 bounces, with an amplitude reduction factor of 0.3.
As you may recall, the bounce method is exponential by default.
The rotation spans 100 frames and goes from -90 to 90 degrees.

To make the animation loop, we listen to the onEndEVENT fired by the interpolator, when the animation ends, and let the event handler call the interpolator's yoyo function.

	// Listen to the onEndEVENT to make a loop
	rotation.addEventListener( InterpolationEvent.onEndEVENT, this, loop );

The event is defined as InterpolationEvent.onEndEvent, 'this' is the current scope and loop is the event hander, defined outside the createScene function.

// The onEndEVENT event handler calls the yoyo function of the interpolator
function loop(){
	rotation.yoyo();
}

In the first animation, the cube rotates around its center and the rotation axis is the default y axis.

In the second it still rotates around its center, but with a new rotation axis, set by setAxisOfRotation to be parallel to the vector [5, 5, 5].

// Set the axis of rotation
rotation.setAxisOfRotation( new Vector( 5, 5, 5 ));

In the third animation we have the default axis of rotation, but with a new center of rotation, set by setPointOfReference.

// Set a reference point
rotation.setPointOfReference( new Vector( 30, 0, 0) );

The vector argument to setPointOfReference sets center of rotation as an offset from the default center, not the position in the global coordinate system.

What now?

You might well ask, but the answer is simple: Almost anything can be done and your fantasy is the limit ( as they say ;).

Want an object or a group of objects move back and forth along a curved path?
Use the PathInterpolator with a BezierPath describing the path.

Want to make a walking man from primitives?
Go ahead and construct the parts, add interpolators to the joints with appropriate easing, rotation axes and centers of rotation. Add some translation to the guy, so he's getting somewhere.

For now I'll leave that project for you, my most valued reader ;)

Next on the agenda are Faces and Skins