Animation with interpolators
Animation comes in the following forms:
- Procedural: This type of animation is controlled by a program, such as the simulation of a bouncing ball impacted by real-world physics.
- Event-driven: This type of animation includes motion based on events in the scene, such as a simulation of a dog that sees, smells, hears, and then reacts to it.
- Key frame: This type of animation is where the animated character's movement(s) and rotation(s) occur at specific times. WebGL interpolates between these key frames.
The Moon orbiting around the Earth, which in turn orbits around the Sun, has a lot of good 3D graphic concepts to review. Note that the Earth's transformation is inside the Sun's transformation, and the Moon's transform is contained inside the Earth's transform. Thus, wherever the Earth goes, rotating around the Sun, the Moon will follow. Also, not only is the Earth 10 units away from the Sun, but it's center is -10 units, which means that the center of the Earth's rotation is the Sun. Now, the Earth also rotates around its own axis for a day; we will show this later. Have a look at the following screenshot:
Engage thrusters
Layout the code with all the objects such as the Sun, Earth, and Moon along with all the <Transform>
nodes, <TimeSensor>
nodes, and interpolators before the ROUTE
node. The order is important, just as we must have the <Transform>
nodes embedded properly to represent the annual, seasonal, and daily rotations of the Earth, as shown in the following code:
<Scene> <Viewpoint orientation="1 0 0 -.3" position="0 8 30"/> <NavigationInfo headlight="false"/> <PointLight/> <Transform DEF="Sun"> <Shape> <Sphere radius="2.5"/> <Appearance> <Material diffuseColor="1 1 0" emissiveColor="1 .5 0"/> </Appearance> </Shape> <Transform DEF="Earth" center="-10 0 0"translation="10 0 0"> <Shape> <Sphere radius="1.2"/> <Appearance> <Material diffuseColor=".2 .4 .8"/> </Appearance> </Shape> <Transform DEF="Moon" center="-3 0 0" translation="3 0 0"> <Shape> <Sphere radius=".6"/> <Appearance> <Material diffuseColor=".4 .4 .4"/> </Appearance> </Shape> </Transform> </Transform> </Transform> <TimeSensor DEF="yearTimer" cycleInterval="36.5" loop="true"/> <OrientationInterpolator DEF="YRotation" key="0 .5 1" keyValue="0 1 0 0 0 1 0 3.14 0 1 0 6.28"/> <ROUTE fromField="fraction_changed" fromNode="yearTimer" toField="set_fraction" toNode="YRotation"/> <ROUTE fromField="value_changed" fromNode="YRotation" toField="rotation" toNode="Earth"/> <TimeSensor DEF="moonTimer" cycleInterval="2.9" loop="true"/> <OrientationInterpolator DEF="YRotMoon" key="0 .5 1" keyValue="0 1 0 0 0 1 0 3.14 0 1 0 6.28"/> <ROUTE fromField="fraction_changed" fromNode="moonTimer" toField="set_fraction" toNode="YRotMoon"/> <ROUTE fromField="value_changed" fromNode="YRotMoon" toField="rotation" toNode="Moon"/> </Scene>
Objective complete – mini debriefing
The <TimeSensor>
nodes, interpolators, and the ROUTE
nodes create the key frame animation. The <TimeSensor>
node specifies the duration (cycleInterval
) of the animation, which is 36.5 seconds here, where each day represents one tenth of a second. Interpolators specify the key
and keyValue
functions such that for each key function, there must be a key value function. Since this is a rotation or change in orientation, we use <OrientationInterpolator>
. Also, there are <PositionInterpolator>
and <ColorInterpolator>
nodes that move the 3D mesh and change its color, respectively, over time. Unlike films, where we have a fixed 30 frames per second, in real-time animations we can have more or less frames per second depending on how complex the scene is and also on the performance of our CPU.
We will break down the interpolator at the three keys when the time equals 0, 0.5, and 1, as follows:
- The
<OrientationInterpolator>
node says that at time = 0, the rotation will be (0, 1, 0, 0), meaning a rotation of 0 radians around the y axis. - At time = 0.5, which is 18.25 seconds (half of 36.5 seconds) here, the rotation will be (0 1 0 3.14) or 3.14 radians, which is 180 degrees around the y axis.
- Finally, at time = 1, which is 36.5 seconds, the rotation will be 6.28 radians, which is a full 360-degree circle around the y axis.
So, why do we have to put a midpoint such as 180 degrees? The problem is that the <OrientationInterpolator>
node optimizes the rotation distances to be the smallest. For example, a 120-degree rotation clockwise is the same as a 240-degree rotation counter-clockwise. However, the <OrientationInterpolator>
node will take the shortest route and rotate 120 degrees clockwise. If you wanted to force a 240-degree counter-clockwise rotation, you'd need to add a midpoint.
Finally, we have to connect the timer to the interpolator with the 3D object that has to be rotated. The ROUTE
node directs value(s) from a timer or sensor to another sensor or object. The first ROUTE
node takes the output from yearTimer.fraction_changed
to YRotation.set_fraction
. Note the passing of a fraction value within the ROUTE
node. The timer will count from 0 to 36.5 seconds and divide this value by 36.5 so that it is a value between 0 and 1. The orientation interpolator will receive this fraction value and interpolate between the two key values. For example, at 10 seconds, the fraction is 10/36.5 or 0.274, which is between 0 and 0.5 in the <OrientationInterpolator>
node's key. This becomes a key value of (0 1 0 1.72), about 98.63 degrees (1.72 radians * 180 degrees/π).
The next ROUTE
node sends the keyValue
function from the <OrientationInterpolator>
node and sets the rotation values of the <Transform>
node defined as Earth, using the DEF="Earth"
statement inside the <Transform>
node. And with this, we nearly have our Solar System. We just have to add more planets.