High Fidelity animation and the 90 / 90 rule


It’s been a while since I last posted an update on my procedural animation work, but not because I’ve been idle! With the latest release of the walk.js script, I think I’ve finally started chipping away at that final 10% of a project that takes (at least) 90% of the time. (If you’re not familiar with the 90/90 rule, this article explains it quite well).

Updated: The Walk Cycle

To get the walk to a standard synonymous with high fidelity, we needed to re-work every joint of the animation. Under @Ozan's direction, I read up on the Disney principles of animation, biomechanics and generally absorbed everything I could lay my hands on regarding animating walk cycles. We then chose a mocapped walk cycle as a reference and I started the somewhat long winded process of analysing every curve for every joint. It quickly became clear that simple sine waves were not going to be enough to produce a high fidelity walk cycle, so I came up with a couple of more interesting ways of reproducing realistic animation curves. For the hips yaw rotation, for example, I created a triangle wave generator using the geometric wave synthesis fundamentals from here that quite closely match the mocap data's curve.

Fourier Synthesis

But basic geometric wave synthesis wasn't quite doing the job for certain curves, particularly the pitch curves for the leg joints. So I decided to follow up on an earlier idea to employ Fourier synthesis. This branch of mathematics is based on the principle that waveforms can be represented or approximated by summing a given number of sine waves of different amplitudes, frequencies and phases. By using Corban Brook's dsp.js script in conjunction with the walkTools.js app I've implemented for procedural animation development, I created Fourier series data that does a pretty good job of matching the required curves:

The advantages of using frequency domain (i.e. Fourier series) data to represent motion are as follows:

  1. Since the wave is effectively represented by a continuous curve in frequency domain, there are no interpolation errors when speeding up or slowing down the animation. This is not the case for keyframe / mocap data. A reasonable (if slightly tenuous) analogy would be the difference between vector and raster graphics.

  2. For the same reasons as (1), there are no interpolation errors when amplifying or attenuating the amplitude of the animation, thus allowing the cadence of the animated character to be cleanly adjusted programmatically.

  3. The 64 values of keyframe / mocap data comprising each curve became 19 (or sometimes fewer) values holding the Fourier series data, thus implementing effective, almost entirely lossless compression.

Once I’d got the more tricky maths stuff up and running, Ozan and I then worked on the final polishing the walk - getting the hips ‘figure of eight’ movement correct, making sure the centre of gravity was always placed nicely over the supporting leg and adding some character and cadence. I hope all the extra effort shows in the final animation!

Slowmo of the walk during development:

Updated: The Other Animations

There are still no transitions between different animations for sidestepping, and only the walk animation has been under close scrutiny. However, using what I've learnt about animation recently, I've hopefully gone some way to improving the flying, standing and sidestepping animations. The much needed 'turn on the spot' animation currently exists only in my head, and looks set to present it's own set of challenges...

Updated: Hips offset in Interface

When I started implementing procedural animation for HiFi, the avatar's hips translations whilst walking were sketchy to say the least - the positioning was not always accurate and the camera bobbed around with the avatar's movement. So I started making a case for the necessary functionality. @leviathan very kindly took up the baton, and has implemented the new setSkeletonOffset JavaScript command, thus accurately enabling the small hips translations so essential for animation - without affecting the camera position. This currently only works in either 3rd person or independent camera modes (i.e. NOT in fullscreen mirror mode), but works very well nevertheless.

Updated: Speed and Acceleration

The ground / foot syncing in earlier versions of the walk script was not 100% accurate. The reason for this was that I was doubling the walk stride value in an attempt to account for the unnatural walk speed specified by Interface. In addition, there was a slow drift to stop at the end of the avatar's movement that was not helping with the realism. Working with @leviathan, who patiently listened to my ideas as they spiraled around and finally down to workable solutions, we adjusted both the avatar's top walk speed and the shape of the avatar's acceleration curve in Interface. Using a realistic value for human walking speed (2.5 m/s) in conjunction with the modified acceleration curve has given the added realism that I was looking for.

Updated: Hydra and Leap Motion Support

Using a code snippet from the frisbee.js script (thanks @Thoys!), the walk script now checks to see if Hydras are connected on startup. If they are detected, the 'arms free' option is automatically selected and Hydras can be used. However, once 'arms free' has been turned off, the script will need to be restarted to get Hydra support back again. The leapHands.js script works just a bit better - simply select the 'arms free' option when you want to use the Leap. Unlike in the case for Hydra support, 'arms free' can be toggled on and off whilst using the walk script.

Animation File Sizes

In a drive to reduce the animation file sizes, I reduced the number of decimal places for the animation data, producing a (roughly) 30% reduction in the total file size without any perceivable loss of quality. The entirety of animation data, including all the Fourier series values for the walk, now weighs in at around 42k.

Code Refactoring

I've recently broadened my knowledge of Javascript, paying particular attention to the finer points of the metaprogramming techniques the language offers. I have now completely restructured the code, splitting off three new files containing the various JavaScript objects and constructors. The previous version of the script had some code to work around some timing bugs in Interface that have now been fixed, so that code has now been removed from the script.


If you've read this far, it's probably obvious the walk script is still very much work in progress - the transitions between animations need more work and the 'turn on the spot' animation needs implementing. The icing on the cake would be the introduction of 'reach poses', whereby during a transition the avatar reaches for a pose, peaking midway between the two animation states. For example, when transitioning from flying down to standing, the reach pose would probably be a crouch. I should probably look at refactoring the main core of the script too, as one function that is over 2000 lines of code long can't be right! Ultimately, I'm aiming to get the script to a level of quality whereby it becomes the default, base animation set for new avatars with no animation overrides or input devices. Then, once we start being able to import fbx mocap / keyframe data, and start being able to layer and prioritise animation from different sources (e.g. upper body motion capture, data gloves, etc) it could become the fallback set of animations for when no other character motion sources are active. Finally, as a separate project, the automatic encoding of imported fbx motion data to Fourier series data may make sense in terms of reduced asset size / bandwidth usage and ease of blending / speed matching with other animations whilst presenting small (but not insignificant) technical hurdles for any would-be copyright infringers...


  • davedub