diff --git a/crates/sketch/src/model.rs b/crates/sketch/src/model.rs index d426826c45..29190e515d 100644 --- a/crates/sketch/src/model.rs +++ b/crates/sketch/src/model.rs @@ -1716,9 +1716,29 @@ impl Sketch { } } - /// Axis-aligned bounds of all geometry (for views). + /// Axis-aligned bounds of all geometry (for views). An arc's are exact: its polyline's points + /// need not reach the circle's extremes (a whole circle is one arc, whose chords start at its + /// start angle), so the quarter points it sweeps through are added. pub fn bounds(&self) -> Option<(Vec2, Vec2)> { - let b: Aabb2 = (0..self.curves.len()).flat_map(|i| self.segs(i)).flat_map(|s| s.polyline(0.5)).collect(); + let extremes = |s: &Seg2| -> Vec { + let Seg2::Arc { center, radius, start, sweep } = *s else { return Vec::new() }; + let (lo, hi) = if sweep >= 0.0 { (start, start + sweep) } else { (start + sweep, start) }; + let quarter = std::f64::consts::FRAC_PI_2; + let first = (lo / quarter).ceil() as i64; + let last = (hi / quarter).floor() as i64; + if !(lo.is_finite() && hi.is_finite()) || last - first > 4 { + return Vec::new(); + } + (first..=last).map(|k| center + Vec2::from_angle(k as f64 * quarter) * radius).collect() + }; + let b: Aabb2 = (0..self.curves.len()) + .flat_map(|i| self.segs(i)) + .flat_map(|s| { + let mut pts = s.polyline(0.5); + pts.extend(extremes(&s)); + pts + }) + .collect(); (!b.is_empty()).then_some((b.min, b.max)) } } diff --git a/crates/sketch/src/tests.rs b/crates/sketch/src/tests.rs index 29a58496ed..1e5e61f093 100644 --- a/crates/sketch/src/tests.rs +++ b/crates/sketch/src/tests.rs @@ -1279,3 +1279,19 @@ fn a_fillet_folded_through_flat_is_refused_like_a_fold() { } assert!(refused.is_some_and(|s| (7..=9).contains(&s)), "refused at {refused:?}"); } + +/// A sketch's bounds reach a circle's and an arc's extremes, not only the points of their +/// polylines: a whole circle is one arc whose chords start at its start angle, and a circle of +/// radius 5 at (-300, 0) spans x from -305 exactly. +#[test] +fn bounds_reach_the_extremes_of_circles_and_arcs() { + let mut sk = Sketch::new(); + sk.add_circle(v(-300.0, 0.0), 5.0, None, None).unwrap(); + let (lo, hi) = sk.bounds().unwrap(); + assert!(lo.dist(v(-305.0, -5.0)) < 1e-9 && hi.dist(v(-295.0, 5.0)) < 1e-9, "{lo:?} {hi:?}"); + // A quarter arc from (10, 0) to (0, 10) about the origin: its corners, no more. + let mut sk = Sketch::new(); + sk.add_arc(v(0.0, 0.0), v(10.0, 0.0), v(0.0, 10.0), [None; 3], None).unwrap(); + let (lo, hi) = sk.bounds().unwrap(); + assert!(lo.dist(v(0.0, 0.0)) < 1e-9 && hi.dist(v(10.0, 10.0)) < 1e-9, "{lo:?} {hi:?}"); +}