Tutorial 8 of 10
ANIMATION CODING - Let's Get It ON!
CREATOR NOTEby Morning.star, published with permission
The Animation Basics
Animation coding looks complicated until you understand what the various parts of the coding are & why they're there, what they're doing. Read through the information here for the explanations & begin the process of familiarizing yourself with animation jargon.
The 1st part of an animation's coding is the piece or group of pieces you want to animate. As an example, let's say we're creating a single door & because we are using it in an animation, we have to DEF it ( give it a name ) on the Translation line as you see below. If you've grouped a number of pieces together, the DEF goes on the Group's Transform line instead :
DEF Door1 Transform { translation 0 1.5 0 children [
Shape { appearance Appearance { material Material {}
texture ImageTexture { url "door.gif" }}
geometry Box { size 1.5 3 0.01 }}
]}The other main areas of animation coding are :
The TimeSensor - a clock that determines how quickly the animation moves along
The sensors that activate the animation - ProximitySensor (PS) & TouchSensor (TS)
The Interpolators ( a bunch of them are named below )
The script - there are different kinds of these but we'll only be dealing with one - the OnOff script
And finally, the ROUTES that hook everything up & give the commands
All of these are parts of coding that work together to make your animation happen & they each, except for the ROUTES, require that you DEF ( define or name ) them so they can be used or referenced in the ROUTES area of the coding.
- * * * * * * * * * * * * * * * * * * *
The TimeSensor
The TimeSensor is the clock for the animation, governing the total time it will take for the animation to run from start to finish as well as whether the animation will repeat or not :
DEF Clock1 TimeSensor { cycleInterval 10 loop FALSE },The lower the number of the cycleInterval the faster the animation will run; the higher the number, the slower it will run. The loop is straightforward as well. Set to FALSE the animation runs through once & stops. Set to TRUE the animation continues to repeat.
- * * * * * * * * * * * * * * * * * * *
ProximitySensor & TouchSensor
These sensors define what condition the user must meet in order to activate the animation.
DEF PS1 ProximitySensor { center X X X size X X X enabled TRUE },The center attribute defines the point from which the calculation of the size of the active field is established. Typically, one would use the very center of the object being animated for the center coordinate.
The size attribute refers to the size of the active field, ie, the field within which the animation will run. For Mall objects, there are fairly low maximum limits required for the field size ( approx 25 25 25 ) but I'd go lower values ( like 10 10 10 ) regardless.
The numbers used for center & size are the same kind as those used elsewhere for object positioning : the 1st number refers to the position to be covered left/right of center. The 2nd number refers to the vertical position to be covered; & the 3rd number refers to the front/back position to be covered.
The enabled attribute defines whether or not the sensor will be activated. Set it to FALSE & the sensor ( & therefore, your animation ), will not function. Take a look at the 'Grill Master 2025' in the club - the color of the coals starts pusating when you approach the object.
- * * * * * * * * * * * * * * * * * * *
DEF TS1 TouchSensor {},
With this sensor, all the user needs to is click the object to activate the animation. For this sensor, there are no attributes, it is simply on if it's included. It's a good idea when using a TouchSensor to Group the object(s) & sensor together as well so that the activation function doesn't spill over onto other parts of your object.
- * * * * * * * * * * * * * * * * * * *
The Interpolators
Sounds like a gang of spies but what they really are is detailed sets of instructions outlining the path of an animation. There are 4 interpolators :
PositionInterpolator (PI) - defines a translation path of an animation OrientationInterpolator (OI) - plots the rotational path of an object & is often used in combination with the PI ColorInterpolator (CI) - plots a color sequence animation ScalarInterpolator (SI) - plots a sequence of varying conditions with regard to material attributes like transparency
- * * * * * * * * * * * * * * * * * * *
The PositionInterpolator (PI) Coding
DEF PI1 PositionInterpolator {
key [ 0 0.05, 0.9, 1, ]
keyValue [ 0 1.5 0, -0.75 1.5 0, -0.75 1.5 0, 0 1.5 0, ] }The key is where you define at which TEMPORAL POINT ( point in time ) in the animation a particular action defined in your keyValue is going to occur. Always begin your key numbers with 0 & end with 1.
The keyValue is where you code in the translated movements of the object ( the series of coordinates you want your object to occur at in the course of the animation ). Make certain that the 1st translated coordinate you define is THE SAME as the object's main translation coordinates or you'll find your object leaping into place before the animation begins. Then code in each set of coordinates you want your object to occur at using as many sets as it takes to construct your animation path. Just make certain that you have the SAME NUMBER of key points defined as you have keyValue coordinates. With the use of many keyValue coordinates you'll be getting into a lot of fractional key points.
- * * * * * * * * * * * * * * * * * * *
The OrientationInterpolator (OI) Coding
This interpolator governs rotational movement - front/back tilting, twirling, & left/right tilting.
DEF OI1 OrientationInterpolator {
key [ 0, 0.5, 1, ]
keyValue [
0 1 0 0,
0 1 0 3.14,
0 1 0 6.28, ] },The key again here plots your clock & must have the same number of points defined as your keyValue has coordinates.
The keyValue, though, is a little different in that each set of coordinates ( the rows ) has 4 numbers in it rather than 3 & they function completely differently. The 1st COLUMN ( not row ) refers to the front/back tilt. The 2nd COLUMN refers to a twirling rotation, & the 3rd COLUMN refers to a left/right tilt. Whichever action you want to use, you would put the '1's in that column & 0s in the other columns ( so in the example above, the piece will be twirling because the '1's are all in the 2nd column ).
The 4th column is used to define the DEGREE of rotation applied to the other 3 columns. Make certain that you code in the object's natural position with a 0 degree in the 1st row 4th column as in the example above or your object will snap in before commencing its animation. On your object's translation line the rotation coding will be reading rotation 0 0 0 0. This is a MUST DO if you want the animation to work at all ! The most commonly used rotations are 0.785/-0.785 ( for 45 degree ), 1.57/-1.57 ( for 90 degree ), 3.14/-3.14 ( for 180 degree ) & 6.28/-6.28 ( 360 degree ).
QUICK TIPs :
- If you are using an OI on a GROUPED set of objects, make certain that the pieces are built around the 0 0 0 translation coordinate. Only afterwards move the Group into whatever geographic position you desire on an additional translation line otherwise the animation will skew off position.
- You can put '1's in more than one column to achieve combination actions but don't be surprised if you get a wonky result because rotations also work off of the center point of an object(s) & if those are off, you'll get an unwanted result. I find that the only way to overcome this is to separate the animations - do one of the rotations first, then do a second translation & rotation line, & then regroup for another rotation animation. A bit complicated, yep.
- * * * * * * * * * * * * * * * * * * *
The ColorInterpolator (CI)
The CI shifts color values along the RGB spectrum from RED to GREEN to BLUE although you can code it to shift between more limited color choices as well, as in the example coding here :
DEF CI1 ColorInterpolator {
key [ 0, 0.33, 0.66, 1, ]
keyValue [ 0 0 1, 0 1 0, 0 0 1, 0 2 0, ]},The same rules apply to the key here as for the prior interpolators. The keyValues for the CI are what you code in sequentially of the RGB material colors. My Starlight Christmas series ( wreath, garland & tree ) all use this kind of animation. I've put the tree in the club for you to see.
- * * * * * * * * * * * * * * * * * * *
The ScalarInterpolator (SI)
Material attributes such as shininess & transparency can be manipulated with the SI. For example, you can turn an object completely transparent or have it flicker in & out using this interpolator ( handy for a fire effect ). Help yourself to a celebratory glass on the 'New Years Toast' tray !
DEF SI1 ScalarInterpolator {
key [ 0, 0.33, 0.66, 1, ]
keyValue [ 0, 1, 0, 1, ] },The keyValues for the SI vary according to the material attribute used, in most instances from 0 to 1. Have another look at the tutorial on material Material if you need to refresh your memory with regard to those attributes.
- * * * * * * * * * * * * * * * * * * *
Scripting
A certain kind of animation can't be done using the interpolators alone. That's where javascripting comes in. Oh oh, I thought I heard some thunks that sounded like bodies hitting the floor *wink* Not to worry. If you are a javascripting expert, you can write new & exciting scripts for VRML. If not, you can use this one to create click to start/stop animations. This is what its coding looks like :
DEF OnOff1 Script {
field SFNode tproc USE Clock1
field SFBool state FALSE
field SFTime pause 0
eventIn SFTime toggle
eventOut SFTime triggerStart
eventOut SFTime triggerStop
eventOut SFTime triggerStartInt
eventIn SFBool activated
eventIn SFTime cycle
directOutput TRUE
url "vrmlscript:
function activated( active, curtime ) {
if( active ) {
triggerStart = curtime;
state = TRUE;
}
else {
triggerStop = curtime;
state = FALSE;
}}
function cycle( curtime ) {
triggerStartInt = curtime;
}
function toggle( stime ) {
if( state )
{
state = FALSE;
pause = stime - tproc.startTime_changed;
tproc.enabled = FALSE;
}
else
{
state = TRUE;
tproc.set_startTime = stime - pause;
tproc.enabled = TRUE;
pause = 0;
}}"}Copy/paste but don't mess with it ! No changing ANY of its values or attributes. No collapsing of lines! You should also DEF your Script & Clock uniquely ( even across different objects you create ). And I'll include this now so it doesn't get lost down below. These are its ROUTES. Both lines must be included ( more about ROUTES below ) :
ROUTE TS1.touchTime TO OnOff1.toggle ROUTE Clock1.cycleTime TO OnOff1.cycle
- * * * * * * * * * * * * * * * * * * *
Grouping
A Group is what glues pieces together & defines the scope of an ainimation, ie. everything involved in one. You should Group the whole piece including all of its animation coding :
DEF Animation Group { children [Name it, & put that line at the top of your coding for the animated object, & place its matching brackets ]} right after all the ROUTES. Grouping all of these parts together will make sure that none of the animations present in an object will affect each other.
QUICK TIP : It's useful sometimes to create an animation at 0 0 0 & when you're done, Group it & Translate it into position. You can also try doing this if you're having a skewing problem.
- * * * * * * * * * * * * * * * * * * *
The ROUTEs
Once you have all of your parts assembled & grouped, you're going to need something to hook their actions together - instructions to make the animation function. The ROUTEs are those instructions. Basically what a ROUTE does is say "take this & hook it up with that", so think logically about which parts need to be hooked together to make a functioning animation.
Begin by hooking your ProximitySensor or TouchSensor up with the TimeSensor ( your clock ). Each of these types of sensors has its own kind of coding :
For the PS1 ProximitySensor :
ROUTE PS1.enterTime TO Clock1.set_startTime # hooks it up ROUTE PS1.exitTime TO Clock1.set_stopTime # unhooks it
For the TS1 TouchSensor :
ROUTE TS1.touchTime TO Clock1.set_startTime # hooks it up, no need to unhook
Once you have the above ROUTE in & the user satisfies the condition - by entering or touching ( clicking ), the animation's clock will start running. Now we need to hook the clock up to the main event - the animation path - all those interpolators & the on/off script.
For the PI1 PositionInterpolator :
ROUTE Clock1.fraction_changed TO PI1.set_fraction
For the OI1 OrientationInterpolator :
ROUTE Clock1.fraction_changed TO OI1.set_fraction
For the CI1 ColorInterpolator :
ROUTE Clock1.fraction_changed TO CI1.set_fraction
For the SI1 ScalarInterpolator :
ROUTE Clock1.fraction_changed TO SI1.set_fraction
Now your clock is hooked up to your interpolator, but, as they say, "all dressed up & nowhere to go". Now you need to tell your coding WHAT to do with the interpolator that is hooked up to the clock which has started ticking because the user activitated it. In these examples I will use DEF Door1 from our example below ( no need to go look right now ).
For the PI1 PositionInterpolator :
ROUTE PI1.value_changed TO Door1.set_translation
For the OI1 OrientationInterpolator :
ROUTE OI1.value_changed TO Door1.set_rotation
For the CI1 ColorInterpolator :
First make certain that you have DEFd the material in your object. You can apply animation to 1 or more material color attributes. Here's how to DEF the material :
material DEF Color1 Material { diffuseColor X X X specularColor X X X emissiveColor X X X shininess X transparency X }You need that material DEF to write the CI ROUTEs :
ROUTE CI1.value_changed TO Color1.set_diffuseColor ROUTE CI1.value_changed TO Color1.set_specularColor ROUTE CI1.value_changed TO Color1.set_emissiveColor
Choose whichever 1 of these you want to animate. If you want to animate more than 1 of them, you'll have to write a series of different CIs ( for example CI1a, CI1b etc ), & include the above ROUTE lines rewritten with the correct DEF names accordingly.
For the SI1 ScalarInterpolator :
Again, 1st DEF the material attribute as in the example above & if you want to animate more than 1 material attribute you'll have to write a separate SI for each & change the lines below to include the correct DEF names :
ROUTE SI1.value_changed TO Color1.transparency ROUTE SI1.value_changed TO Color1.shininess
QUICK TIPS
- Keep the DEFd names for your coding short & sweet. The ones I have used here are the ones I use in my own work & you are free to use them as well. Remember - the longer the name, the larger the filesize !
- When using more than one animation in an object or world, number your animations consecutively & number all of the DEFd parts of each with that respective number. So, for example, your 1st animation would include Door1, Clock1, TS1, & PI1. Your 2nd animation would include Door2, Clock2, TS2, & PI2; & so forth.
- It's easier to keep track of your animations if you keep all of them in order at the bottom of your handcoding instead of distributing them throughout your code.
- * * * * * * * * * * * * * * * * * * *
A Note Before We Begin with Examples of Animated Doors :
You can use a Box to make an animated door, but there are situations where that kind of shape will make it impossible to animate & have it function the way you want it to. I would steer away from them entirely & learn how to code an Extrusion instead. The reason for this is that an animated Box will ALWAYS move from its centerpoint, which may not be a problem if you simply want to move it sideways, up & down, or back & forth, or you want it to twirl rather than be a rotate-on-a-hinge kinda piece. But if you do want it to be a realistically opening door, then you'll need to use the kind of coding where you can create the pivot point at 0. Enter the Extrusion ! So, before we go any further, here's one of those, coded to rotate :
Transform { translation 0 0 0 children [
Shape { appearance Appearance { material Material {}}
geometry Extrusion { creaseAngle 1 beginCap FALSE endCap FALSE solid FALSE
crossSection [ 0 0, -1 0, ] # this one goes from center to left starting at 0 & is going to pivot from 0. #Translate it to your right doorframe after animating
spine [ 0 0 0, 0 2 0, ] # it's 2 meters tall
scale [ 1 1, 1 1, ]}} # you want it to be exactly the size defined by the crossSection & spine
]}crossSection [ 0 0, 1 0, ] # this one goes center to right starting at 0 & is going to pivot from 0. Translate it to your left doorframe after animatingYou can use either of these for a sliding door or a pair of sliding doors. See the doors for the modular homes that I've put in the club - the 'Cosmo Pocket Doors' & the 'Manse Doors' & the 'Playtime Curtains', each of which is animated differently.
- * * * * * * * * * * * * * * * * * * *
The Sliding Door
Ok, have a look at this puppy. I'm using a Box here & a TouchSensor. Very simple stuff.
Group { children [
DEF Door1 Transform { translation 0 -0.25 0 children [
Shape { appearance Appearance { material Material {}
texture ImageTexture { url "door.gif" }}
geometry Box { size 2 3 0.01 }}
]}DEF Clock1 TimeSensor { cycleInterval 10 loop FALSE },
DEF TS1 TouchSensor {},
DEF PI1 PositionInterpolator {
key [ 0, 0.1, 0.9, 1, ]
keyValue [ 0 -0.25 0, -1.5 -0.25 0, -1.5 -0.25 0, 0 -0.25 0, ]},ROUTE TS1.touchTime TO Clock1.set_startTime
ROUTE Clock1.fraction_changed TO PI1.set_fraction
ROUTE PI1.value_changed TO Door1.set_translation
]}- * * * * * * * * * * * * * * * * * * *
The Opening ( Rotating ) Door
Here's where you're going to need that EXTRUSION coding to build a door so you can have it rotating in a normal, not a turnstile, fashion because its pivot point will be coded at 0. For our purposes here I'm leaving the door at 0 side to side rather than further translating it to the left.
Group { children [
DEF Door1 Transform { translation 0 -1.75 0 rotation 0 0 0 0 children [
Shape { appearance Appearance { material Material {}
texture ImageTexture { url "door.gif" }}
geometry Extrusion { creaseAngle 1 beginCap FALSE endCap FALSE solid FALSE
crossSection [ 0 0, 1 0, ]
spine [ 0 0 0, 0 2 0, ]
scale [ 1 1, 1 1, ]}}
]}DEF Clock1 TimeSensor { cycleInterval 10 loop FALSE },
DEF TS1 TouchSensor {},
DEF OI1 OrientationInterpolator {
key [ 0, 0.2, 0.8, 1, ]
keyValue [
0 0 0 0,
0 1 0 1.57,
0 1 0 1.57,
0 0 0 0, ] },ROUTE TS1.touchTime TO Clock1.set_startTime
ROUTE Clock1.fraction_changed TO OI1.set_fraction
ROUTE OI1.value_changed TO Door1.set_rotation
]}To have the door swing in the opposite direction just change the 1.57 to -1.57.
- * * * * * * * * * * * * * * * * * * *
Double Opening & Closing Doors
I'm not going to use sliding doors for this example. The principle is the same if you want to do pocket doors - you need to make certain that you recode the Extrusion so that you build the 2nd door in the opposite direction & code its PI to open it in the opposite direction as the other door.
Here is the coding for doors that open & close simultaneously on a TouchSensor :
Group { children [
DEF LDoor Transform { translation -0.74 -1.75 0 rotation 0 0 0 0 children [
Shape { appearance Appearance { material Material { emissiveColor 0.3 0.3 0.3 }
texture ImageTexture { url "door.jpg" }}
geometry Extrusion { beginCap FALSE endCap FALSE solid FALSE
crossSection [ 0 0, 0.74 0, ]
spine [ 0 0.25 0, 0 2.5 0, ]
scale [ 1 1, 1 1, ]}}
]}
DEF RDoor Transform { translation 0.74 -1.75 0 rotation 0 0 0 0 children [
Shape { appearance Appearance { material Material { emissiveColor 0.3 0.3 0.3 }
texture ImageTexture { url "door.jpg" }}
geometry Extrusion { beginCap FALSE endCap FALSE solid FALSE
crossSection [ 0 0, -0.74 0, ]
spine [ 0 0.25 0, 0 2.5 0, ]
scale [ 1 1, 1 1, ]}}
]}DEF TS1 TouchSensor {},
DEF LOI OrientationInterpolator {
key [ 0, 0.2, 0.8, 1, ]
keyValue [
0 0 0 0,
0 1 0 -1.57,
0 1 0 -1.57,
0 0 0 0, ]},DEF ROI OrientationInterpolator {
key [ 0, 0.2, 0.8, 1, ]
keyValue [
0 0 0 0,
0 1 0 1.57,
0 1 0 1.57,
0 0 0 0, ]},DEF Clock1 TimeSensor { cycleInterval 20 loop FALSE },ROUTE TS1.touchTime TO Clock1.set_startTime
ROUTE Clock1.fraction_changed TO LOI.set_fraction
ROUTE Clock1.fraction_changed TO ROI.set_fraction
ROUTE LOI.value_changed TO LDoor.set_rotation
ROUTE ROI.value_changed TO RDoor.set_rotation
]}Easy peasey :)
QUICK TIPS :
- Make certain when choosing the sliding style of door that you have enough wall thickness to house the door in its open position. If your door is basically 0.001 thick you won't need a wall thicker than 0.2.
- When coding the TimeSensor have the door(s) open swiftly enough that folks don't have to stand around waiting for a gap to go through, & stay open long enough to allow for entry. If you look at the example above, the opening of the doors is set at 0.2 - that's a swift opening so that folks in motion won't run into it, & they close towards the end of the clock's cycle ( 0.8 ) to allow time to pass over the threshold.
- Don't overextend an active ProximitySensor field. Keep the field tight so users are not constantly activating the doors when they are not near them.
For an item involving a lot of different facets of animation, check out 'Its Funtime' up on the stage in the club.