For example, assuming one object with two animation tracks, motionTrack and rotationTrack, it makes sense to control both using a single controller: myObject.addAnimationTrackmotionTrack
Trang 1ANIMATION IN M3G
than180◦in 3D space In practice, this means that the dot product of any two adjacent
quaternion keyframes must be positive If this is not the case with your source data, you
can enforce it by adding extra keyframes for segments that rotate180◦or more in 3D
space
Note that each AnimationTrack object only defines the type of property to be
ani-mated, without specifying an object An AnimationTrack can therefore be
associ-ated with multiple objects to animate the same property, in the same way, for each one
Similarly, a KeyframeSequence can be associated with multiple AnimationTrack
objects, and hence multiple animation targets of possibly different types The only
restric-tion is that the keyframe types and animated properties must be compatible This makes it
possible to share the keyframe data between multiple objects, while being able to control
the animation of each one individually
16.3 TIMING AND SPEED: AnimationController
In addition to a KeyframeSequence and an AnimationTrack, each individual
animation needs an AnimationController to “drive” the animation The controller
is attached to each AnimationTrack object to define the speed and timing of that
particular animation Again, a single controller can be attached to multiple animation
tracks, and in noninteractive animation, a single controller will often handle the entire
scene However, using multiple controllers will give you the degree of flexibility you want
for playing back multiple animations simultaneously
For example, assuming one object with two animation tracks, motionTrack and
rotationTrack, it makes sense to control both using a single controller:
myObject.addAnimationTrack(motionTrack);
myObject.addAnimationTrack(rotationTrack);
AnimationController control = new AnimationController();
control.setActiveInterval(10000, 25000);
control.setPosition(0.f, 10000);
control.setSpeed(0.5f);
motionTrack.setController(control);
rotationTrack.setController(control);
Assuming milliseconds for the time unit, this would begin the animation of your
object at ten seconds into the animation, animate it at half speed for fifteen seconds,
and then stop The animation would start playing from the beginning of your keyframe
sequences
World time and sequence time
Before we look at AnimationController in more detail, we must be more
spe-cific about time as it applies to M3G animation With KeyframeSequence, we are
Trang 2talking in terms of sequence time: time 0 (zero) is the start of our keyframe sequence, and
all keyframes in the sequence are defined relative to that What you want to feed to the
animation system from the application, however, is usually world time: that can be time
passed since the start of your application, game session, or composite animation, or it may be the time of day if you so desire Individual animation tracks may start and stop
at arbitrary points in world time It would often be impractical to build your keyframe sequences using world time, so AnimationController lets you easily map world time to the time of each keyframe sequence
AnimationControlleruses two pieces of data to convert between world time and
sequence time: a reference point and relative speed You set the reference point by calling
setPosition(float sequenceTime, int worldTime) This is exactly equivalent to
saying “I want my sequence time to be sequenceTime when the world clock reaches
world-Time.” Often, your desired sequenceTime will be zero and you are just saying “I want my
sequence to start at worldTime,” but M3G gives you a bit more flexibility in mapping the
times
In addition to setting the position of your animation sequence, you may want to set the speed at which the sequence time passes relative to the world clock Figure 16.3
sequence time
world time
reference point
speed 5 2.0
speed 5 0.5
speed 5 1.0
Relationship of world time and sequence time with different speed settings.
Trang 3ANIMATION IN M3G
illustrates the relationship of the reference times and speed By default, sequence time
and world time are synchronized, but you can speed up or slow down a particular
animation, or specify different units of time for your keyframes You do this by calling
setSpeed(float speed, int worldTime) A speed of 0.5, for example, will make
your sequence run at half the normal speed, whereas 2.0 makes it twice as fast A speed
of 0.001 would let you specify your keyframes as whole seconds if your world time is in
milliseconds
Note the other parameter, worldTime Why do you need to specify that? That is the point in
world time at which your sequence speed change occurs Imagine you have been running
a long animation sequence for a while, and suddenly want to speed it up for one reason or
another You will still want to continue the animation from the current position, so if you
were to just change the speed factor, you would have to adjust the reference time point
to avoid a sudden jump Instead, setSpeed takes the current world time as input and
automatically makes that the new reference point The sequence time at the new
refer-ence point is changed to match whatever it was for your animation before you decided to
change the speed As a result, you will only notice that your animation continues to run
smoothly at the new speed
Weight and active interval
In addition to the speed of a controller, you can also set its weight via setWeight
(float weight) The interpolated animation data is then multiplied by weight prior to
applying it to its animation target This is useful for animation blending, which we will
describe in more detail in Section 16.5.2
Performance tip: You can speed up the animation process by telling M3G to ignore any
animations you do not need at any given moment You can do that by either setting
the weight of the respective controller to zero, or setting the controller of an animation
track to NULL
Finally, each AnimationController has an active interval, set via
setActiveInterval(intstart, int end) start and end are the world times at
which the activity of this animation controller begins and ends, respectively Each
con-troller is only considered in animation calculations if the current world time falls within
its active interval; otherwise, it is as if the animations controlled by that controller did
not exist at all
pitfall: The animations controlled by an inactive controller (either zero weight or
out-side of the active interval) are not updated at all In other words, the animation targets
are not reset to any default values when a controller becomes inactive In particular,
if you make a jump in your animation that lands the world time outside of the active
interval of any controller, the values animated through that controller will retain the
values they had before the jump
Trang 4On a related note, an animated M3G scene will contain initial values for everything when loaded from an M3G file, but there is no direct way to reset those initial values You must either reload the file, or have your animations active at time zero to set the correct values
16.4 ANIMATION EXECUTION
So, you have set up your KeyframeSequence, AnimationTrack, and AnimationController You have added the track to an object—say, myMesh How
do you effect your animation to the mesh? You call animate(int worldTime), passing
in the time currently displayed by the world clock you maintain in your application:
static long startTime = System.currentTimeMillis();
myMesh.animate(System.currentTimeMillis() - startTime);
You can call animate on myMesh, or if myMesh happens to be a part of myWorld, you can animate all of myWorld with a single animate call
myWorld.addChild(myMesh);
myWorld.animate(System.currentTimeMillis() - startTime);
Animation in M3G is always requested by the application There are no timers or events involved unless you want to involve them Neither does the animation engine have any state that you need to change to play, stop, or rewind your animations All you do is pass
in your desired world time to animate, and everything is updated to reflect that point
in time
Let us look at an example on event-based animation Assume an animation controller, actionController, controls a composite animation representing an action that a game entity would perform in response to some event To trigger the animation upon receiving that event, we only need one line of code in addition to the previous example:
actionController.setPosition(0.0f, eventTime);
Here, eventTime is the world time of the event When myWorld.animate() is next called, the animation controlled by actionController is automatically effected
on the target objects To re-trigger the animation in the future, another call to setPositionis sufficient
Performance tip: There is no way to read back the current state of a
par-ticular animation in M3G, but you may want to know the phase of an animation in order to execute some corresponding action in the game logic The duration of each animation could be encoded in the user data field of the animation controller, but you can also link that information directly to your animation: create an empty Group node
Trang 5ANIMATION IN M3G
and attach an animation track to its alpha factor You can then set the keyframe values
so that the alpha factor will contain any desired data values, synchronized to the actual
animation, which you can query directly If you only need a binary state, you can use
the node enable flags instead
Animation proceeds recursively: all objects reachable from the object being animated,
according to the rules set forth in Section 13.4.1, are automatically animated as well If you
call animate on a World object, for example, everything in your World is animated;
if you animate a Group, only objects within that group are touched Note, however, that
all referenced objects are animated—if your Group has a Mesh using a Texture2D
via an Appearance, the transformation of the Texture2D will also update if it has
an animation attached Normally, you need not worry about this—M3G just handles it
automatically for you, and the result is what you would expect in most cases
We mentioned at the beginning of this chapter that animation in M3G is essentially just
a way of quickly setting a number of parameters, and that is exactly what happens
When your animate function call returns, all the animated parameters will have
their new values and the animation engine leaves the rest up to you Normally, you
will just proceed to Graphics3D.render and draw your scene, but you are free
to modify anything between animation and rendering if you want to One common
step is to call align on your entire world or some specific nodes to compute any
node alignments you have defined However, you can do any other processing before
rendering You do not even have to render; you can just keep on animating if you
have a special use case for that—for example, you could use the spline interpolation
to generate vertex data by reading back the animated values after each animation call,
then assigning the data to a vertex array
16.5 ADVANCED ANIMATION
We have now covered the basic setup and execution of keyframe animation in M3G By
now, you can get simple animations up and running to modify your object parameters
We can now look at how to make more complex animations involving animated meshes,
blending between multiple animation sequences, and some useful animation features not
explicitly described in the M3G specification
16.5.1 DEFORMABLE MESHES
In Section 4.2 we discussed how morphing and skinning can be used to bring polygon
meshes to life M3G has support for both of these techniques via the MorphingMesh
and SkinnedMesh classes The former allows morphing between sets of vertex
attributes, and the latter implements skinning
Trang 6Creating a MorphingMesh is very similar to creating a regular Mesh In fact, the only
difference is that you need to pass in an additional targets parameter:
submeshes, Appearance[] appearances) The targets array is your set of morph targets The base vertex buffer gives the vertices for your undeformed mesh, and each buffer in the targets array is one morphed version of the
original mesh The morph targets only need to contain the vertex properties that change For example, if all of the morph targets share the same texture coordinates, those may be
specified only in the base mesh Morph targets cannot contain vertex attributes that are
not present in the base mesh, and the set of attributes in each morph target must be the same Refer to the M3G specification for more details
In the common case, you want to blend vertex positions and normals between multiple shapes, while retaining per-vertex colors or texture coordinates This is illustrated in Figure 16.4, and easily achieved in code:
F i g u r e 16.4: An example of morphing in M3G The base mesh, top left, is modified by blending in positions and normals from three morph targets See the code example in text.
Trang 7ANIMATION IN M3G
VertexArray basePositions, baseNormals, colors, texCoords;
VertexArray morphedPositions[3], morphedNormals[3];
IndexBuffer primitives;
Appearance appearance;
// Array initialization omitted
// Initialize the base vertex buffer
VertexBuffer baseVertices = new VertexBuffer();
baseMesh.setPositions(basePositions, 1.f, null);
baseMesh.setNormals(baseNormals);
baseMesh.setTexCoords(0, texCoords, 1/128.f, null);
baseMesh.setColors(colors);
// Initialize the morph target vertex buffers note that
// only the morphed attributes, i.e., positions and normals,
// are needed for these
VertexBuffer morphedVertices[] = new VertexBuffer[3];
for (int i = 0; i < 3; ++i) {
morphedVertices[i] = new VertexBuffer();
morphedVertices[i].setPositions(morphedPositions[i], 1.f, null);
morphedVertices[i].setNormals(morphedNormals[i]);
}
// Create the final mesh object
MorphingMesh mesh = new MorphingMesh(baseVertices,
morphedVertices, primitives, appearance);
// Set to an even blend between the base mesh and each morph target
float weights[3] = { 0.25f, 0.25f, 0.25f };
mesh.setWeights(weights);
Once you have the MorphingMesh constructed, you can animate it via the morph target
weights You can either call setWeights(float[] weights) to set them directly, or
animate the MORPH_WEIGHTS property of the mesh The keyframes in that case will be
vectors that have one floating-point weight corresponding to each morph target However
you apply the weights, each morph target will contribute to the final mesh shape according
to its weight The actual equation is
M = B +w i (T i − B) = (1 −w i )B +w i T i, (16.1)
that is, the difference between the base mesh B and each morph target T is weighted and
added to the base mesh to produce the final shape M Note that if the weights sum to
Trang 8one, the effect of the base mesh is canceled out and you are only blending between your morphed shapes You are free to use any weights, though, including negative ones An alternative way of thinking about this is that each morph target represents a single feature, and you can blend these features to alter your base mesh This approach is often used to produce facial animation, with different expressions being blended in
pitfall: In morphing, it is your responsibility to make sure that the morphed vertex
coor-dinates do not overflow the numeric range of your vertex buffer M3G can handle very large intermediate results (from weighting each morph target), but the end result must still fit within the original range of either 8 or 16 bits
SkinnedMesh
Now, let us build a complete animated character using skinning Most of this will already be familiar, so we will begin with a practical example before introducing the new functions in detail While the code is fairly straightforward, there is quite a bit
of it, so we have split it into a couple of sections The entire example is also available from the companion web site
Example: Building a Skinned Character
Our skinned character is shown in Figure 16.5 First, we will construct the skinned mesh object with the various bones that we can control separately:
pelvis
right thigh
torso left upper arm right upper arm
left fore arm right fore arm
neck head
left shin right shin
F i g u r e 16.5: Our example of a skinned character The rendered figure is shown on the left, and the skeleton group on the right The illustration shows the origin (sphere) and primary axis (triangle) of each bone, while the arrows indicate parent-child relationships in the scene graph The torso node
is co-located with the root node, pelvis, emphasized.
Trang 9ANIMATION IN M3G
private SkinnedMesh stickMan;
private Group torso, neck, head;
private Group leftThigh, rightThigh, leftShin, rightShin;
private Group leftUpperArm, rightUpperArm, leftForeArm,
rightForeArm;
We have defined simple 2D vertex data along the outlines of our character From this, we
construct the vertex and index buffers as well as the actual mesh object in a way that is
similar to past examples:
static private byte vertices[] = {
// Head and neck
// Arms and torso
// Lower body and legs
};
static private int tristrips[] = {
1, 0, 3, 2, 5, 4, 7, 6,
8, 9, 10, 11, 12, 13, 6, 20, 7, 21, 18, 19, 16, 17, 14, 15,
20, 22, 21, 23, 24,
23, 22, 26, 25, 28, 27,
24, 23, 30, 29, 32, 31
};
static private int striplens[] = { 8, 16, 5, 6, 6 };
// Create the vertices and triangle strips for the mesh
VertexArray pos = new VertexArray(vertices.length / 3, 3, 1);
pos.set(0, vertices.length / 3, vertices);
VertexBuffer vb = new VertexBuffer();
vb.setPositions(pos, 1.f, null);
vb.setDefaultColor(0x000000);
IndexBuffer ib = new TriangleStripArray(tristrips, striplens);
stickMan = new SkinnedMesh(vb, ib, new Appearance(),
new Group());
Trang 10Connecting the Bones
So far, our mesh is no different from regular Mesh class objects, except that there is an empty group to serve as a skeleton Next, we will create the bones and connect them into
a skeleton as shown in Figure 16.5, starting with the group we already inserted above:
Group pelvis = stickMan.getSkeleton();
// Connect the torso, neck, and head torso = new Group();
pelvis.addChild(torso);
neck = new Group();
torso.addChild(neck);
neck.setTranslation(0.f, 60.f, 0.f);
head = new Group();
neck.addChild(head);
head.setTranslation(0.f, 20.f, 0.f);
// Connect the arms to the torso leftUpperArm = new Group();
torso.addChild(leftUpperArm);
leftUpperArm.setTranslation(30.f, 50.f, 0.f);
leftUpperArm.setOrientation( — 90.f, 0.f, 0.f, 1.f);
leftForeArm = new Group();
leftUpperArm.addChild(leftForeArm);
leftForeArm.setTranslation(0.f, 50.f, 0.f);
Note how the arms, for example, are offset inside the torso group The translation of each bone determines the location of its hinge point (origin) relative to the hinge point of its parent bone We have also used a convention where the Y axis of each bone runs along the length of the bone, so we are rotating some of the bones The character defined by the untransformed vertices is standing with arms stretched out to the sides, and our bones now match that rest pose
Attaching the Skin
The final step in creating a skinned character is attaching flesh to the bones We must tell M3G which vertices each bone should affect, and if multiple bones affect any single vertex, M3G will average their influences based on the weights assigned to each bone You can use any integer values for the weights—in this example, we use a nominal scale of 0
to 100 We have also laid out the vertex data so that we can attach each bone to a group
of vertices at once: