eulerInto method
Write this rotation's Euler angles (radians, see the class docs) into
out.
Every rotation has two Euler triples (plus multiples of 2π). Without a
hint the one with x in -π/2, π/2 and y, z in (-π, π] is returned.
With a hint — typically the angles the rotation was last set from —
the triple closest to it is returned, each angle unwrapped towards the
hint; and when setEuler(hint) already gives this rotation, out is
exactly the hint. That is what lets an inspector show 370° or
(180°, 0, 180°) and get the same numbers back.
Safe when out == hint.
Implementation
void eulerInto(Vector3 out, {Vector3? hint}) {
if (hint != null && _reproduces(hint.x, hint.y, hint.z)) {
out.setValues(hint.x, hint.y, hint.z);
return;
}
final xx = x * x, yy = y * y, zz = z * z;
final m12 = 2.0 * (y * z - x * w);
final m02 = 2.0 * (x * z + y * w);
final m22 = 1.0 - 2.0 * (xx + yy);
final m10 = 2.0 * (x * y + z * w);
final m11 = 1.0 - 2.0 * (xx + zz);
final sx = -m12;
// cos x from a whole row: unlike asin(sx), it stays exact near ±90°.
final cx = math.sqrt(m10 * m10 + m11 * m11);
double ex, ey, ez;
if (cx > 1e-8) {
ex = math.atan2(sx, cx);
ey = math.atan2(m02, m22);
ez = math.atan2(m10, m11);
} else {
// Gimbal lock (x = ±90°): only y ∓ z is defined. Keep the hint's z
// (0 without one) and solve y from it. Below this cos x, rounding in
// the matrix moves y and z more than snapping x to ±90° does.
final m00 = 1.0 - 2.0 * (yy + zz);
final m20 = 2.0 * (x * z - y * w);
final s = sx > 0 ? 1.0 : -1.0;
ex = s * math.pi / 2;
ez = hint?.z ?? 0.0;
ey = math.atan2(-m20, m00) + s * ez;
if (hint == null) ey = _wrapPi(ey);
}
if (hint == null) {
out.setValues(ex, ey, ez);
return;
}
// The other triple for the same rotation: (π - x, y + π, z + π).
final ax1 = _near(ex, hint.x), ay1 = _near(ey, hint.y);
final az1 = _near(ez, hint.z);
final ax2 = _near(math.pi - ex, hint.x);
final ay2 = _near(ey + math.pi, hint.y);
final az2 = _near(ez + math.pi, hint.z);
final d1 = _sq(ax1 - hint.x) + _sq(ay1 - hint.y) + _sq(az1 - hint.z);
final d2 = _sq(ax2 - hint.x) + _sq(ay2 - hint.y) + _sq(az2 - hint.z);
if (d2 < d1) {
out.setValues(ax2, ay2, az2);
} else {
out.setValues(ax1, ay1, az1);
}
}