Impractical Instruments

Lesson 001 · Basis & handedness · ~12 minutes

Handedness is chirality, not which way is up

Unity and glTF both call +Y up. They both call +Z forward. And a skeleton copied between them still comes out mirrored. This lesson shows why that is inevitable — and which single sign flip fixes it.

Here is the bug you have all met. A pose leaves the sensor pipeline correct, arrives in the viewer, and the character's right arm is raised on its left side. Nothing is rotated. Nothing is scaled. It is reflected. And no amount of adding 90° anywhere will fix it, because no rotation can turn a left hand into a right hand.

The usual next move is to start negating axes until it looks right. That works about a third of the time, which is worse than useless — it means the wrong fix survives. So let's find the actual invariant.

First, which bug is it?

Almost every wrong-looking skeleton we ship is one of four things, and they have nothing in common but the symptom. Naming the class first is what stops you from applying a reflection fix to a multiply-order bug:

ClassWhat went wrong
Reflection A handedness conversion is missing, or applied to positions but not rotations. The pose is mirrored.
Wrong basis A rotation is measured against a frame you did not mean — a rest pose, a bind pose, a sensor-to-segment offset. One bone goes wrong; its parent is fine.
Wrong multiply order Local and world confused, or a correction composed on the wrong side. Error compounds down the hierarchy: root fine, fingers wild.
Accumulating error Drift. Not a convention bug at all — normalization and reference-frame decay. It gets worse over time, which none of the other three do.
Lessons 002–004 take the last three. This one takes the first — and, more usefully, gives you the observation that tells the first apart from all of them.

That observation is chirality: whether the body's right hand is still genuinely its right hand. It is worth learning first because it is the only one of the four you can settle with a single glance and no history. A reflection is visible in one frame. The other three are not: wrong-basis and multiply-order bugs need you to compare a joint against its parent, and drift needs you to compare now against a minute ago. So if the pose is mirrored, it is the first class; and if it is not mirrored, you have ruled that class out and can stop permuting signs.

The knowledge

A basis is three direction vectors you agree to measure everything against. Coordinates are just recipes: (3, 1, 4) means three of the first direction, one of the second, four of the third. The numbers alone say nothing about where the point is — they only mean something once you know the basis. Practically every convention bug in your pipeline is a number that got read against the wrong basis.

Handedness is one bit of information about a basis, and it is not about which axis is up. Eric Lengyel states the test cleanly: the two arrangements

are called left-handed and right-handed because the positive z axis points in the direction of the left or right thumb when the fingers of the same hand curl around the z axis in the direction from the positive x axis to the positive y axis. Foundations of Game Engine Development, Vol. 1: Mathematics, §1.2

So: sweep from X toward Y. Whichever thumb follows that sweep and ends up pointing along Z names the system. Drag the two figures below and find the one difference between them.

Unity — left-handed+Z away from you
glTF / three.js — right-handed+Z toward you
Both drawn the way each system's own documentation draws it: X to the right, Y up. The grey dashed arrow is where your right thumb points when you curl your fingers along the sweep from X to Y. On the right it lands on +Z. On the left it lands on −Z — so you need your left hand instead. Drag to orbit (the two views are locked together); double-click to reset.

That is the entire difference. One axis points the other way. Now watch what that costs you.

Why they cannot agree about X

Both systems make two promises about what their axes mean for a character, and the promises are the same. Here are the primary sources.

glTF 2.0 Specification § 3.4 — Coordinate System and Units

glTF uses a right-handed coordinate system. glTF defines +Y as up; the front side of a glTF asset faces +Z, the left side of a glTF asset faces +X. The units for all linear distances are meters. All angles are in radians.

registry.khronos.org

Unity Manual — Quaternion and euler rotations in Unity

Unity uses a left-handed coordinate system … the positive x-axis points to the right, the positive y-axis points up, and the positive z-axis points forward.

Unity's manual is terser than the glTF spec here: it pins the axes and the handedness, and that is all this lesson needs from it. Where the reference sheet lists a Unity row with no quotable sentence behind it — units, angle units, multiply order — it says so. docs.unity3d.com

Read those two side by side and the collision is right there. Both say up is +Y. Both say the character's forward is +Z. But glTF says +X is the asset's left, and Unity says +X is its right.

The key idea

Two systems that agree on two axes and differ in handedness cannot agree on the third.

Handedness is a property of the three axes taken together. Fix up and fix forward, and the last axis has no freedom left — its direction is forced by the handedness. Unity and glTF both chose up and forward the same way, so the disagreement had nowhere to go but X. That is not an inconsistency to be worked around. It is the conversion, and the spec sentence the left side of a glTF asset faces +X is where it is written down.

The skill: pick the flip that survives both checks

Below, the same pose is authored in Unity on the left and copied into glTF on the right. Choose what to do to the coordinates on the way across. The body is asymmetric on purpose — a symmetric T-pose looks perfectly fine mirrored, which is exactly how these bugs reach production.

Before you touch it, predict: three of the four options reverse handedness. Which one of those three also leaves the character facing the way glTF expects?

Unity — the truthsource pose
glTF — what you importedresult
On import
Verdict
R marks the character's right hand; the blue stub is its nose. The nose tells you which way it faces, and R tells you whether it has been reflected. Drag to orbit; the views are locked.

Read each body against its own pane's Z arrow, never against the other pane. Each pane draws its axes the way its own documentation does, so the two +Z arrows point in opposite directions. That makes the side-by-side picture actively misleading: under negate Z the two bodies render as the same picture, pixel for pixel, while the correct negate X makes R jump sides and the nose reverse. Matching pictures is the wrong test — it is comparing coordinates across two frames that disagree. The verdict panel below compares each body against its own convention's promises, which is the test that survives.
The trap

Negate Z passes the mirror check. The character is genuinely not reflected — its right hand is still its right hand. It is also facing backwards, because you preserved X and spent the handedness flip on Z, which is the axis that meant forward. It differs from the correct answer by exactly 180° of yaw, so it looks plausible from the front and wrong the instant anything turns around. Negate X is the only option that reverses handedness and keeps every semantic promise glTF makes.

How the verdict panel knows

Neither check up there is hard-coded — both are computed from the pose, so they cannot quietly disagree with this text. They are worth doing by hand once, because they are the two questions you will actually be asking of a real pipeline.

Is it reflected? Take the body's three semantic directions — forward, up, and its own right — and compute the triple product:

the chirality of a body, in a right-handed frame (forward × up) · right

Check it once on a body facing you in a right-handed frame: forward is +Z, up is +Y, and because it faces you, its right is your left, −X. Then (0,0,1) × (0,1,0) = (−1,0,0), which is its right, so the product is +1. Any real body gives +1 here; a mirrored one gives −1. Two things to hold onto: the sign depends on the handedness of the frame you measure in — the same correct body reads −1 in a left-handed frame like Unity's — so compare against a pose you know is right in the same frame. And it is a direction test: it catches mirroring, not wrong limb lengths.

And this is why no rotation can fix a mirror. That triple product is exactly the determinant of the matrix whose columns are forward, up, and right. Apply any linear map A to all three and the determinant is multiplied by det A — that is what a determinant is. A rotation has det = +1, so it leaves the sign alone, every time, for every rotation. A reflection has det = −1. A sign that only det < 0 can change cannot be repaired by anything with det > 0, and that closes off the entire "add a rotation somewhere" family in one line.

Does it honour the target's axes? The second check is different in kind: it compares the body's three semantic directions against what glTF declares its axes mean (+Y up, +Z front, +X the asset's left). This is the stricter of the two — passing it implies not-mirrored — and it is the one that separates negate X from negate Z, because both are unmirrored and only one faces the right way.

The rule

Unity → glTF, positions and directions (x, y, z) → (−x, y, z)

This is what the real importers do. Khronos's UnityGLTF applies a coordinate-space conversion scale of (-1, 1, 1) on the way in and on the way out. It is its own inverse, so the same line of code works in both directions:

// Unity ⇄ glTF. Same function both ways: negating X twice is identity.
static Vector3 FlipHandedness(Vector3 v) => new Vector3(-v.x, v.y, v.z);

Rotations need a partner rule, and it is not the obvious one — you do not negate the quaternion's x. The reason positions get one M and rotations get two is worth a sentence, because it is the whole reason the two rules end up negating opposite components.

A position is a thing in the space, so converting it means converting it once: p′ = M p. A rotation is not a thing in the space — it is an operation on the space, a machine that eats a vector and returns a vector. To run that machine in the new convention you have to translate on the way in and back on the way out: take the new-convention vector back to the old one, rotate there, then bring the result forward again. That is R′ = M R M⁻¹ — three steps, read right to left. And because our M is a mirror, applying it twice is the identity (M² = I, so M⁻¹ = M), which is why it is written M R M here and not M R M⁻¹.

What does that conjugation do to the rotation? A reflection reverses the sense of every rotation, so M R M is a rotation by the negated angle about the reflected axis. Writing q = (sin(θ⁄2)·n, cos(θ⁄2)) and substituting θ → −θ, n → Mn, the two sign changes cancel on the component you flipped and survive on the other two:

The same conversion, for a rotation quaternion (XYZW) (x, y, z, w) → (x, −y, −z, w)

So the position rule and the rotation rule negate opposite components. If you take one thing from this lesson into your next debugging session, take that: a handedness conversion that negates the same letter in both places is wrong, and it is wrong in a way that looks fine until something rotates. Lesson 002 makes you feel that one directly.

What you can now do

Given a mirrored skeleton, you can say which axis is being negated and which one should be — from the spec, not from trial and error. And you can rule out the whole family of "add a rotation to fix it" attempts in one sentence: no rotation changes chirality.

Read this next

Primary source, 4 minutes: glTF 2.0 Specification § 3.4, “Coordinate System and Units”. It is four sentences long and it settles every argument your team is going to have about +X. Read the neighbouring § 3.5.3 on TRS while you are there — it is the subject of lesson 003.

The other side of the boundary, 2 minutes: Unity Manual — Quaternion and euler rotations in Unity. Shorter and less formal than the glTF spec, but it is the sentence to cite when someone claims Unity is right-handed. Note what it does not pin down — units, angle units, multiply order — because those are the rows on the reference sheet that carry no quotation.