Footskate Cleanup Library Documentation

Documentation By: Josh Lulewicz             jlulewic@cs.wisc.edu

University of Wisconsin Madison

Introduction

Footskate is a phenomenon that can occur when raw motion data is fit to a rigid skeleton. Footskate is observed at frames where the feet or hands are planted. Instead of remaining planted until the next conscious move, the joints continue to shift causing displeasing artifacts in the animation. In 2002 at the Symposium of Computer Animation, Kovar et al presented a simple and effective algorithm to eliminate these artifacts. This library is a direct implementation of the algorithm presented in this paper. For specific details on the approach please refer to http://www.cs.wisc.edu/graphics/Papers/Gleicher/Mocap/cleanup.pdf.

Overview

The files that are included in this library are: CQuaternion.h (.cpp), Point3.h (.cpp) and IKSolver.h (.cpp). You may wish to use your own quaternion and point class in which you will need to substitute all instances of CQuaternion and Point3 with your own. The current project was created with Microsoft Visual C++ .NET ver 7.1.3088.

Getting Started

First you will need to store your skeleton so that it has these basic joints:

BVH Skeleton Structure

Data Structures

The three main data structures of this solver you need to familiarize yourself with are ikConstraint, ikCompletePose and ikPose.

Below is a discussion each.

ikConstraint comprises three elements: a state, the positions of the constrained joints, and the positions and orientations for the hip, shoulders, ankles, wrists, elbows and knees for the constrained frame.

ikCompletePose contains an origin and orientation of the hip, local and global rotations for the knees, elbows, ankles, wrists, hip and shoulders and the offsets for each of the joints.

ikPose is the data structure that will hold the results of the solve. It is made up of the origin of the hip, the local rotations of the hip, shoulders, knees, elbows, ankles and wrists and a factor by which to scale each limb.

Setting up the Data Structures

To begin declare an array of ikConstraint for the number of frames you are solving. (ikConstraint constraints[numberOfFrames])

For each known foot plant, calculate the frame intervals. For every frame in the interval mark the joints that are locked in the state field: Right and Left Toes, Right and Left Heels, Right and Left Wrists and Right and Left Fingers. At the same time record the position of each joint in the dest field of ikConstraint for the frame.

for each footplant;
  start = currentFootPlant.start()
  end = currentFootPlant.end()
  for each frame from start to end
    if (joint = leftToe)
      constraints[frame].state |= IK_LEFT_TOE_LOCKED
      constraints[frame].dest[limb][0->toe/finger, 1->heel/wrist].dest = position(joint.x, joint.y, joint.z)
    endif
    if (joint = rightToe)
      .
      .
      .
    endif
  endfor
endfor

Next you will need to fill the pose field in each ikConstraint. The ikCompletePose consists of the following information: hipRot (hip rotation), hipOrigin (location of the origin of the root), rots (local rotations of the hip, shoulders, knees, elbows, ankles and wrists), globalRots (global rotations of the joints), offsets (offsets in local coordinates for the joints).

To do this simply extract the root position, root orientation, local rotations and local offsets from your skeleton for the current frame and insert them into the pose.hipOrigin, pose.hipRot, pose.rots, pose.offsets fields respectively. The pose.globalRots field can than be filled in by computing them based on the information stored in pose.hipRot and pose.rots.

for every frame
  fillPose(frame, constraints[frame].pose)
endfor

proc fillPose
  joint = getJoint(leftHip)
  pose.offsets[0][0] = position(joint.x, joint.y, joint.z)

  joint = getJoint(rightHeel)
  pose.offsets[1][4] = position(joint.x, joint.y, joint.z)
  .
  .
  .
  rotation = getRotation(leftHip, frame)
  pose.rots[0][0] = rotation
  .
  .
  .
  Compute global rotations
  pose.globalRots[0][0] = globalRotation
  .
  .
  .
endproc

Keep in mind that to get the left and right shoulder offsets you will need to invert the hiprot and rotate it by the quaternion resulting from the position of the respective shoulder minus the position of the root. A similar process will be required to get the shoulder rotations -- Invert the global rotation of the root and multiply it by the global rotation of the shoulder.

Once the pose is collected for every frame you are ready to do the solve.

Solving

Create a variable of type ikPose to store the result and then call the function ikBlendSolve with the appropriate parameters.

ikBlendSolve(int jointWindow,   // Blending window sizes
            int ankleWindow,        
            int rootFilter,         
            real kneeThresh,        // tradeoff between knee pops and stretching the leg
            ikConstraint *ikConst,  // ikConstraint data structure we just created
            int numConst,           // number of frames we are solving
            ikPose* result,         // result of the solve
            ikToeInfo* toeInfo,     // toe information if present (see ikToeInfo structure in IKSolver.h)
            int solveAll,           // solve every frame of the motion
            int fixPen,             // fix the displacement so we're not going through the floor at constrained frames
            int ankleOption         // ankle roll option (0 for Vertical, 1 for Original)
           )

Updating

After the solve is complete you need to extract the values from you results (ikpose) and copy them into your skeleton for every frame. Extract the rots for the Legs and Arms, the position of the Root (hipOrigin) and the value by which to scale the leg and arm bones (lengthAdjusts).

Closing

Using this library will eliminate disturbing footskate artifacts that can occur in your animated characters when using motion capture data. It can be used off-line for your characters in animations and it is fast enough to be done on-line with characters in games and other real-time applications.

Questions/Feedback

Forward questions, comments, etc to jlulewic@cs.wisc.edu