From 1ed3315e5043eec498d02606ce1f1eff91ffcb6e Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 20 Jun 2026 13:28:36 +0300 Subject: [PATCH] fix(ocs): use Wz in arbitrary-axis algorithm else branch The OCS arbitrary-axis algorithm crossed the normal with Wx=(1,0,0) in the else branch, which collapses to zero for an X-aligned normal (e.g. N=(-1,0,0)): the OCS axes became zero and every point mapped to the origin. The resulting coincident points fed a NaN truck arc whose parameter_division never converged, hanging the load on "Parsing entities". Cross with Wz=(0,0,1) per the DXF spec. (#142) Co-Authored-By: Claude Opus 4.8 --- src/scene/view/transform.rs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/scene/view/transform.rs b/src/scene/view/transform.rs index e55edd1c8..965838993 100644 --- a/src/scene/view/transform.rs +++ b/src/scene/view/transform.rs @@ -97,15 +97,20 @@ pub fn ocs_axes(normal: (f64, f64, f64)) -> ((f64, f64, f64), (f64, f64, f64)) { return ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0)); } // Arbitrary-axis algorithm (DXF spec). - // When |Nx| < 1/64 and |Ny| < 1/64 use Wy=(0,1,0)×N, else Wx=(1,0,0)×N. + // When |Nx| < 1/64 and |Ny| < 1/64 the normal is near ±Z, so cross with + // Wy=(0,1,0); otherwise cross with Wz=(0,0,1). The else branch MUST use Wz, + // not Wx: Wx=(1,0,0)×N collapses to zero for an X-aligned normal (e.g. + // N=(-1,0,0)), zeroing the OCS axes and mapping every point to the origin — + // the resulting coincident points then send the arc tessellator into an + // infinite loop. (#142) let (ax_x, ax_y, ax_z) = if nx.abs() < 1.0 / 64.0 && ny.abs() < 1.0 / 64.0 { // (0,1,0) × (nx,ny,nz) = (nz, 0, -nx) let (x, y, z) = (nz, 0.0, -nx); let len = (x * x + y * y + z * z).sqrt().max(1e-12); (x / len, y / len, z / len) } else { - // (1,0,0) × (nx,ny,nz) = (0, -nz, ny) - let (x, y, z) = (0.0, -nz, ny); + // (0,0,1) × (nx,ny,nz) = (-ny, nx, 0) + let (x, y, z) = (-ny, nx, 0.0); let len = (x * x + y * y + z * z).sqrt().max(1e-12); (x / len, y / len, z / len) }; @@ -200,3 +205,16 @@ mod mirror_delegation_tests { assert!((sx - 8.0).abs() < 1e-9 && sy.abs() < 1e-9); } } + +#[cfg(test)] +mod ocs_axes_142 { + #[test] + fn x_normal_axes_nonzero() { + let (ax, ay) = super::ocs_axes((-1.0, 0.0, 0.0)); + let nz = |v: (f64,f64,f64)| v.0.abs()+v.1.abs()+v.2.abs(); + assert!(nz(ax) > 0.5, "ax collapsed: {:?}", ax); + assert!(nz(ay) > 0.5, "ay collapsed: {:?}", ay); + let p = super::ocs_point_to_wcs((-25.0, 90.0, 0.0), (-1.0,0.0,0.0)); + assert!(p.0.abs()+p.1.abs()+p.2.abs() > 1.0, "point collapsed: {:?}", p); + } +}