fix(io): purge zero-length point-collapsed splines that hang load

sample_layout_03/04.dwg would not open: a periodic 9-control-point spline
with every control point pinned at the origin (zero extent) reaches truck's
parameter_division, which never converges on a zero-length curve and hangs
the first-frame tessellation. The spline passed every existing guard
(finite points, no knots, degree 3). Reject splines whose control-point
extent is sub-precision (< 1e-6 on all axes).

Verified: all five sample_layout DWGs now load + tessellate (03/04 went
from a hang to ~356 ms; 01/02/05 unchanged, no valid spline dropped).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.8 committed 2026-06-10 20:27:45 +03:00
1 parent e5d0c3950b
commit 0ca1e58b1f
1 file changed
+28
+28
View File
@@ -498,10 +498,28 @@ pub(crate) fn is_entity_corrupt(e: &EntityType) -> bool {
&& (s.knots.iter().any(|k| !k.is_finite())
|| s.knots.windows(2).any(|w| w[1] < w[0])
|| s.knots.len() != n + deg + 1);
// Degenerate: every control point collapses onto (nearly) the same
// point, so the curve has zero length. truck's `circle_arc` /
// `parameter_division` never converges on it and the tessellation
// hangs — a periodic 9-point spline pinned at the origin is the seen
// case. Reject when the control-point extent is sub-precision.
let degenerate_extent = n >= 2 && {
let (mut mn, mut mx) = ([f64::MAX; 3], [f64::MIN; 3]);
for p in &s.control_points {
mn[0] = mn[0].min(p.x);
mx[0] = mx[0].max(p.x);
mn[1] = mn[1].min(p.y);
mx[1] = mx[1].max(p.y);
mn[2] = mn[2].min(p.z);
mx[2] = mx[2].max(p.z);
}
(mx[0] - mn[0]).max(mx[1] - mn[1]).max(mx[2] - mn[2]) < 1.0e-6
};
n >= MAX_VERTS
|| degree_bad
|| s.control_points.iter().any(|p| !finite_vec3(p))
|| knots_bad
|| degenerate_extent
}
_ => false,
}
@@ -651,6 +669,16 @@ mod corrupt_guard_tests {
assert!(is_entity_corrupt(&EntityType::Spline(s)));
}
// A periodic spline whose control points all collapse onto (nearly) one
// point has zero length; truck's parameter_division never converges and the
// tessellation hangs. The control-point extent floor must reject it.
#[test]
fn rejects_degenerate_point_spline() {
let pts = vec![Vector3::new(1e-12, -1e-12, 0.0); 9];
let s = Spline::from_control_points(3, pts);
assert!(is_entity_corrupt(&EntityType::Spline(s)));
}
// A normal cubic spline (4 control points, valid clamped knots) survives.
#[test]
fn keeps_valid_spline() {