vidar.games

Motion matching

2026-11-07 ยท Discuss on X

Same stick input, same 5 minutes of mocap.
Left: idle, walk and run clips with crossfades. Right: motion matching searches the whole take for the best next frame.

The problem

A state machine plays one clip per state and crossfades between them. The character's root follows the gameplay controller, but the clip has its own speed and its own turn rate. When they disagree, the feet skate. Starts and stops are the worst: the controller speeds up or slows down while the clip keeps its steady stride.

How it works

Motion matching skips the graph. Every frame of the mocap becomes a row of features:

  1. Where the character will be 20, 40 and 60 frames ahead, and which way it will face, in its own space.
  2. Ankle positions and velocities, and hips velocity.

Every 10 frames, or right away when the stick swings hard, the controller's predicted path plus the current pose form a query. A linear search over all rows returns the closest frame. If it is more than 20 frames from the current one, playback jumps there. Inertialization then decays the pose difference over a 0.1 s half-life, so the jump does not pop.

What I hit

My first measure of foot sliding was "ankle low and moving". It flagged swing feet too. Checking it against the raw mocap showed planted ankles sit at 11 to 12 cm, so the 13 cm threshold holds. By the same measure the raw take slides under 7 cm per foot per walking second.

The source also has foot lock: when the data says a toe is planted, two-joint IK pins it until it drifts 20 cm. It only locks when the toe is already nearly still (under 0.15 m/s). With a jump every third of a second, inertialization keeps the toe moving, so it locked only 14 times in my 14.5 s run.

My build

three.js 0.170 renders the playback. The simulation is my JavaScript port of JLPM22's Unity package. It uses the sample take that ships with it: 18,120 frames at 60 fps, 23 joints, 27 features per frame. Both characters get the same scripted stick: start, run, stop, walk back, curve, stop. The state machine uses three clips cut from the same take, with 0.25 s crossfades and no foot IK.

Over the 14.5 s run: 546 searches, 42 jumps.
Foot sliding: state machine 7.53 m, motion matching 3.72 m (4.45 m with foot lock off).
The stop after the run: 1.01 m vs 0.23 m.

Trade-offs

When to use it

Third-person characters with lots of locomotion and a decent mocap take. For a small cast with a few hand-keyed moves, a state machine is still simpler.

New breakdowns go out on X first: @vidar_0208. If this one helped, you can buy me a coffee.

Source and credits

Technique source: MotionMatching by JLPM22 (Jose Luis Ponton), https://github.com/JLPM22/MotionMatching, MIT license, commit 35fc8ee9a0fd97f12de458ea81eaef7c8219d87a. Ported to JavaScript: the simulation bone and feature extraction (future positions and directions at 20/40/60 frames, ankle positions and velocities, hips velocity, per-group normalization), the linear search with the "ignore jumps within 20 frames" rule, inertialization with decay springs, the spring character controller with its adjustment and 10 cm clamp, and foot lock (contact detection, contact inertialization, two-joint IK). No files were copied unchanged.

Motion data: the sample take jl_motionMatching_hd that ships with the same repository (MIT), used as the animation database. The state machine clips were cut from the same take.

Render framework: three.js by mrdoob and contributors, https://github.com/mrdoob/three.js, MIT license, version 0.170.0 (npm). Used only to draw the recorded playback. It is not the source of the motion matching technique.

My own code: the BVH loader, the scripted stick input, the state machine baseline (three clips, 0.25 s crossfades, no foot IK), the foot-slide measurement, the mannequin renderer, the stage and the labels. No external models. No IA model.