The orthonormal camera basis lookAt builds, exposed on its own.
Extracted so the only other place that reconstructs a screen basis —
CameraProjection.unprojectToRay's orthographic branch, which turns a
cursor position into a picking/measurement ray — derives it from this
function instead of recomputing cross(forward, up) itself (#2467). The
duplicate was not merely a second copy: it had none of the degeneracy
handling below, so a non-finite camera.up returned a ray whose origin
was (NaN, NaN, NaN) while the rendered frame was perfectly fine. A NaN
origin does not throw; it makes every t < tMin / dist < tolerance
hit test false, so picking reports "clicked empty space" and a
measurement silently refuses to snap.
That is also what settles what an unprojection should return when the
basis is unusable, which is otherwise a matter of taste. It should
return the ray for the basis the user is actually looking at: when
up carries no usable orientation this function substitutes a
deterministic hint and the view matrix — hence the rendered image — is
built from that substitute. Deriving the ray from the same substitute
makes picking agree with the picture. Returning null instead would
fail a pick on a frame the user can see and interact with, and push a
branch onto every caller of a published API.
eye is returned alongside the axes because it is scrubbed here too,
and a ray origin must come from the same scrubbed eye the view matrix's
translation row does.
All four vectors are always finite. right/up are unit length;
forward points from eye towards target (the negation of lookAt's
third row, which is the camera's backward axis).
The orthonormal camera basis
lookAtbuilds, exposed on its own.Extracted so the only other place that reconstructs a screen basis —
CameraProjection.unprojectToRay's orthographic branch, which turns a cursor position into a picking/measurement ray — derives it from this function instead of recomputingcross(forward, up)itself (#2467). The duplicate was not merely a second copy: it had none of the degeneracy handling below, so a non-finitecamera.upreturned a ray whose origin was(NaN, NaN, NaN)while the rendered frame was perfectly fine. A NaN origin does not throw; it makes everyt < tMin/dist < tolerancehit test false, so picking reports "clicked empty space" and a measurement silently refuses to snap.That is also what settles what an unprojection should return when the basis is unusable, which is otherwise a matter of taste. It should return the ray for the basis the user is actually looking at: when
upcarries no usable orientation this function substitutes a deterministic hint and the view matrix — hence the rendered image — is built from that substitute. Deriving the ray from the same substitute makes picking agree with the picture. Returningnullinstead would fail a pick on a frame the user can see and interact with, and push a branch onto every caller of a published API.eyeis returned alongside the axes because it is scrubbed here too, and a ray origin must come from the same scrubbed eye the view matrix's translation row does.All four vectors are always finite.
right/upare unit length;forwardpoints from eye towards target (the negation oflookAt's third row, which is the camera's backward axis).