"Keep quaternion keys hemisphere-continuous"
▸ SYMPTOM
- Scripted animation keyframes look correct at each key pose, but the character explodes between keys: limbs swing a full 360 degrees during interpolation.
- The artifact only appears in transitions, never at the keyed frames themselves.
- The problem is intermittent: some rotations interpolate fine, others violently detour.
▸ CAUSE
Quaternions q and -q represent the same orientation but interpolation between them takes opposite paths around the 4D sphere. When you convert euler angles to quaternions per-keyframe independently, some keys may land on q and their neighbors on -q. The interpolator (slerp) doesn't know they're equivalent and takes the long path, a near-360 degree swing instead of a tiny step.
This is invisible at the key poses (both q and -q produce the same visible rotation) and only manifests during the blend.
▸ FIX
Negate the quaternion if it's on the wrong hemisphere:
# Python (Blender scripted keyframes)
import mathutils
prev_q = None
for frame in keyframes:
q = compute_rotation(frame) # euler to quat
if prev_q is not None and prev_q.dot(q) < 0:
q = -q # flip to same hemisphere
set_keyframe(bone, frame, q)
prev_q = q// C# equivalent
Quaternion prev = Quaternion.Identity;
foreach (var key in keys)
{
var q = key.Rotation;
if (Quaternion.Dot(prev, q) < 0f)
q = -q;
ApplyKey(bone, key.Frame, q);
prev = q;
}The check is one dot product per key. Apply it at authoring time (when generating keyframes). The runtime interpolator doesn't need to change.
▸ WHY IT WORKS
A quaternion lives on the surface of a 4D unit sphere. Slerp always takes the shorter arc between two points on that sphere. When q and -q are on opposite hemispheres (dot product < 0), the "short arc" is actually the long way around in 3D rotation space. Negating one to match the other's hemisphere ensures slerp takes the minimal rotation path, which is what you see as a smooth transition instead of a violent spin.
This applies to any scripted keyframe pipeline: Blender Python, runtime procedural animation, or custom FBX exporters. The engine's own AnimGraph cross-fades handle this internally, but direct sequence playback with scripted keys does not.