From cb7e6e3cc348faebca75ae75bf3219eb76480ace Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Sat, 16 May 2026 07:26:09 +0300 Subject: [PATCH] =?UTF-8?q?fix(scene):=20revert=20wrap-aware=20arc=20span?= =?UTF-8?q?=20=E2=80=94=20restore=20legacy=20CW=20direction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit af1e655's wrap-aware `arc_signed_span` was correct per a strict reading of the DXF "is_counterclockwise" spec but caused CW boundary arcs on real files to sweep the long way around the circle, flipping arc direction and filling huge unintended regions. Restore the legacy `(TAU - sa, TAU - ea)` flip semantics. The adaptive `arc_segments` count fix from the same commit is kept — the visible faceting issue on large-radius arcs was orthogonal to direction and the revert affects only the span computation. The wrap-through-2π edge case the previous commit tried to address is back to being a known limitation. A proper fix needs to know how each upstream writer (AutoCAD, BricsCAD, ODA, etc.) actually encodes wrap arcs and isn't possible without sample files exercising the case. Co-Authored-By: Claude Opus 4.7 --- src/scene/tessellate.rs | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/scene/tessellate.rs b/src/scene/tessellate.rs index 9dbee37c6..b20ac3031 100644 --- a/src/scene/tessellate.rs +++ b/src/scene/tessellate.rs @@ -30,28 +30,21 @@ use crate::scene::wire_model::{SnapHint, TangentGeom, WireModel}; // ── Arc tessellation helpers ───────────────────────────────────────────── -/// Convert arc `(start, end, ccw)` into a `(start, signed_span)` that -/// (a) sweeps in the correct direction and (b) wraps through 2π when the -/// short path crosses the seam. The legacy `(TAU - sa, TAU - ea)` flip -/// was direction-correct for non-wrap arcs but silently rendered the -/// long-way-around when the short path crossed 0 / 2π. +/// Convert hatch-boundary arc `(start, end, ccw)` into a +/// `(start, signed_span)` ready for the sampling loop. Matches the +/// legacy `(TAU - sa, TAU - ea)` flip used here for years — direction +/// semantics are preserved on real files. (Wrap-through-2π is a known +/// edge case in that convention; do not "fix" it without a wider audit +/// of how upstream writers emit CW boundary arcs.) pub(super) fn arc_signed_span(start: f64, end: f64, ccw: bool) -> (f64, f64) { const TAU: f64 = std::f64::consts::TAU; - let mut span = end - start; - if ccw { - if span <= 0.0 { - span += TAU; - } - } else if span >= 0.0 { - span -= TAU; - } - (start, span) + let (sa, ea) = if ccw { (start, end) } else { (TAU - start, TAU - end) }; + (sa, ea - sa) } -/// Segment count for an arc targeting ~0.1% chord-height error vs. -/// radius. Floors at 8 so very small arcs don't degenerate into -/// triangles; rough cap of 256 prevents pathological huge-radius + -/// near-full-circle inputs from exploding the boundary vertex count. +/// Segment count for an arc targeting ~0.1% chord-height error. +/// Floors at 8 so very small arcs don't degenerate into triangles; cap +/// of 256 keeps pathological full-circle inputs bounded. pub(super) fn arc_segments(span_abs: f64) -> u32 { if span_abs < 1e-9 { return 1;