Character Modeling for Semi-Autonomous Animation

Building a Dude using WavesWorld

Traditionally, animation on computers has been similar to clay animation in that each motion in a character (or dude) needs to be specified throughout every scene. Some advantages exist in computing; namely you can allow the computer to interpolate (in a variety of ways, like linear and B-spline) between positions so that you needn't specify positions at every single frame. However, there haven't been very many good methods for re-using a series of motions in conjunction with others. Certainly you can re-run a motion sequence just like the first time you did it, but if some other factors also effect the character's motion, it is difficult to merge and reconcile any completing demands without doing a lot of re-writing.

WavesWorld provides a wonderful environment which icludes a mechanism for teaching characters to do things, like turn around towards an object. Furthermore, these abilities can be generalized so that a character can execute an instruction in a different way depending on its state and the state of its enclosing environment. Perhaps your character is tired, and will therefore turn more or less slowly. Or perhaps she is particularly giddy and turns quite suddenly and enthusiastically. And if she needs to be chewing on her bubble gum while turning, you merely tell her to do both. The exiting thing about WaveWorld is that you can build characters with various skills upon which you can draw at any time.

WavesWorld uses a modeling language called Eve, which is TCL with some extra features like a complete set of Renderman interface calls, some handy math functions, and the ability to specify that some commands should be re-evaluated at every sample, which others remain constant. In Eve, you can model any object using RenderMan primitives, or by including RIB objects modelled with other software. Then you can specify how objects and parts of objects interact with each other.

Additionally, WavesWorld provides a mechanism called a RIB Well by which you can instantly modify a dude's state and see the result, allowing you to preview the effect of certain Eve commands, and letting you test your character's functionality.

WavesWorld was written by Michael B. Johnson (Wave) as his Ph.D. thesis at the MIT Media Laboratory, under Professor David Zelter. For more information about using WavesWorld, consult his thesis document, WavesWorld: A Testbed for Semi-Autonomous Animation, available on-line as http://xenia.media.mit.edu/~wave/PhDThesis/outline.html, and from the MIT Libraries. (See also Wave's home page at http://xenia.media.mit.edu/~wave/.)

A character model in WavesWorld consists of shape, shading, and state. Shape and shading are what you seen on screen when you look at the character; state is a bit less visible, but determines the behavior and personality of a character, and is therefore quite perceptible in a scene. In this tutorial, we'll model a character - from concept sketches to final product - beginning with his shape (and, quite underemphasized, his shading), and ending with some state variables for emotion, so we can draw on these later. We'll consider the animatability of Tony's parts as we design them, and then having built a working model, we will write a script for a particular action our dude will perform and see the implementation of this new skill.

Shape

The first thing one needs is a character shape in the model. A character's shape is comprised of some geometric primitives - like spheres, cones, and patch meshes - and the transformations - like scale and rotate - which are applied to them.

Characters can be made up from a variety of geometric primitives, or from shapes created using any of a variety of modeling programs, such as Pixar's Showplace. Therefore, a character can be arbitrarily complex, with any number of parts.

Modeling Software

Modeling programs provide a powerful interface to allow one to create arbitrary shapes visually. This is very useful, since it is difficult to create complex shapes from primitives by hand in Eve code. However, when building a model, it is important to retain as much control as is necessary to make the characters do interesting things. This presents a problem to users of modeling programs. Modeling programs are good for creating static objects - everything in a given model is a single, rigid body. It is easy enough to apply linear transformations, such as shearing and scaling to a static model, but not much more. What we're usually looking for in animation is a character with articulated parts. Parts of the character should be able to move around with respect to the rest of the model. Rigid bodies don't allow parts to move independently, and are therefore difficult to work with.

This doesn't mean that using modeling programs is out of the question - in fact it is still very important if one wants to make a character model from complex shapes. The solution is to create each individual part separately using a modeler, make each into an Eve model, and then combine them together in a single Eve model.

State

An animated character has a state comprised of information like the position of its movable parts, which effects its geometry, and its emotional state, which effects what it may do next. The procedural agents which act on the model do so by manipulating the model's state. It is therefore important to consider what state variables one might need to represent the state of the character. When defining the character's parts and the transformations that act on them, one could factor in the these variables.

Meet Tony

I chose to model a character that could be modeled almost entirely with standard geometric primitives, so that rather than spending a lot of time using other modeling software, I could do most of the construction of my dude within WavesWorld using Eve commands. After some thought, I decided that household appliances have a certain appeal and settled on a vacuum cleaner, recently named Tony, as the basis for my dude. The vacuum cleaner I envisioned was geometrically simple, but had enough articulated parts to make for an interesting character. For example, Tony could run around finding garbage to consume when he was hungry. As Tony ate, his sack would fill up, and he would no longer be hungry.

I jotted down the original concept drawing in my pocket notebook:

concept

Parts List

I then proceeded to itemize each of Tony's articulated parts. Each part would be constructed from a Renderman geometric primitive, with the exception of the sack, which would be modeled using SolidThinking:

Connecting the Parts

It is very difficult, or at the least annoying, to control the character as a whole if all of the parts need to be manipulated individually every time you want to move the whole thing. Therefore it would be nice if parts could be grouped together in a coherent way. Rather than having each part positioned relative to the world, they can be positioned with respect to each other. In Tony's case, for example, the sack is always positioned just below the spine. Therefore, if the spine moves, it would be good if the sack moved with it. This suggests a hierarchy like this:

Tony

The Origin

origin

The above hierarchy works well because now, when the head moves, the eyes (and pupils) follow. Similarly, the tail moves with the spine. Note, however, that now when you move the tail, you move it with respect to the spine, not with respect to the whole character. Tony's body must have an origin point (we'll call it O(T)). All transformations applied to Tony are applied with respect to his origin. For example, if you rotate Tony, he will turn on an axis which goes through O(T). Tony's position is also defined by the position of his origin with respect to the world's origin. One can move Tony's origin by translating him body with respect to it, but for simplicity, we want to keep his origin in one place. We will construct Tony such that his origin lies right in between the bottoms of his two wheels (see figure 2). This makes moving Tony around on a plane (ie. the floor) simple.

However, now we need to consider how each part of our character will move. If, say, we want to be able to wag Tony's tail, we don't want to rotate it with respect to Tony's origin, as in that case it will move or of alignment with the spine. We therefore need the tail to have an origin of its own. As we model each part of Tony, we'll consider what motions we expect that part to be able to articulate. We'll then decide where it's origin should be.

Implementation

At this point, we can begin modeling our character in Eve. Our model.eve file looks like this:


source $modelPath/defines.eve
source $modelPath/procs.eve

loadControlPanel controls.nib

startShape Tony
   drawVacMan
endShape

source $modelPath/animation.eve

The file defines.eve will contain the initial definition of all of Tony's global variables, including all of Tony's state variables, which may be read (sensed) and/or manipulated by any skill and sensor agents we may later write.

The file procs.eve will contain the procedures use to draw Tony's body. We will be using only one procedure for Tony, drawVacMan().

controls.nib is the Interface Builder generated user interface for Tony which we will create to test out Tony's abilities.

We define a shape Tony which is Tony's body. The procedure drawVacMan will handle the details of drawing Tony's body.

Finally, animation.eve will contain the procedures used to define Tony's skills and sensors.

Articulated Parts

We will begin by defining Tony's various articulated parts. We'll be concurrently adding to defines.eve and procs.eve as we go. For each part, we'll go through the primitive or pre-modeled object used as that part, and the transformations and variables we'll use to control the part's position and shape.

The Spine

The first item we will create is Tony's spine. Since vacuum cleaners tend to have spines which are straight tubes, a cylinder is pretty well suited for the task of being a spine.

We'll define a shape Spine within the shape Tony. This means that any transformations applied to Tony are applied to Spine. Furthermore, Spine's origin is defined in relation to Tony's origin. This is good, as it allows for the hierarchy we want: If we move Tony, we move Spine with him, yet we can move Spine around independently, in relation to Tony.

In relation to Tony want the bottom of Tony's spine to be slightly above the XZ plane and slightly behind the YZ plane (see Figures 3b and 3c). We also want to tilt it back some around the Z axis, but we want to do this before we move the cylinder away from the origin. The result is that the rotation happens at the bottom of the spine (see Figure 3d), and therefore we will call that point the local origin of Spine (O(S)).

We want to be able to manipulate the displacement of the spine from O(T), the radius and length of the spine, and the spine color, so we create some public variables which agents are encouraged to play with (see listing below and Figure 3). The variable spine(tilt) ranges from -1.0 to 1.0, and moves the tilt angle of the spine from its default angle (spine(tiltDef)) by a maximum of spine(tiltVar) degrees in either direction. This provides an easy way to stay within a range of tilt angles which make sense.


## Spine
# Public
set spine(color)  {0.7 0.7 0.7}
set spine(length)  14.0
set spine(radius)  0.4
set spine(tilt)    0.0
set spine(disp)    0.6
set spine(height)  2.5
# Private
set spine(tiltDef) 35.0
set spine(tiltVar) 30.0
set spine(xR)      0.0
set spine(yR)      0.0
animatable: {set spine(zR) \
        [expr {$spine(tiltDef) + ($spine(tiltVar) * $spine(tilt))}]}
animatable: {set spine(xT) [expr {0.0 - $spine(disp)}]}
animatable: {set spine(yT) [expr {0.0 + $spine(height)}]}
set spine(zT)      0.0

startShape Spine
  animatable: {Translate $spine(xT)     $spine(yT)     $spine(zT)}
  animatable: {Rotate    $spine(xR) 1 0 0}
  animatable: {Rotate    $spine(yR) 0 1 0}
  animatable: {Rotate    $spine(zR) 0 0 1}
  # Z is up
  Rotate -90 1 0 0
  ##
  # Expansion Slot
  ##
  # The actual spine is a cylinder
  Color $spine(color)
  Surface DPMetallic
  animatable: {Cylinder $spine(radius) 0 $spine(length) 360}
endShape

Note that we left an "expansion slot" in the code above. We will be defining other parts that will be positions with respect to the spine, so we will instantiate their shapes within Spine.

The Tail

Tony's tail is defined relative to his spine. We therefore define a shape Tail in shape Spine. Tony's tail is another cylinder, which initially sits on top of the spine (see Figure 4a). The tail is then rotated and displaced from the top of the spine (Figure 4b).

The tail is allowed to rotate along its Z axis (spine(wag)) and along its Y axis (spine(wig)). Like Spine's spine(tilt), these values range from -1.0 to 1.0 and allow for a set amount of rotation in either direction from the defaults. The displacement along X(S) and Y(S) from the top of the spine is defined by spine(disp).


## Tail
# Public
set tail(color)    {0.7 0.2 0.3}
set tail(radius)   $spine(radius)
set tail(length)   1.3
set tail(disp)     0.3
set tail(wig)      0.0	# -1.0 to 1.0
set tail(wag)      0.0	# -1.0 to 1.0
# Private
animatable: {set tail(xT) [expr {0.0 - $tail(disp)}]}
set tail(yT)       0.0
animatable: {set tail(zT) [expr $tail(disp)]}
set tail(wigDef)   0.0
set tail(wigVar)   80.0
set tail(wagDef)   -30.0
set tail(wagVar)   20.0
set tail(zR)       0.0
animatable: {set tail(yR) [expr {$tail(wagDef) + ($tail(wagVar) * \
	                         $tail(wag))}]}
animatable: {set tail(xR) [expr {$tail(wigDef) + ($tail(wigVar) * \
                                 $tail(wig))}]}

startShape Tail
  animatable: {Translate 0 0 $spine(length)}
  animatable: {Translate $tail(xT) $tail(yT) $tail(zT)}
  animatable: {Rotate    $tail(xR) 1 0 0}
  animatable: {Rotate    $tail(yR) 0 1 0}
  animatable: {Rotate    $tail(zR) 0 0 1}
  # The tail is a cylinder
  Color $tail(color)
  animatable: {Cylinder $tail(radius) 0 $tail(length) 360}
endShape

The Sack

The sack was originally intended to be a pair of paraboloids, facing each other. However, I thought it might be more interesting to use a less regular shape, and I wanted to include an example of inserting a modeled object, so I instead used SolidThinking to model the sack as a RIB object, which I then dropped into a RIB Well to generate an Eve file. The sack merely sits right under the spine, and so is also inserted as a shape within Spine. The sack's malleable feature is that is fullness (sack(full))may be set, as a value from 0.0 to 1.0, and this will effect the size of the sack. The sack's fullness value also represents how hungry Tony is.


## Sack
# Public
set sack(color)    {0.8 0.4 0.3}
set sack(full)     0.7
# Private
set sack(max)      5.0
set sack(width)    5.0
animatable: {set sack(sag) [expr {2.0 + ($sack(max) * \
                                         $sack(full))}]}
animatable: {set sack(disp) [expr {-0.1 - $spine(radius)}]}

startShape Sack
  animatable: {Translate $sack(disp) 0 0}
  animatable: {Scale $sack(sag) $sack(width) $spine(length)}
  Color $sack(color)
  source $modelPath/sack.eve
endShape

The Wheels

The wheels are simply tori. Since the wheels are Tony's only source of locomotion, they are placed so that the midpoint between the lowest point on each wheel is at Tony's origin, O(T). The wheels' only adjustable geometry is the radius of the wheels. Each of the two individual wheels is a separate nested shape within Wheels, allowing us to address the wheels as a pair, or each wheel individually.



## Wheels
# Public
set wheel(color) {0.6 1.0 0.6}
set wheel(radius) 2.0
# Private
set wheel(disp)   2.3
animatable: {set wheel(minorRad) [expr {$wheel(radius) * 0.35}]}
animatable: {set wheel(majorRad) [expr {$wheel(radius) - \
                                        $wheel(minorRad)}]}

startShape Wheels
  Color $wheel(color)
  animatable: {Translate 0 $wheel(radius) 0}
  ####
  startShape LeftWheel
    animatable: {Translate 0 0 $wheel(disp)}
    animatable: {Torus $wheel(majorRad) $wheel(minorRad) 0 360 360}
  endShape
  # LeftWheel
  ####
  startShape RightWheel
    animatable: {Translate 0 0 [expr {0 - $wheel(disp)}]}
    animatable: {Torus $wheel(majorRad) $wheel(minorRad) 0 360 360}
  endShape
  # RightWheel
endShape

Because we'd really like the height of the spine to be centered slightly above the height of the wheels, we'll modify the spine(height) so that instead of being a publicly modifiable variable, it is a private variable which depends on wheel(radius).


animatable: {set spine(height) [expr {$wheel(radius) * 1.5}]}

The Head

Tony's head is a paraboloid with its vertex on the Y axis, facing down the (positive) X axis, and resting on the floor (XZ plane). It's adjustable features are its height (head(height)), its length (head(length)), and its scale factor in Z (head(squish)) and Y (head(squash)). The length, squish, and squash variables allow for an interested sucking gesture when the vacuum is in action. The actual head is nested into Head as shape Sucker so as to allow it to be manipulated independently of the eyes, which are also nested into Head. The eyes are borrowed from Wave's singleEyeWithShinyThing model, and serve as as example of re-using eve code from other characters. One can modify the look direction and pupil size of the eyes.

Tony


## Head
# Public
set head(color)  {0.8 0.3 0.3}
set head(radius) 2.0
set head(length) 4.0
set head(squish) 0.0
set head(squash) 0.0
# Private
set head(xR)     0.0
set head(yR)     0.0
set head(zR)     0.0
animatable: {set head(ssquash) [expr {1.125 + \
                                     [expr {0.40 * $head(squash)}]}]}
animatable: {set head(ssquish) [expr {0.85  + \
                                     [expr {0.15 * $head(squish)}]}]}
animatable: {set head(height)  [expr {$head(radius) * \
                                           $head(ssquish)}]}
animatable: {set head(disp)    [expr {0.6 * $head(height)}]}

## Eyes
# Public
set eyes(color) {0 0 0}
set eyes(radius) 0.6
set eyes(pupil) 0.4
set eyes(yR) 0
set eyes(zR) 0
set eyes(disp_side) 1.0
# Private
set eyes(xR) 0
animatable: {set eyes(height) [expr {$head(radius) + 0.1}]}
animatable: {set eyes(disp_forw) [expr {$head(length) / 2.0}]}

startShape Head
  animatable: {Rotate    $head(xR) 1 0 0}
  animatable: {Rotate    $head(yR) 0 1 0}
  animatable: {Rotate    $head(zR) 0 0 1}
  Rotate 90 0 1 0
  animatable: {Translate 0 $head(height) $head(disp)}
  ####
  startShape Eyes
    ####
    startShape LeftEye
      animatable: {Translate [expr {0.0 - $eyes(disp_side)}] \
                        $eyes(height) $eyes(disp_forw)}
      source $modelPath/eye.eve
    endShape
    # LeftEye
    ####
    startShape RightEye
      animatable: {Translate $eyes(disp_side) $eyes(height) \
                             $eyes(disp_forw)}
      source $modelPath/eye.eve
    endShape
    # RightEye
  endShape
  # Eyes
  ####
  startShape Sucker
    animatable: {Scale $head(ssquash) $head(ssquish) 1.0}
    Color $head(color)
    animatable: {Paraboloid $head(radius) 0 \
                       [expr {$head(length) * 1.0}] 360}
  endShape
  # Sucker
endShape

Again, we want to change something we've already done. If the height of the head changes, we want the wheels to grow to accommodate the larger or smaller head. We make a simple edit to the code for wheel(radius):


animatable: {set wheel(radius) $head(height)}

Emotional State

We'll create an emotion variable which we can use to effect our animation agents. Tony gets hungry when his sack isn't full, and when he's hungry, he's less happy.


## Emotional State
# Public
set state(happyfactor) 0.5
# Private
animatable: {set state(happy)  [expr {$state(happyfactor) * \
                                      $state(hungry)}]}
animatable: {set state(hungry) $sack(full)}

Shape Summary

We now have a working model of Tony's shape, consisting of:

See the listings defines.eve and procs.eve to see the complete Eve code for our character model.

Test Interface

WavesWorld allows you to build up a user interface for your model which behaves much like an agent. You can create buttons, sliders, text fields, and thumbwheels which call Eve functions in your model's environment. For example, you can create a slider which sets head(squish) to a value ranging from -1.0 to 1.0. This allows you to easily and dynamically modify the variables you have created in your model to see if the resulting behavior is as expected. You can also have a button which activates a skill agent, and you can watch as the skill agent modifies your model over time.

Animation

Having a complete model of our vacuum cleaner dude, we can now write a skill agent that acts on the model over time. We'll write a function suckMotorProgram() which tells Tony to start up the vacuum engine and suck in an object.

Before we start on our function, we need to decide on what will happen as Tony sucks up an object. The test user interface comes in quite handy here, so we can play with out model as we decide what motions make Tony look just right.

Over the course of executing, our motor program has a sense of time via one variable, u, which ranges from 0.0 at the beginning and 1.0 and the end. This makes our program scalable - the duration of the scene to be set to any length, without having to edit the motor program. Our script should include periods of time as ranges of u in which activity occurs.

Bring attention to object, react (u = 0.0 -> 0.2)
Eyes to object
Shock - head grows and shrinks quickly
Wag tail, amount depends on how happy Tony is

Start sucking (u = 0.2 -> 0.4)
Straighten tail
Spine tilts back
Head stretches forward

Suck (u = 0.4 -> 0.8)

Object reaches mouth (u = 0.8)

Swallow (u = 0.8 -> 0.9)
Head scrunches back
Spine returns to normal position
Head Grows a bit

Object is eaten (u = 0.9 -> 1.0)
Eyes forward / attention released
Head shrinks a bit
Head unscrunches, re-expands a bit
Head to normal

Yum, yum
Sack grows
Tony becomes a bit happier.

See the listing animation.eve to see the Eve code for procMotorProgram().

Summary

It is evident that had we not considered the capability to move each individual part of our dude, we would not have been able to articulate a sucking action very well. We could also have made life far more difficult on ourselves by not imposing a sensible hierarchy on the relative origin and position of each part. With some forethought, we were able to make a fairly simple and agile character, which we could teach to do something interesting.

The terrific feature of the agent we implemented is that it can be used at any time, even concurrently with another. If we were to write a rotate body skill agent, we could have our dude sucking while rotating to follow the object he is preying on. Or we could have Tony sneeze after sucking up a piece of dust by attaching a sneeze skill to the end of suckMotorProgram. The potential is limitless, and the extensibility is built right in.


Some extras:


Wilfredo Sánchez Vega | M.I.T. Department of Electrical Engineering and Computer Science | [email protected]
Copyright © 1995 Wilfredo Sánchez