Tuesday, 6 April 2010

XML Scripting

I'd also like to write about the XML scripting that I'm working on, this will be used to script up cut-scenes and maybe even semi-dynamic behaviours. I got my first script working earlier:

<actor>
  <activities>
    <activity id="montage">
        <sequence loop="true">
          <stage overlap="500"><clip name="female/Female1_D6_CartWheel.bvh"        /></stage>
          <stage overlap="500"><clip name="female/Female1_B01_StandToWalk.bvh"     /></stage>
          <stage overlap="500"><clip name="female/Female1_C05_WalkToRun.bvh"       /></stage>
          <stage overlap="500"><clip name="female/Female1_C11_RunTurnLeft90.bvh"   /></stage>
          <stage overlap="500"><clip name="female/Female1_C20_RunToJumpToWalk.bvh" /></stage>
        </sequence>
    </activity>
  </activities>
</actor>

The script simply sequences up a few animations together with a blending overlap of 500ms for all of them, it's fairly self explanatory really. The sequence node is set to loop and since the first stage is given an overlap it will transition back to the start smoothly, making an infinitely long continuous animation.

It certainly makes playing about with animations simpler as I now don't need to wait for the tedious recompile every time I want to fiddle around with numbers.
The script doesn't yet handle every kind of node, in fact it only handles sequence and clip nodes at the moment but the system is in place now and with that exposing new interfaces to the script is relatively easy.

Quaternion Fun

I recently implemented velocity based blending, this in terms of vector-based velocity and quaternion angular velocity. This essential feature wasn't implemented before and as such animations could not transition correctly; if you imagine a person strolls a corner and intends to blend into a straight-walking animation you would not expect them to change heading any further, without velocity based blending the direction encoded in the original animation decides the direction the character moves with respect to the global axis.

To blend quaternions correctly I had to implement weighted quaternions, these are scaled, non-normal quaternions which are contributed into the blend buffer and eventually combine into a regular, near-normalised, quaternion. Getting this working was quite a mean feat, various boundary cases play havoc with the interpolation causing the actor to 'jump' sporadically when extreme blends are performed. The main issue was that a dot product test would change sign, signalling a sign-inversion of the to-be-weighted quaternion, this is important to ensure a shortest-arc blend around the quaternion hypersphere. Unfortunately the sign would change a fraction too early, as a consequence of the discretised sampling and tweening. To solve this I detect when the dot product is close to zero, at which point I begin calculating both the inverted and non-inverted quaternions, contributing them both to copies of the destination buffer. I then blend between the destination buffer alternatives, depending on the actual value of the dot product in the epsilon range around zero. This has the effect of smoothing out the inversion transitions in a range around the boundary case and produces visually pleasing results.

Calculating the positional and rotational velocities is simply a matter of working out the delta from frame to frame, the rotational delta is propagated through the blend-tree but not through the pose structure, rather it is propagated through the blend_info structure. This structure holds various useful infos that travel from node to node, including the destination buffer, blend weight, skeletal structure/bone names and the rotational delta. Rotation is handled separately simply because of the nature of this delta. It's actually the rotational delta around the y-axis, the x and z axis are not stored as deltas and remain in the pose structure to be blended as usual, only the y-axis rotation is converted to a delta since this decides the heading of the character over an essentially 2D terrain system. Naturally, to achieve this, I have to extract the y-rotation from the interpolated root rotation, this proved interesting when combined with my smoothing algorithm and required a couple of attempts to get it right.

All in all the quaternion blending is quite robust, numerically stable and reasonably fast, although there's plenty of room for improvement.