OptionalonOptional change hook invoked at the end of every mutator. Engine-internal: owners install it, user code normally leaves it unset. It is intentionally not copied by Quaternion.copy or Quaternion.clone.
Allocates a new quaternion with the same components. The clone has no change hook. This is the only allocating method on the type (§7b).
Negates the vector part, giving the conjugate. For a unit quaternion this is the inverse rotation; for a non-unit quaternion it is not (the inverse would also divide by the squared length).
Copies the components of q into this quaternion. The change hook is not copied.
Resets this quaternion to the identity rotation (0, 0, 0, 1).
Hamilton product this = this * q: the resulting rotation applies q
first and this quaternion second. Aliasing-safe — q may be this.
Scales this quaternion to unit length.
Zero-length behaviour (defined here): a zero-length quaternion is not a
rotation at all and cannot be scaled into one, so it is reset to the
identity rather than producing NaN. This differs from Vector3.normalize,
which leaves the zero vector alone — the zero vector is a legitimate
value downstream, the zero quaternion never is. The change hook fires in
either case.
Range behaviour — the guard below tests the squared length, so three ranges do not produce a unit quaternion and none of them reports an error (measured from a consumer seat, dogfood cycle 10):
0 (components below
about 1.57e-162) is read as zero length, so the quaternion is reset
to the identity — a tiny but real rotation is discarded silently.1.3407807929942597e+154 squares
to Infinity, so the reciprocal is 0 and every component becomes 0,
including w. Unlike the zero-length branch, this leaves the zero
quaternion rather than the identity.NaN component makes lengthSquared > 0 false,
so the quaternion is reset to the identity; an infinite component gives
NaN in that slot and 0 in the others.Rotates v by this (unit) quaternion and writes the result into out,
which is required (plan D7: the result is not this, so the caller
owns the storage — nothing here allocates). Returns out.
Aliasing-safe: v and out may be the same vector; v is read into
scalars before out is written. This quaternion is not modified and its
change hook does not fire; out is written through Vector3.set, so
out's hook fires exactly once.
Uses v' = v + 2 * w * (u x v) + 2 * (u x (u x v)) with u = (x, y, z),
the standard expansion of q * v * q⁻¹ for unit q.
Sets all four components.
Sets this quaternion to the rotation of angleRadians about axis,
counter-clockwise when looking down the axis toward the origin
(right-handed, §7a). Angles are radians (§7a) — there is no degree overload.
axis need not be unit length: it is normalized from plain scalars, with
no temporary vector. A zero-length axis has no direction to rotate about,
so it produces the identity rotation rather than NaN (defined here; the
same "no-op beats poisoning the frame" reasoning as Vector3.normalize).
The change hook fires in either case.
Sets this quaternion to the rotation that aims the −Z axis along
direction while keeping the +Y axis as close to up as the aim allows
(§7a: right-handed, +Y up; §7b: radians and mutable math types).
−Z is the engine's one "forward" for every node, not a camera special case:
Matrix4.setPerspective projects down −Z (plan D8), Camera.viewMatrix is
the inverse of a world matrix built on that basis, and a directional or
spot light shines along its node's −Z (§68). One convention means a rig
aims a camera and a light with the same call.
Neither argument need be unit length and up need not be perpendicular to
direction: the basis is built as
zAxis = normalize(−direction) // local +Z, opposite the aim
xAxis = normalize(up × zAxis) // local +X
yAxis = zAxis × xAxis // local +Y, already unit
and converted with Shepperd's method, so the result is a unit quaternion
built from an exactly orthonormal basis. Allocates nothing — the basis
lives in plain scalars — and writes through a single set(...), so the
change hook fires exactly once.
A zero-length direction, a zero-length up, an up parallel to
direction, or a non-finite component of either leaves every component
alone and does not fire the change hook: there is no rotation those
inputs describe, and the two rejected alternatives are both worse.
Resetting to the identity substitutes a different, plausible-looking
orientation for the failure (the reasoning Matrix4.invert already gives
for refusing a singular matrix); picking a fallback up silently rewrites
the aim the caller asked for. Leaving the previous aim in place is the only
outcome that neither invents nor destroys information.
The math package validates nothing (see Matrix4.setPerspective), so this
method reports nothing either. Node.lookAt in @fourjs/scene is the policy
layer: it makes the same two tests on its own inputs and throws, so a scene
node never reaches the silent branch.
Spherically interpolates this quaternion toward target by t, mutating
this quaternion. t = 0 leaves it unchanged, t = 1 makes it equal to
target (up to sign — see below). Values outside [0, 1] extrapolate
along the same arc; no clamping is applied. Both inputs are assumed unit
length.
Shortest arc (plan D8): q and -q denote the same rotation, so when the
dot product is negative the target's components are negated for the
interpolation. The result then travels the short way round, and at t = 1
equals -target — the same rotation as target, with opposite components.
Nearly parallel inputs (dot above SLERP_LINEAR_THRESHOLD, 0.9995)
fall back to normalized linear interpolation, which avoids dividing by
sin(theta) as theta goes to zero. See that constant for the error
bound.
Mutable unit quaternion
(x, y, z, w)representing a 3D rotation (§7b). The world is right-handed with +Y up (§7a) and every angle is radians; quaternions compose with the Hamilton product, soa.multiply(b)yields the rotation that appliesbfirst andasecond.The identity rotation is
(0, 0, 0, 1)— hencewdefaults to 1 while the vector part defaults to 0.Allocation policy (§7b, plan D7): mutating methods mutate in place and return
this; only Quaternion.clone allocates. Quaternion.rotateVector3 produces a result that is notthis, so it takes a requiredoutparameter and returns it — nothing on that path allocates either.Change notification (plan D3): every mutator invokes Quaternion.onChanged exactly once, after the last component is written. Direct field writes (
q.x = 1) bypass the hook by design.Methods assume unit quaternions where the mathematics requires it (Quaternion.conjugate as inverse, Quaternion.rotateVector3, Quaternion.slerp); nothing normalizes defensively on those hot paths.