API Reference · Math utilities
Rotations
Namespace: Ava3D
public static class Rotations
Rotations built from directions rather than from angles.
Quaternion ships with constructors that take an axis and an angle, or three Euler angles. Neither is what a scene usually has. What a scene has is a pair of directions — where a thing points now and where it should point, or which way a ship is flying and which way is up — and turning those into angles first is a detour through trigonometry that loses precision where the angles fold together and gains nothing.
Neither member here calls a trigonometric function, and both take vectors of any length, so a velocity or an unnormalised face normal can be handed over as it is.
Plural, like Primitives, and for a duller reason as well: Rotation is already the name of a property on Node, and a static class of that name could not be called from inside one without qualifying it.
Methods
| Member | Description |
|---|
static Quaternion FromTo(Vector3 from, Vector3 to)
| The shortest rotation taking from onto to. Lengths are ignored; only the directions matter. This is the general case, and Rotations.LookAlong is the named special one. Use it to lay a decal flat on a surface (FromTo(Vector3.UnitZ, normal)), to swing a hinge onto its axis, or to ask how far one attitude is from another — the angle is 2·acos(|q.W|), which is the question Euler angles cannot answer at all. Built from the half-way direction: with h the unit bisector of the two, the answer is (from × h, from · h), already unit and needing no normalisation. The textbook form (from × to, 1 + from·to) normalised is the same rotation and one operation shorter, but that 1 + from·to cancels catastrophically as the two directions approach opposite, and measured against a double-precision reference it is about twice as far out over uniformly random pairs — 2.4e-5 against 1.2e-5 — for no gain. Two inputs have no single answer, and neither returns a NaN: a zero-length vector gives Quaternion.Identity, and directions within about a twentieth of a degree of opposite give a half turn about an arbitrary perpendicular axis, since every one of them is equally short. That arbitrary choice is stable — the same inputs always give the same axis — but it is a choice, and a caller that turns a vector through 180° every frame will see it jump. |
static Quaternion LookAlong(Vector3 forward, Nullable<Vector3> up = default)
| The rotation that aims a model's nose along forward and keeps its top as near up as that heading allows. The nose is −Z and the top is +Y, which is the glTF convention, the same one Camera.Forward follows, and what every exporter writes by default. A model that came out of a modelling package already faces this way. up is a preference, not a constraint — it cannot be honoured exactly unless it happens to be perpendicular to the heading, so what is honoured is its component across the heading, which is what "keep the wings level" means for an aircraft climbing at 30°. World +Y when omitted. Pass something else to bank a turn (the flown attitude's own up), to stand a signpost on a slope (the surface normal), or to aim from a spacecraft that has no horizon at all.
The basis is built directly — side, then top, then straight to a quaternion — rather than by composing two Rotations.FromTo arcs, which is the obvious construction and is far worse: over 20,000 random headings the composed form lands the nose 5.3e-4 from where it was asked to and this one lands it 3.4e-7, because each arc carries the error of the last and the second one is near a half turn whenever the first left the top pointing away from up. There is a singularity, and it is real rather than a rounding problem. A heading along up leaves nothing to level against: the roll about the nose depends entirely on which side the heading was approached from, so no function of the heading alone can be continuous there. Within about 1e-5 radians of the up axis this stops trying and takes a fixed perpendicular instead, which is deterministic — the same heading always gives the same attitude — but is not the limit of the headings around it, and something rolling through straight up will snap once as it passes. Anything that must survive that carries its own up vector and passes it in; Camera.RollToward is the same idea for the camera. |
See also