Introduction to Twig's internals

This section gives a brief description of the basic kinds of objects in Twig, their functions, and the relationships between them.

Contents

TwigGame

Games that use Twig must inherit from the TwigGame class, rather than the Game class.  The TwigGame class calls the appropriate physics methods for constraint propagation and collision handling, and provides centralized support for HUDs and key binding.

HUDs

On-screen heads-up displays inherit from the HUD class.  There are a number of built-in HUD classes, that provide support for documenting key bindings and displaying various kinds of debug data.  Note that dialog balloons are not considered to be a HUD.

TwigObjects

All in-game objects inherit from the TwigObject class.  Twig implicitly has three different models of an object's geometry:

All TwigObjects provide at a minimum, a set of nodes, links, and collision elements, together with a set of methods for. Update(), Draw(), and Constrain(). The latter propagates geometric constraints between nodes.

Physics: nodes and constraints

The physics system's view of a TwigObject is determined almost entirely by Nodes, Links, and CollisionElements properties, which specify, not surprisingly, its nodes, links, and collision volumes.  I say "almost entirely", because the TwigObject can explicitly manipulate its nodes through its Update() and Constrain() methods.

All kinematic information for an object is stored in its nodes.  In other words, the TwigObject makes a contract that if you save the positions of its nodes, move the object, and restore the positions of its nodes, the object will be in exactly the same kinematic configuration after the restoration as it had been before you started moving it.  Similarly, all dynamic information is stored in the velocities of its nodes, so the motion of an object is determined entirely by the positions and velocities of its nodes, save for the case of "active" objects like characters, that apply deliberate forces to their own nodes.

The physics system basically supports three things: updating node positions based on their velocities, updating their velocities based on forces, and satisfying geometric constraints on or between nodes.

The most common form of constraint is a link; it forces the two nodes to be a specified distance apart, so if one node falls, it drags the other along with it and/or the other node pulls it back in place, depending on what, if any constraints are in place for the other node.  Character bodies are built as a set of nodes with links between them.  For example, an arm is three nodes (shoulder, elbow, and wrist), connected by two links, upper arm and forearm.  The nodes can move any way they want, so long as their distances are preserved.  This has the disadvantage that the elbow then acts as a universal joint, allowing unconstrained rotation in any direction, so additional constraints are provided to make sure the arm doesn't rotate in unseemly ways.

The other main kind of physical interaction is collision handling.  Whenever the CollisionElements of two TwigObjects interpenetrate, the nodes associated with those elements are moved so as to disengage the elements.  CollisionElements can be boxes, spheres, or capsules (cylinders with round ends).

Object/object interaction: supporting surfaces

Passive objects can only interact when they happen to collide.  Most collisions are handled as discussed above.  However, certain objects, such as tables, act as supporting surfaces for other objects.  Handling such situations through repeated collision resolution is expensive and unstable, so objects that are intended to act as supporting surfaces can be specially tagged as supporting surfaces.  The physics system automatically marks what supporting surface a node is above, if any, and prevents its Y coordinate from becoming smaller than the height of the surface.  Designating an object as a supporting surface requires the author to implement additional methods.

Update for active objects

For passive objects, such as the merry-go-round, object update is essentially performed within the physics system.  However, objects that are active, such as characters, will likely have specialized state variables and actions.  These are updated within the Update() method, which generally acts by changing the accelerations, velocities, or positions of the object's nodes.

Character/object interaction

Currently, characters can interact with objects in five different ways:

Drawing

TwigObjects are responsible for drawing themselves somehow when their Draw() methods are called.  One way or another, this will eventually involve meshes being drawn to the screen, however, Twig doesn't have much to saw about how that's done other than that:

The TwigGame also provides a shared BasicEffect object that can, but need not, be used for drawing objects.

Characters

Characters are implemented as subclasses of the Mammal class, which is in turn a subclass of the Ragdoll class, which is a subclass of TwigObject.  The system should probably be refactored to separate the body geometry, from cognition and control, but this won't happen until it's more clear what the API should look like.

Bodies

Character bodies consist of two LimbPairs, connected by a spine, with a head on top.  A LimbPair is two Limbs, each of which has three nodes, called root (shoulder/pelvis), joint (elbow/knee), and end (hand/foot).  The head is just an extension of the spine.  The body's collision geometry is just a set of capsules, which is suboptimal, but looks okay.

Low-level control

Low-level control of posture and limb motion is performed by relatively unstructured code that

Limb control

Limbs can be told to move their end nodes to specified places (for reaching and stepping) and to attach themselves to other nodes (for grasping).  The Constrain() method imposes joint limits on limbs.

Posture control

Posture control is implemented by a set of methods in Posture.cs, which apply forces to the pelvis (the connecting structure of the lower LimbPair) and the shoulders (the connecting structure of the upper LimbPair) to hold them at the right levels so that the character is standing upright (it situp and standup are set) and the spine is the right length.  It also adjusts the orientation of the pelvis and shoulders to follow the direction of locomotion, or failing that, of gaze.

Gaze control

Twig currently doesn't support faces, so gaze is performed by moving the entire head.  This is currently performed by teleporting the cylinder forming the head into the appropriate position, creating an exaggerated sense of motion, similar to the head saccades of birds. While this isn't realistic, it helps cue the user to the character's shifts of attention and generally contributes to the sense of the character being alive and motivated.

High-level control

Attention and short-term memory

Characters continually track the salience of the objects in the environment.  Salience is determined by an "appraisal" mechanism that rates objects along different dimensions (currently valence and monitoring priority).  Whatever object is determined to be most salient is made the focus of attention until some other object becomes more salient.  Recent foci of attention are also stored in an LRU cache called the attention buffer.  A character's attention buffer is stored in its Attention property, and the focus of attention is in the Attention.Focus property.  The attention buffer currently has 7 slots in it.

Locomotion and gait generation

The locomotion system takes a velocity vector as input.  It turns the pelvis toward that vector and translates it along the vector.  This stretches the legs, whose feet remain in their original location.  When a leg comes to full extension, the system generates a ballistic motion of the foot to a location in front of the body.  The translation direction is stored in the WalkVector property.

Behavior infrastructure

Most of the code above the level of posture control and the gait generator is packaged as collections of "behaviors" that compete with one another.  Behaviors are objects of the generic type Behavior<T>, which have two properties:

There is also a special kind of behavior, MaxBehavior<T> that, given a set of other behaviors, finds the one with the highest activation level and copies its output to its own output, i.e. its output at any moment is the output of whichever component behavior his highest activation.

Locomotion behaviors

The low-level WalkVector is computed by the locomotionController, which is a MaxBehavior<Vector3>.  It's current driven by three "locomotion behaviors" (objects of type Behavior<Vector3>) that bid for control of the WalkVector.  The default behavior is the FreezeBehavior, which outputs the zero vector.  The second is the PainWithdrawalReflex behavior, which generates an open-loop evade response when an object produces pain in the character.

The final, and principle, locomotion behavior is the approach controller.  The approach controller takes as input (from higher level behaviors) an object to approach, a direction to approach it from, and a closing distance (distance at which to stop).  The approach controller uses a simple artificial potential field navigation scheme, where the goal produces an attractive force and obstacles produce repulsive force together with a curl component to guide the character around obstacles.  Objects can be ignored by the potential field algorithm by setting their IsObstacle property to false.

Grasping and sitting

Grasping and sitting are both implemented by creating an invisiting, zero-length link between the appropriate node of the body, and the appropriate node of the target object.  In the case of sitting, the base of the spine is attached to whatever node of the object is designated by its SittingTarget property.  In the case of grasping, the end node of the appropriate arm is attached to a designated node of the object.  The specific arm and object node are determined by the object's SetupHold and/or SetupHoldForUse methods.

As of this writing, grasping and sitting, while supported, can only be used in practice through the scripting and RPC interfaces.

High-level behaviors

Currently, the highest level of behavior-based control is performed by a set of behaviors that are attracted to specific kinds of objects:

The scripting system

Twig includes a simple scripting language that allows method calls to be read from a text file.  Scripting language commands can also be run over an RPC interface, allowing the user to control the system from systems not running under the .NET platform.

The interpreter

The scripting language, internally referred to as "Script Script", is implemented by the ScriptScriptInterpreter game component.  It can either read scripts imported through the content pipeline or can be driven by the RPC system.  The interpreter basically just takes commands of the form "name: method args ...", looks up the object with the specified name, parses the args, and uses the reflection API to invoke the specified method with the specified arguments.

Blocking and parallelism

The interpreter runs in the same thread as the rest of the game.  As with other game components, it does its work by when its Update() method is called on each tick, and it is assumed the Update() routine will return quickly.  Thus method calls performed in the scripting language also need to run quickly.  They can't block waiting for the character to complete an action because the method needs to return because the character can even update itself.

Actions

Because script commands can't block, some mechanism is needed to support commands, such as goto, which aren't conceptually completed until something happens in the game world some indeterminate period of time later.  Such durative commands are implemented by Action objects.  When a the script calls a method, such as goto, on an object, the interpreter checks whether method returned an Action object.  If so, it saves the Action and polls it on each clock tick to see if it has completed.  If not, it calls the action's Update() method.

An Action is polled through its Status property, whose value is either Running, Succeed, or Fail.

Sequencing commands from script files

When executing commands from a script file, the interpreter continually reads and executes commands until it encounters a durative action (i.e. something returns an Action object).  The interpreter's Update() routine then exits for that clock tick.  On successive ticks, it will poll the Action until it completes.  Once the Action completes, it continues dispatching commands as usual.  However, commands can be forced to run in parallel by adding an '&' to the end of the command.

RPC server

TwigServer is a subclass of ScriptScriptInterpreter that reads commands from a TCP connection rather than a script file.  Its behavior is largely similar to the normal interpreter except that it never blocks on durative actions.  That is, if you send it three commands at once it will always run them at once, regardless of whether one of them returns and Action object.  In order to allow the client to monitor when actions complete, the client can tag commands with a numeric request id.  When the command completes, the server generates an asynchronous notification giving its request id, and whether the command succeeded or failed.  For details see the RPC server documentation.