mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-26 19:18:14 -06:00
#1010: recover a static Orientation's Y angle without losing precision near gimbal lock
uniform_parent_pose took the Y angle from asin, which loses precision within about 1e-6 degrees of +-90. The Y angle now comes from atan2 on cos(Y), as the X and Z angles already do. The gimbal branch and the principal-range output are unchanged. A new test parents a static Orientation with Y 1e-6 degrees off gimbal lock under a uniform parent. Its largest corner displacement was 3.3e-8 px before this change and 1.5e-14 px after. Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Haiku 5.5
parent
470ea7a3af
commit
be99fecfdc
@@ -743,9 +743,10 @@ fn uniform_parent_pose(m: effectcraft_geom::Mat4, orientation: [f64; 3]) -> Opti
|
||||
return Some((orientation, factor));
|
||||
}
|
||||
let r = (rotation * Mat4::orientation(Vec3::from(orientation))).0;
|
||||
let sy = (-r[2][0]).clamp(-1.0, 1.0);
|
||||
let y = sy.asin();
|
||||
let (x, z) = if (1.0 - sy * sy).sqrt() > 1e-9 { (r[2][1].atan2(r[2][2]), r[1][0].atan2(r[0][0])) } else { ((-r[1][2]).atan2(r[1][1]), 0.0) };
|
||||
// atan2 rather than asin: near ±90 degrees asin loses precision, while cos(y) stays exact.
|
||||
let cy = r[0][0].hypot(r[1][0]);
|
||||
let y = (-r[2][0]).atan2(cy);
|
||||
let (x, z) = if cy > 1e-9 { (r[2][1].atan2(r[2][2]), r[1][0].atan2(r[0][0])) } else { ((-r[1][2]).atan2(r[1][1]), 0.0) };
|
||||
Some(([x.to_degrees(), y.to_degrees(), z.to_degrees()], factor))
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user