//! What the constructive seed reads from a sketch and how it intersects it, shared by the //! point-by-point placement and the cluster decomposition: the relations driving constraints //! set between points, their loci, circle ∩ circle, circle ∩ line, line ∩ line, which of two //! mirror-image intersections keeps the orientation of a reference layout (Owen 1991, §2.4.3 //! and §5), and where two or more loci put a point ([`place`]). //! //! [`read`] turns the driving constraints into //! //! - **relations between two points** ([`Rel`]): coincidence, distance, line length (also //! through equal lines), horizontal and vertical (also through parallel, perpendicular and //! collinear lines, and axis-aligned line angles), horizontal and vertical distances; the //! radius of an arc (a radius or diameter, also through equal arcs and circles) as distances //! from its centre to its ends, a point on a circle of known radius, concentric centres, //! tangent circles of known radii (external or internal as in the reference), the centre //! distance of a slot of known radii; and, on a horizontal or vertical line, a point on it, //! at a distance from it, its midpoint, a point mirrored across it and a tangent circle of //! known radius (its centre at the radius from the line, above or beside the touch point). //! These are invariant under translation (and rotation unless along an axis), so the //! decomposition's clusters are built on them; //! - **ties** between more points ([`Tie`]), read where a placement has the points they are //! anchored on: a point on a line or at a distance from it (a tangent circle's centre, with a //! known or arc radius), the radius to a touch point normal to the line, a midpoint, a point //! mirrored across a line, equal lengths, an arc's ends at one distance from its centre (and //! the centre on their bisector), and a line along a direction derived from a line angle, an //! angle to another line, or parallel or perpendicular to it. //! //! Signs no residual keeps (horizontal or vertical distances, distances from a line, which //! side of a line a tangent circle is on, internal or external tangency) come from the //! reference layout. Ellipses, splines, conics, arc sweeps and lengths, and curvature //! continuity are not read: the solver handles them. //! //! Tolerances are relative to the loci's own size plus the rounding of the coordinates they //! are computed from, so a construction far from the origin behaves like the same one near it. use solidcraft_geom::Vec2; use crate::model::{ConstraintKind, CurveKind, MAX_COORD, Sketch, in_range, radius_in_range}; use crate::union_find::{find, union}; /// Two intersections closer than this (relative to the size of the loci: radii, centre /// distance) are one touching intersection. pub(crate) const TANGENT: f64 = 1e-6; /// A point this close to the line that mirrors two candidates (relative to the size of the /// loci) is on neither side of it: its branch is unknown. const ON_MIRROR: f64 = 1e-9; /// A distance shorter than this (mm) joins two points into one. const ZERO_LENGTH: f64 = 1e-9; /// Loci considered per point; more only add pairs that confirm the same position. pub(crate) const MAX_LOCI: usize = 16; /// Direction sources read per line. const MAX_SOURCES: usize = 4; /// How many lines deep a direction is derived through angles between lines. const DEPTH: usize = 3; /// Lines whose ties one placed point wakes through direction dependencies. const MAX_WOKEN_LINES: usize = 16; /// Largest absolute coordinate. pub(crate) fn mag(v: Vec2) -> f64 { v.x.abs().max(v.y.abs()) } /// Rounding error of a position computed from coordinates up to `m` (mm), with headroom for /// the few operations of an intersection. Independent of the loci's own size. fn rounding(m: f64) -> f64 { 8.0 * f64::EPSILON * m } /// A length tolerance: `rel` of the loci's own `size` (at least 1 mm), plus the rounding of /// coordinates up to `coords`. pub(crate) fn tol(rel: f64, size: f64, coords: f64) -> f64 { rel * size.max(1.0) + rounding(coords) } /// A point `miss` away from a locus of `size` (coordinates up to `coords`) lies on it. pub(crate) fn within(miss: f64, size: f64, coords: f64) -> bool { miss <= tol(TANGENT, size, coords) } /// `v` is clear of zero: farther from it than the `ON_MIRROR` share of `size` plus the /// rounding of coordinates up to `coords`. fn clear_of_zero(v: f64, size: f64, coords: f64) -> bool { v.abs() > tol(ON_MIRROR, size, coords) } /// The sign of an offset `delta` between two points (coordinates up to `coords`) that an /// offset of `size` keeps; None when it is too close to zero to tell. pub(crate) fn side(delta: f64, size: f64, coords: f64) -> Option { clear_of_zero(delta, size, coords).then(|| delta.signum()) } /// How a driving constraint relates two points, as seen from either of them. #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) enum Rel { Same, SameX, SameY, Dist(f64), /// |q.x − p.x| = d. OffsetX(f64), /// |q.y − p.y| = d. OffsetY(f64), } impl Rel { /// Sort key, so that a result never depends on the order of the constraints. pub(crate) fn key(self) -> (u8, u64) { match self { Rel::Same => (0, 0), Rel::SameX => (1, 0), Rel::SameY => (2, 0), Rel::Dist(d) => (3, d.to_bits()), Rel::OffsetX(d) => (4, d.to_bits()), Rel::OffsetY(d) => (5, d.to_bits()), } } /// The two points are one: coincident, or at a distance shorter than [`ZERO_LENGTH`]. pub(crate) fn joins(self) -> bool { match self { Rel::Same => true, Rel::Dist(d) => d < ZERO_LENGTH, _ => false, } } /// Along an axis of the sketch: holds only in a frame oriented as the sketch's. fn along_axis(self) -> bool { matches!(self, Rel::SameX | Rel::SameY | Rel::OffsetX(_) | Rel::OffsetY(_)) } /// The dimension's value (zero for the relations without one). pub(crate) fn size(self) -> f64 { match self { Rel::Same | Rel::SameX | Rel::SameY => 0.0, Rel::Dist(d) | Rel::OffsetX(d) | Rel::OffsetY(d) => d, } } /// Does it hold between `p` and `q`, within the tolerance of a locus of size `scale` /// (at least the dimension's own)? pub(crate) fn holds(self, p: Vec2, q: Vec2, scale: f64) -> bool { let d = q - p; let miss = match self { Rel::Same => d.len(), Rel::SameX => d.x.abs(), Rel::SameY => d.y.abs(), Rel::Dist(v) => (d.len() - v).abs(), Rel::OffsetX(v) => (d.x.abs() - v).abs(), Rel::OffsetY(v) => (d.y.abs() - v).abs(), }; within(miss, self.size().max(scale), mag(p).max(mag(q))) } /// The locus of a point so related to a point at `at`; `side` is the side of an offset /// (±1, 0 when unknown). None for a coincidence, or an offset whose side is unknown. fn locus(self, at: Vec2, side: f64) -> Option { Some(match self { Rel::Same => return None, Rel::SameX => Locus::Line { o: at, d: Vec2::Y }, Rel::SameY => Locus::Line { o: at, d: Vec2::X }, Rel::Dist(r) => Locus::Circle { c: at, r }, // A zero offset needs no side; any other offset needs the side the point was on. Rel::OffsetX(d) if d == 0.0 || side != 0.0 => Locus::Line { o: at + Vec2::X * (side * d), d: Vec2::Y }, Rel::OffsetY(d) if d == 0.0 || side != 0.0 => Locus::Line { o: at + Vec2::Y * (side * d), d: Vec2::X }, Rel::OffsetX(_) | Rel::OffsetY(_) => return None, }) } /// Which side of the anchor `p` was on before, along x or y (0 when unknown or level). fn offset_side(self, p: Option, anchor: Option) -> f64 { let (Some(p), Some(a)) = (p, anchor) else { return 0.0 }; let (delta, d) = match self { Rel::OffsetX(d) => (p.x - a.x, d), Rel::OffsetY(d) => (p.y - a.y, d), _ => return 0.0, }; side(delta, d, mag(p).max(mag(a))).unwrap_or(0.0) } } /// A relation between two points, as stored at each of them. #[derive(Clone, Copy, Debug)] pub(crate) struct Edge { pub p: usize, pub q: usize, pub rel: Rel, } impl Edge { pub(crate) fn other(&self, i: usize) -> usize { if self.p == i { self.q } else { self.p } } } /// Where a point can be. #[derive(Clone, Copy, Debug)] pub(crate) enum Locus { Circle { c: Vec2, r: f64, }, /// The line through `o` along the unit direction `d`. Line { o: Vec2, d: Vec2, }, } impl Locus { /// How far `x` is from the locus. pub(crate) fn miss(&self, x: Vec2) -> f64 { match *self { Locus::Circle { c, r } => (x.dist(c) - r).abs(), Locus::Line { o, d } => d.cross(x - o).abs(), } } /// The locus's own size (a line has none). pub(crate) fn extent(&self) -> f64 { match *self { Locus::Circle { r, .. } => r, Locus::Line { .. } => 0.0, } } /// A point of the locus's definition (how far from the origin it is sets the rounding). fn origin(&self) -> Vec2 { match *self { Locus::Circle { c, .. } => c, Locus::Line { o, .. } => o, } } /// A locus the arithmetic can use: finite, in range, a unit direction or a radius. fn checked(self) -> Option { let ok = match self { Locus::Circle { c, r } => in_range(c) && r.is_finite() && (0.0..=MAX_COORD).contains(&r), Locus::Line { o, d } => in_range(o) && d.is_finite() && (d.len() - 1.0).abs() < 1e-9, }; ok.then_some(self) } } /// The intersection of two loci. pub(crate) enum Meet { /// Parallel lines or concentric circles: no isolated intersection. Degenerate, /// The loci do not meet. Empty, One { at: Vec2, transversal: bool, }, /// Mirror images across the line of [`mirror`]: `pos` on its left, `neg` on its right. Two { pos: Vec2, neg: Vec2, }, } /// The line (point, unit direction) across which the two intersections of `l1` and `l2` mirror /// each other. fn mirror(l1: &Locus, l2: &Locus) -> Option<(Vec2, Vec2)> { match (*l1, *l2) { (Locus::Circle { c: c1, .. }, Locus::Circle { c: c2, .. }) => Some((c1, (c2 - c1).normalized()?)), (Locus::Circle { c, .. }, Locus::Line { d, .. }) | (Locus::Line { d, .. }, Locus::Circle { c, .. }) => Some((c, Vec2::new(d.y, -d.x))), (Locus::Line { .. }, Locus::Line { .. }) => None, } } pub(crate) fn meet(l1: &Locus, l2: &Locus) -> Meet { let noise = rounding(mag(l1.origin()).max(mag(l2.origin()))); // The candidates are mid ± h·n (n to the left of the mirror line). h² is a difference of // squares of lengths up to `s`: touching when within the band of its size and of the // rounding of the positions. let chord = |mid: Vec2, h2: f64, n: Vec2, s: f64| { let band = (TANGENT * s).powi(2) + 2.0 * s * noise; if !(h2 >= -band) { Meet::Empty } else if h2 <= band { Meet::One { at: mid, transversal: false } } else { let h = h2.sqrt(); Meet::Two { pos: mid + n * h, neg: mid - n * h } } }; match (*l1, *l2) { (Locus::Circle { c: c1, r: r1 }, Locus::Circle { c: c2, r: r2 }) => { let d = c2 - c1; let dd = d.len(); if !(dd > tol(TANGENT, r1.max(r2), mag(c1).max(mag(c2)))) { return Meet::Degenerate; } let u = d / dd; let along = (r1 * r1 - r2 * r2 + dd * dd) / (2.0 * dd); chord(c1 + u * along, r1 * r1 - along * along, u.perp(), r1.max(r2).max(dd).max(1.0)) } (Locus::Circle { c, r }, Locus::Line { o, d }) | (Locus::Line { o, d }, Locus::Circle { c, r }) => { let foot = o + d * (c - o).dot(d); let off = (c - foot).len2(); // The mirror line runs along -perp(d), so d points to its left. chord(foot, r * r - off, d, r.max(off.sqrt()).max(1.0)) } (Locus::Line { o: o1, d: d1 }, Locus::Line { o: o2, d: d2 }) => { let s = d1.cross(d2); if s.abs() < 1e-12 { return Meet::Degenerate; } Meet::One { at: o1 + d1 * ((o2 - o1).cross(d2) / s), transversal: true } } } } /// Which side of the mirror line of `l1` and `l2` the point `p` is on: positive on its left. /// None when unknown or on the line. Built from the loci as they were in the reference layout, /// it tells which of two intersections keeps the orientation `p` had there. pub(crate) fn branch(l1: Option<&Locus>, l2: Option<&Locus>, p: Option) -> Option { let (l1, l2, p) = (l1?, l2?, p?); let (o, u) = mirror(l1, l2)?; let side = u.cross(p - o); clear_of_zero(side, l1.extent().max(l2.extent()), mag(p).max(mag(o))).then_some(side) } /// The crossing of the loci `now` that keeps the orientation `p` has relative to `then` (the /// same loci in the reference layout). None when the loci do not cross (a touching /// intersection pins nothing down well) or the side is unknown. pub(crate) fn crossing(now: (Locus, Locus), then: Option<(Locus, Locus)>, p: Option) -> Option { match meet(&now.0, &now.1) { Meet::Two { pos, neg } => { let (t1, t2) = then.unzip(); branch(t1.as_ref(), t2.as_ref(), p).map(|side| if side > 0.0 { pos } else { neg }) } Meet::One { at, transversal: true } => Some(at), Meet::One { transversal: false, .. } | Meet::Empty | Meet::Degenerate => None, } } /// What one relation says about a point. #[derive(Clone, Copy, Debug)] pub(crate) enum Shape { /// The point is here. At(Vec2), /// The point is on `locus`; `then` is the same locus in the reference layout (it picks the /// branch of an intersection). On { locus: Locus, then: Option }, } /// One relation of a point to placed points; `anchor` is the placed point it is anchored on /// (the lowest, for a relation anchored on several). #[derive(Clone, Copy, Debug)] pub(crate) struct Item { pub anchor: usize, pub shape: Shape, } /// Where [`place`] puts a point: at the intersection of two loci (or at a point) anchored on /// `a` ≤ `b`; `transversal` when the loci cross. #[derive(Clone, Copy, Debug, PartialEq)] pub(crate) struct Fix { pub at: Vec2, pub a: usize, pub b: usize, pub transversal: bool, } /// Where the relations `items` put a point that was at `p_before`: every pair of loci is /// intersected, and a candidate must also lie on every other locus and point. A transversal /// placement is preferred to a touching one, then the one from the lowest anchors, so the /// result does not depend on the order of the relations. None when no pair pins the point /// down, or the relations conflict. pub(crate) fn place(items: &[Item], p_before: Option) -> Option { let points: Vec<(usize, Vec2)> = items .iter() .filter_map(|i| match i.shape { Shape::At(x) => Some((i.anchor, x)), Shape::On { .. } => None, }) .collect(); let loci: Vec<(usize, Locus, Option)> = items .iter() .filter_map(|i| match i.shape { Shape::On { locus, then } => Some((i.anchor, locus, then)), Shape::At(_) => None, }) .collect(); // Does `x` lie on every point and locus except the ones it was built from? let fits = |x: Vec2, from_point: Option, from_loci: Option<(usize, usize)>, scale: f64| { let ok = |miss: f64, size: f64| within(miss, size.max(scale), mag(x)); points.iter().enumerate().all(|(k, (_, q))| Some(k) == from_point || ok(x.dist(*q), 0.0)) && loci.iter().enumerate().all(|(k, (_, l, _))| from_loci.is_some_and(|(i, j)| k == i || k == j) || ok(l.miss(x), l.extent())) }; // Best first: transversal, then the lowest anchors. let mut best: Option<((bool, (usize, usize)), Vec2)> = None; let mut offer = |transversal: bool, a: usize, b: usize, at: Vec2| { let key = (!transversal, (a.min(b), a.max(b))); if in_range(at) && best.is_none_or(|(k, _)| key < k) { best = Some((key, at)); } }; for (k, (anchor, at)) in points.iter().enumerate() { if fits(*at, Some(k), None, 0.0) { offer(true, *anchor, *anchor, *at); } } for (i, (a1, l1, t1)) in loci.iter().enumerate() { for (j, (a2, l2, t2)) in loci.iter().enumerate().skip(i + 1) { let scale = l1.extent().max(l2.extent()); match meet(l1, l2) { Meet::Degenerate => {} // The constraints conflict here: the solver reports it. Meet::Empty => return None, Meet::One { at, transversal } => { if fits(at, None, Some((i, j)), scale) { offer(transversal, *a1, *a2, at); } } Meet::Two { pos: left, neg: right } => { let at = match (fits(left, None, Some((i, j)), scale), fits(right, None, Some((i, j)), scale)) { (true, false) => Some(left), (false, true) => Some(right), (true, true) => branch(t1.as_ref(), t2.as_ref(), p_before).map(|side| if side > 0.0 { left } else { right }), (false, false) => None, }; if let Some(at) = at { offer(true, *a1, *a2, at); } } } } } best.map(|((touching, (a, b)), at)| Fix { at, a, b, transversal: !touching }) } /// Turn `d` by `by` radians. fn turned(d: Vec2, by: f64) -> Vec2 { let (s, c) = by.sin_cos(); Vec2::new(d.x * c - d.y * s, d.x * s + d.y * c) } /// `x` mirrored across the line through `o` along the unit direction `d`. fn reflected(x: Vec2, o: Vec2, d: Vec2) -> Vec2 { let w = x - o; o + d * (2.0 * w.dot(d)) - w } /// The other one of `x` and `y` when `t` is one of them. fn partner(t: usize, x: usize, y: usize) -> Option { if t == x { Some(y) } else if t == y { Some(x) } else { None } } /// The readings of one value: the lowest and highest read. #[derive(Clone, Copy, Debug)] pub(crate) struct Span { pub lo: f64, pub hi: f64, } impl Span { pub(crate) fn add(span: &mut Option, v: f64) { *span = Some(span.map_or(Span { lo: v, hi: v }, |s| Span { lo: s.lo.min(v), hi: s.hi.max(v) })); } /// The value the readings agree on; None when they disagree. pub(crate) fn agreed(self) -> Option { (self.hi - self.lo <= tol(TANGENT, self.hi.abs(), 0.0)).then_some(self.lo) } } /// Where a line's direction comes from, besides its two placed ends. #[derive(Clone, Copy, Debug, PartialEq)] enum Src { /// Fixed in the sketch's frame (horizontal, vertical, a line angle): only in a frame /// oriented as the sketch's. Fixed(Vec2), /// Line `from`'s direction turned by `by` (an angle between lines; for parallel and /// perpendicular lines the turn of the reference layout). Turn { from: usize, by: f64 }, } impl Src { fn key(&self) -> (u8, usize, u64, u64) { match *self { Src::Fixed(d) => (0, 0, d.x.to_bits(), d.y.to_bits()), Src::Turn { from, by } => (1, from, by.to_bits(), 0), } } } /// A length: a value, or the distance between two points once both are placed. #[derive(Clone, Copy, Debug, PartialEq)] enum Len { Value(f64), Between(usize, usize), } impl Len { fn value(self, pos: &dyn Fn(usize) -> Option) -> Option { match self { Len::Value(v) => Some(v), Len::Between(a, b) => Some(pos(a)?.dist(pos(b)?)), } .filter(|v| v.is_finite() && *v <= MAX_COORD) } } /// A relation between more than two points (lines `l` by curve index). #[derive(Clone, Copy, Debug, PartialEq)] enum Tie { /// `p` lies on line `l` (or, for an end of `l`, `l` runs along its known direction). OnLine { p: usize, l: usize, }, /// `p` is `len` from line `l`, on its side in the reference. Offset { p: usize, l: usize, len: Len, }, /// `p − e` is normal to line `l` (the radius to a touch point; a point and its mirror). Normal { p: usize, e: usize, l: usize, }, Midpoint { p: usize, a: usize, b: usize, }, /// `q` mirrors `p` across line `l`. Mirror { p: usize, q: usize, l: usize, }, /// |q1 − p1| = |q2 − p2|. SameLength { p1: usize, q1: usize, p2: usize, q2: usize, }, /// |b − c| = |a − c| (an arc's ends; a point on an arc). Equidistant { c: usize, a: usize, b: usize, }, } impl Tie { fn key(&self) -> [u64; 6] { let u = |i: usize| i as u64; let len = |l: Len| match l { Len::Value(v) => (0, v.to_bits(), 0), Len::Between(a, b) => (1, u(a), u(b)), }; match *self { Tie::OnLine { p, l } => [0, u(p), u(l), 0, 0, 0], Tie::Offset { p, l, len: n } => { let (k, a, b) = len(n); [1, u(p), u(l), k, a, b] } Tie::Normal { p, e, l } => [2, u(p), u(e), u(l), 0, 0], Tie::Midpoint { p, a, b } => [3, u(p), u(a), u(b), 0, 0], Tie::Mirror { p, q, l } => [4, u(p), u(q), u(l), 0, 0], Tie::SameLength { p1, q1, p2, q2 } => [5, u(p1), u(q1), u(p2), u(q2), 0], Tie::Equidistant { c, a, b } => [6, u(c), u(a), u(b), 0, 0], } } /// The line whose direction it reads. fn line(&self) -> Option { match *self { Tie::OnLine { l, .. } | Tie::Offset { l, .. } | Tie::Normal { l, .. } | Tie::Mirror { l, .. } => Some(l), _ => None, } } /// The points it relates, given the lines' ends. fn points(&self, ends: &dyn Fn(usize) -> Option<(usize, usize)>) -> Vec { let line = |l: usize| ends(l).map(|(a, b)| [a, b]); let mut v: Vec = match *self { Tie::OnLine { p, l } => [p].into_iter().chain(line(l).into_iter().flatten()).collect(), Tie::Offset { p, l, len } => { let between = match len { Len::Value(_) => None, Len::Between(a, b) => Some([a, b]), }; [p].into_iter().chain(line(l).into_iter().flatten()).chain(between.into_iter().flatten()).collect() } Tie::Normal { p, e: q, l } | Tie::Mirror { p, q, l } => [p, q].into_iter().chain(line(l).into_iter().flatten()).collect(), Tie::Midpoint { p, a, b } | Tie::Equidistant { c: p, a, b } => vec![p, a, b], Tie::SameLength { p1, q1, p2, q2 } => vec![p1, q1, p2, q2], }; v.sort_unstable(); v.dedup(); v } } /// A circle or arc: its centre, and an arc's ends. #[derive(Clone, Copy, Debug)] struct Round { c: usize, ends: Option<(usize, usize)>, } /// Everything the seed reads from a sketch, with the reference layout that picks branches /// and signs. pub(crate) struct Reading { /// Relations between two points. pub rels: Vec<(usize, usize, Rel)>, /// Per point: its relations, sorted by the other point (independent of constraint order). adj: Vec>, ties: Vec, /// Per tie: the points it relates (line ends included). tie_points: Vec>, /// Per point: the ties it takes part in. ties_of: Vec>, /// Per curve: a line's ends. lines: Vec>, /// Per curve: a line's direction sources. sources: Vec>, /// Per curve: the lines whose direction derives from it. dependents: Vec>, /// Per curve: the ties that read its direction. using: Vec>, /// Per point: the lines it ends. lines_of: Vec>, /// Per point: its position in the reference layout. before: Vec>, } /// The value each group of `m` items agrees on: `direct` values per item, groups joined by /// `equal`. A group whose values disagree has none. fn agreed(m: usize, direct: &[(usize, f64)], equal: &[(usize, usize)]) -> Vec> { let mut parent: Vec = (0..m).collect(); for &(a, b) in equal { if a < m && b < m { union(&mut parent, a, b); } } let mut span: Vec> = vec![None; m]; for &(i, v) in direct { let root = find(&mut parent, i); if let Some(s) = span.get_mut(root) { Span::add(s, v); } } (0..m).map(|i| span.get(find(&mut parent, i)).copied().flatten().and_then(Span::agreed)).collect() } /// A line's level: horizontal (`Some(true)`) or vertical (`Some(false)`) when `angle` is a /// multiple of a quarter turn. fn level_at(angle: f64) -> Option { if angle.sin().abs() < 1e-12 { Some(true) } else if angle.cos().abs() < 1e-12 { Some(false) } else { None } } /// Per line: horizontal (`Some(true)`) or vertical (`Some(false)`), from horizontal, vertical /// and axis-aligned line angle constraints carried through parallel, collinear (same), /// perpendicular (swapped) lines and lines at a multiple of a quarter turn. In a group of /// lines whose readings contradict each other, each line has only its own readings (when they /// agree). A line with a line angle has none: a row or column has no direction, and the angle /// gives the line its own (see `Src::Fixed`). fn levels(lines: &[Option<(usize, usize)>], cons: &[&ConstraintKind]) -> Vec> { use ConstraintKind::*; let m = lines.len(); let is_line = |l: usize| lines.get(l).is_some_and(Option::is_some); let mut adj: Vec> = vec![Vec::new(); m]; let mut seeds: Vec> = vec![Vec::new(); m]; let mut angled = vec![false; m]; for k in cons { if let LineAngle { l, .. } = **k && let Some(a) = angled.get_mut(l) { *a = true; } let (pair, seed) = match **k { Parallel { a, b } | Collinear { a, b } => (Some((a, b, false)), None), Perpendicular { a, b } => (Some((a, b, true)), None), Angle { a, b, value, .. } if value.is_finite() => (level_at(value).map(|same| (a, b, !same)), None), Horizontal { l } => (None, Some((l, true))), Vertical { l } => (None, Some((l, false))), LineAngle { l, value } if value.is_finite() => (None, level_at(value).map(|row| (l, row))), _ => (None, None), }; if let Some((a, b, swap)) = pair.filter(|(a, b, _)| a != b && is_line(*a) && is_line(*b)) { for (x, y) in [(a, b), (b, a)] { if let Some(list) = adj.get_mut(x) { list.push((y, swap)); } } } if let Some((l, row)) = seed.filter(|(l, _)| is_line(*l)) && let Some(s) = seeds.get_mut(l) { s.push(row); } } // Per component (breadth first from its lowest line): each line's parity relative to the // root, and the root's level every seed agrees on. let mut parity: Vec> = vec![None; m]; let mut out = vec![None; m]; for root in 0..m { if parity.get(root).is_some_and(Option::is_some) || !is_line(root) { continue; } let mut comp = vec![root]; if let Some(p) = parity.get_mut(root) { *p = Some(false); } let (mut consistent, mut level): (bool, Option>) = (true, None); let mut k = 0; while let Some(&x) = comp.get(k) { k += 1; let px = parity.get(x).copied().flatten().unwrap_or(false); for &s in seeds.get(x).into_iter().flatten() { let at_root = s != px; level = match level { None => Some(Some(at_root)), Some(Some(v)) if v == at_root => Some(Some(v)), Some(_) => Some(None), }; } for &(y, swap) in adj.get(x).into_iter().flatten() { match parity.get(y).copied().flatten() { Some(py) => consistent &= py == (px != swap), None => { if let Some(p) = parity.get_mut(y) { *p = Some(px != swap); } comp.push(y); } } } } for x in comp { let px = parity.get(x).copied().flatten().unwrap_or(false); if let Some(o) = out.get_mut(x) && !angled.get(x).copied().unwrap_or(false) { *o = match level { Some(Some(at_root)) if consistent => Some(at_root != px), // A contradiction: the line's own readings, when they agree. _ => seeds.get(x).and_then(|s| s.first().copied().filter(|f| s.iter().all(|v| v == f))), }; } } } out } /// A circle or arc of `sk` (with valid point indices). fn round_of(sk: &Sketch, k: usize) -> Option { let n = sk.points.len(); match sk.curves.get(k)?.kind { CurveKind::Circle { c, .. } if c < n => Some(Round { c, ends: None }), CurveKind::Arc { c, a, b } if c < n && a < n && b < n && c != a && c != b => Some(Round { c, ends: Some((a, b)) }), _ => None, } } /// Per curve: a circle's or arc's radius as the driving dimensions set it (also through equal /// circles and arcs), or a fixed or projected circle's own. pub(crate) fn radii(sk: &Sketch) -> Vec> { use ConstraintKind::*; let mut radii = Vec::new(); let mut equal = Vec::new(); for (k, c) in sk.curves.iter().enumerate() { if let CurveKind::Circle { r, .. } = c.kind && (c.fixed || c.link.is_some()) && radius_in_range(r) { radii.push((k, r)); } } let round = |k: usize| round_of(sk, k).is_some(); for c in sk.constraints.iter().filter(|c| !c.driven) { match c.kind { Radius { c, value } if round(c) && radius_in_range(value) => radii.push((c, value)), Diameter { c, value } if round(c) && radius_in_range(value / 2.0) => radii.push((c, value / 2.0)), Equal { a, b } if round(a) && round(b) => equal.push((a, b)), _ => {} } } agreed(sk.curves.len(), &radii, &equal) } /// The unit direction of the line `ends` at the positions `pos`. fn direction(ends: Option<(usize, usize)>, pos: &dyn Fn(usize) -> Option) -> Option { let (a, b) = ends?; (pos(b)? - pos(a)?).normalized() } /// Read the driving constraints of `sk`; `before(i)` is point `i`'s position in the reference /// layout (`None`, or out of range: unknown, and whatever it would decide is not read); /// `on_curve` the sketch's point-on-curve pairs ([`crate::solver::points_on_curves`]), `radius` /// its radii ([`radii`]). /// /// Every constraint on a line is read as a tie; a tie on a horizontal or vertical line is /// then lowered to the relations between two points it amounts to (a row, a column, an offset /// across it), which the decomposition builds clusters on. pub(crate) fn read(sk: &Sketch, before: &dyn Fn(usize) -> Option, on_curve: &[(usize, usize)], radius: &[Option]) -> Reading { use ConstraintKind::*; let n = sk.points.len(); let m = sk.curves.len(); let before: Vec> = (0..n).map(|i| before(i).filter(|p| in_range(*p))).collect(); let rf = |i: usize| before.get(i).copied().flatten(); let lines: Vec> = sk .curves .iter() .map(|c| match c.kind { CurveKind::Line { a, b } if a < n && b < n && a != b => Some((a, b)), _ => None, }) .collect(); let rounds: Vec> = (0..m).map(|k| round_of(sk, k)).collect(); let line = |l: usize| lines.get(l).copied().flatten(); let round = |k: usize| rounds.get(k).copied().flatten(); let cons: Vec<&ConstraintKind> = sk.constraints.iter().filter(|c| !c.driven).map(|c| &c.kind).collect(); let dim = |v: f64| (v.is_finite() && (0.0..=MAX_COORD).contains(&v)).then_some(v); // Radii and lengths the dimensions set, also through equal curves. let mut lengths = Vec::new(); let mut equal_lines = Vec::new(); for k in &cons { match **k { Length { l, value } if line(l).is_some() => lengths.extend(dim(value).map(|v| (l, v))), Equal { a, b } if line(a).is_some() && line(b).is_some() => equal_lines.push((a, b)), _ => {} } } let length = agreed(m, &lengths, &equal_lines); let level = levels(&lines, &cons); let r_of = |k: usize| radius.get(k).copied().flatten(); let level_of = |l: usize| level.get(l).copied().flatten(); let ref_dir = |l: usize| direction(line(l), &rf); let mut rels = Vec::new(); let mut ties = Vec::new(); let mut rel = |p: usize, q: usize, r: Rel| { if p != q && p < n && q < n { rels.push((p, q, r)); } }; for k in &cons { match **k { Coincident { p, q } => rel(p, q, Rel::Same), HorizontalPoints { p, q } => rel(p, q, Rel::SameY), VerticalPoints { p, q } => rel(p, q, Rel::SameX), Distance { p, q, value } | DistanceX { p, q, value } | DistanceY { p, q, value } => { if let Some(d) = dim(value) { let r = match **k { DistanceX { .. } => Rel::OffsetX(d), DistanceY { .. } => Rel::OffsetY(d), _ => Rel::Dist(d), }; rel(p, q, r); } } PointOnCurve { p, c } if p < n => { if line(c).is_some() { ties.push(Tie::OnLine { p, l: c }); } else if let Some(rd) = round(c) { match (r_of(c), rd.ends) { (Some(r), _) => rel(rd.c, p, Rel::Dist(r)), (None, Some((a, b))) if p != a && p != b => ties.push(Tie::Equidistant { c: rd.c, a, b: p }), _ => {} } } } PointLineDistance { p, l, value } | LinearDiameter { p, l, value } if p < n && line(l).is_some() => { let d = if matches!(**k, LinearDiameter { .. }) { value / 2.0 } else { value }; if let Some(d) = dim(d) { ties.push(Tie::Offset { p, l, len: Len::Value(d) }); } } Tangent { a, b } => { let (l, curve) = if line(a).is_some() { (a, b) } else { (b, a) }; if line(l).is_some() && let Some(rd) = round(curve) { if let Some(len) = r_of(curve).map(Len::Value).or(rd.ends.map(|(s, _)| Len::Between(rd.c, s))) { ties.push(Tie::Offset { p: rd.c, l, len }); } // The radius to the touch point is normal to the line. if let Some(e) = crate::solver::touch_point(sk, k, on_curve).filter(|e| *e < n && *e != rd.c) { ties.push(Tie::Normal { p: rd.c, e, l }); } } else if let (Some(r1), Some(r2), Some(k1), Some(k2)) = (r_of(a), r_of(b), round(a), round(b)) { // External or internal, as the residual picks it on the reference. let ref_r = |k: usize, rd: Round| match (sk.curves.get(k).map(|c| &c.kind), rd.ends) { (Some(CurveKind::Circle { r, .. }), _) => Some(*r), (_, Some((s, _))) => Some(rf(rd.c)?.dist(rf(s)?)), _ => None, }; if let (Some(c1), Some(c2), Some(q1), Some(q2)) = (rf(k1.c), rf(k2.c), ref_r(a, k1), ref_r(b, k2)) { let d = c1.dist(c2); let external = (d - (q1 + q2)).abs() <= (d - (q1 - q2).abs()).abs(); rel(k1.c, k2.c, Rel::Dist(if external { r1 + r2 } else { (r1 - r2).abs() })); } } } Concentric { a, b } => { if let (Some(k1), Some(k2)) = (round(a), round(b)) { rel(k1.c, k2.c, Rel::Same); } } SlotOverallLength { a, b, value } => { if let (Some(k1), Some(k2), Some(r1), Some(r2)) = (round(a), round(b), r_of(a), r_of(b)) && let Some(d) = dim(value - r1 - r2) { rel(k1.c, k2.c, Rel::Dist(d)); } } Midpoint { p, l } if p < n => { if let Some((a, b)) = line(l).filter(|(a, b)| p != *a && p != *b) { ties.push(Tie::OnLine { p, l }); ties.push(Tie::Midpoint { p, a, b }); } } Symmetric { p, q, l } if p < n && q < n && p != q && line(l).is_some() => { ties.push(Tie::Normal { p, e: q, l }); ties.push(Tie::Mirror { p, q, l }); } Collinear { a, b } => { if let (Some((a0, a1)), Some((b0, b1))) = (line(a), line(b)) { for (p, l) in [(b0, a), (b1, a), (a0, b), (a1, b)] { ties.push(Tie::OnLine { p, l }); } } } Equal { a, b } => { if let (Some((a0, a1)), Some((b0, b1))) = (line(a), line(b)) { if length.get(a).copied().flatten().is_none() { ties.push(Tie::SameLength { p1: a0, q1: a1, p2: b0, q2: b1 }); } } else if let (Some(Round { c: c1, ends: Some((s1, _)) }), Some(Round { c: c2, ends: Some((s2, _)) })) = (round(a), round(b)) && r_of(a).is_none() { ties.push(Tie::SameLength { p1: c1, q1: s1, p2: c2, q2: s2 }); } } _ => {} } } for (l, ends) in lines.iter().enumerate() { if let (Some((a, b)), Some(d)) = (*ends, length.get(l).copied().flatten()) { rel(a, b, Rel::Dist(d)); } } for (k, rd) in rounds.iter().enumerate() { let Some(Round { c, ends: Some((a, b)) }) = *rd else { continue }; match r_of(k) { Some(r) => { rel(c, a, Rel::Dist(r)); rel(c, b, Rel::Dist(r)); } None if a != b => ties.push(Tie::Equidistant { c, a, b }), None => {} } } // Line directions: fixed ones, and turns from other lines. let mut sources: Vec> = vec![Vec::new(); m]; { // The turn from `from` to `l` among `base` and `base + π` the reference has (`base` // when it has none). let turn = |from: usize, l: usize, base: f64| match (ref_dir(from), ref_dir(l)) { (Some(f), Some(d)) if turned(f, base).dot(d) < 0.0 => base + std::f64::consts::PI, _ => base, }; let mut add = |l: usize, s: Src| { if let Some(list) = sources.get_mut(l) { list.push(s); } }; for k in &cons { match **k { LineAngle { l, value } if line(l).is_some() && value.is_finite() => add(l, Src::Fixed(Vec2::from_angle(value))), Angle { a, b, value, flip } if a != b && line(a).is_some() && line(b).is_some() && value.is_finite() && value.abs() <= MAX_COORD => { let by = value + if flip { std::f64::consts::PI } else { 0.0 }; add(b, Src::Turn { from: a, by }); add(a, Src::Turn { from: b, by: -by }); } Parallel { a, b } | Collinear { a, b } | Perpendicular { a, b } if a != b && line(a).is_some() && line(b).is_some() => { let base = if matches!(**k, Perpendicular { .. }) { std::f64::consts::FRAC_PI_2 } else { 0.0 }; add(b, Src::Turn { from: a, by: turn(a, b, base) }); add(a, Src::Turn { from: b, by: turn(b, a, base) }); } _ => {} } } // A row or column gives no sign: the reference's (a line with a line angle has its // own, and no level). for l in 0..m { if let Some(row) = level_of(l) { let axis = if row { Vec2::X } else { Vec2::Y }; let d = if ref_dir(l).is_some_and(|d| d.dot(axis) < 0.0) { -axis } else { axis }; add(l, Src::Fixed(d)); } } } let mut dependents: Vec> = vec![Vec::new(); m]; for (l, list) in sources.iter_mut().enumerate() { list.sort_by_key(Src::key); list.dedup(); list.truncate(MAX_SOURCES); // A line with a direction runs along it from either end. if let (Some((_, b)), false) = (line(l), list.is_empty()) { ties.push(Tie::OnLine { p: b, l }); } for s in list.iter() { if let Src::Turn { from, .. } = s && let Some(d) = dependents.get_mut(*from) { d.push(l); } } } for d in &mut dependents { d.sort_unstable(); d.dedup(); } // Ties on a horizontal or vertical line are relations between two points: a row or // column through the line's ends, an offset across it, a column or row normal to it. let lowered = |row: bool, d: Option| match (row, d) { (true, None) => Rel::SameY, (false, None) => Rel::SameX, (true, Some(d)) => Rel::OffsetY(d), (false, Some(d)) => Rel::OffsetX(d), }; ties.retain(|t| { let Some(((a, b), row)) = t.line().and_then(|l| Some((line(l)?, level_of(l)?))) else { return true }; match *t { Tie::OnLine { p, .. } => [a, b].into_iter().for_each(|q| rel(q, p, lowered(row, None))), Tie::Offset { p, len: Len::Value(d), .. } => [a, b].into_iter().for_each(|q| rel(q, p, lowered(row, Some(d)))), Tie::Normal { p, e, .. } => rel(e, p, lowered(!row, None)), Tie::Offset { .. } | Tie::Mirror { .. } | Tie::Midpoint { .. } | Tie::SameLength { .. } | Tie::Equidistant { .. } => return true, } false }); ties.sort_by_key(Tie::key); ties.dedup(); let tie_points: Vec> = ties.iter().map(|t| t.points(&line).into_iter().filter(|p| *p < n).collect()).collect(); let mut ties_of = vec![Vec::new(); n]; let mut using = vec![Vec::new(); m]; for (i, (t, pts)) in ties.iter().zip(&tie_points).enumerate() { for p in pts { if let Some(list) = ties_of.get_mut(*p) { list.push(i); } } if let Some(list) = t.line().and_then(|l| using.get_mut(l)) { list.push(i); } } let mut lines_of = vec![Vec::new(); n]; for (l, ends) in lines.iter().enumerate() { for p in ends.iter().flat_map(|(a, b)| [*a, *b]) { if let Some(list) = lines_of.get_mut(p) { list.push(l); } } } let mut adj = vec![Vec::new(); n]; for &(p, q, rel) in &rels { for end in [p, q] { if let Some(list) = adj.get_mut(end) { list.push(Edge { p, q, rel }); } } } for (i, list) in adj.iter_mut().enumerate() { list.sort_by_key(|e: &Edge| (e.other(i), e.rel.key())); } Reading { rels, adj, ties, tie_points, ties_of, lines, sources, dependents, using, lines_of, before } } /// The points as one frame has them: `at(i)` the position of point `i` (None while /// unplaced), `oriented` when the frame is turned as the sketch is (directions fixed in the /// sketch hold). pub(crate) struct View<'a> { pub at: &'a dyn Fn(usize) -> Option, pub oriented: bool, } impl Reading { /// How many points the sketch has. pub(crate) fn points(&self) -> usize { self.adj.len() } /// How many ties it read. pub(crate) fn tie_count(&self) -> usize { self.ties.len() } /// The relations of point `i`. pub(crate) fn adj(&self, i: usize) -> &[Edge] { self.adj.get(i).map_or(&[][..], Vec::as_slice) } /// How many ties point `i` takes part in. pub(crate) fn tie_count_of(&self, i: usize) -> usize { self.ties_of.get(i).map_or(0, Vec::len) } pub(crate) fn before(&self, i: usize) -> Option { self.before.get(i).copied().flatten() } /// The point lists of every tie. pub(crate) fn tie_point_lists(&self) -> impl Iterator { self.tie_points.iter().map(Vec::as_slice) } /// The points related to `i` by a relation. pub(crate) fn related(&self, i: usize) -> impl Iterator + '_ { self.adj(i).iter().map(move |e| e.other(i)) } /// The points related to `i` (by a relation or a tie). pub(crate) fn neighbours(&self, i: usize) -> impl Iterator + '_ { let ties = self.ties_of.get(i).into_iter().flatten().flat_map(|t| self.tie_points.get(*t).into_iter().flatten().copied()); self.related(i).chain(ties).filter(move |o| *o != i) } /// The points a tie may now place once `p` is placed, each once: the points of its ties, /// and of the ties reading the direction of a line it ends or of a line whose direction /// derives from one (at most [`MAX_WOKEN_LINES`] lines, [`DEPTH`] deep). pub(crate) fn woken(&self, p: usize) -> Vec { let mut lines: Vec = self.lines_of.get(p).cloned().unwrap_or_default(); let mut start = 0; 'deeper: for _ in 0..DEPTH { let end = lines.len(); for k in start..end { let Some(l) = lines.get(k).copied() else { break }; for d in self.dependents.get(l).into_iter().flatten() { if lines.len() >= MAX_WOKEN_LINES { break 'deeper; } if !lines.contains(d) { lines.push(*d); } } } start = end; } let ties = self.ties_of.get(p).into_iter().flatten().chain(lines.iter().flat_map(|l| self.using.get(*l).into_iter().flatten())); let mut out: Vec = ties.flat_map(|t| self.tie_points.get(*t).into_iter().flatten().copied()).filter(|o| *o != p).collect(); out.sort_unstable(); out.dedup(); out } /// The relations of `t` to points placed in `view`: up to [`MAX_LOCI`] items, relations /// between two points first. pub(crate) fn items(&self, t: usize, view: &View) -> Vec { let mut out = Vec::new(); let t_before = self.before(t); for e in self.adj(t) { if out.len() >= MAX_LOCI { return out; } if !view.oriented && e.rel.along_axis() { continue; } let anchor = e.other(t); let Some(at) = (view.at)(anchor) else { continue }; if let Rel::Same = e.rel { out.push(Item { anchor, shape: Shape::At(at) }); continue; } let side = e.rel.offset_side(t_before, self.before(anchor)); let Some(locus) = e.rel.locus(at, side) else { continue }; let then = self.before(anchor).and_then(|b| e.rel.locus(b, side)); out.push(Item { anchor, shape: Shape::On { locus, then } }); } for ti in self.ties_of.get(t).into_iter().flatten() { if out.len() >= MAX_LOCI { break; } if let Some(item) = self.ties.get(*ti).and_then(|tie| self.tie_item(tie, t, view)) { out.push(item); } } out } /// Does `t` at `x` hold every relation and tie to the points placed in `view`? pub(crate) fn holds(&self, t: usize, x: Vec2, view: &View) -> bool { let pos = |i: usize| if i == t { Some(x) } else { (view.at)(i) }; self.adj(t).iter().all(|e| (view.at)(e.other(t)).is_none_or(|q| e.rel.holds(x, q, 0.0))) && self.ties_of.get(t).into_iter().flatten().filter_map(|ti| self.ties.get(*ti)).all(|tie| self.tie_holds(tie, &pos)) } fn ends(&self, l: usize) -> Option<(usize, usize)> { self.lines.get(l).copied().flatten() } /// Line `l`'s unit direction in the reference layout. fn ref_dir(&self, l: usize) -> Option { direction(self.ends(l), &|i| self.before(i)) } /// Line `l`'s unit direction in `view`: from its placed ends, or derived from its sources /// (at most `depth` lines deep). fn dir(&self, l: usize, view: &View, depth: usize) -> Option { if let Some(d) = direction(self.ends(l), view.at) { return Some(d); } if depth == 0 { return None; } self.sources.get(l)?.iter().find_map(|s| match *s { Src::Fixed(d) => view.oriented.then_some(d), Src::Turn { from, by } => self.dir(from, view, depth - 1).map(|d| turned(d, by)), }) } /// A placed point of line `l` other than `t` (its first end first). fn line_point(&self, l: usize, t: usize, view: &View) -> Option<(usize, Vec2)> { let (a, b) = self.ends(l)?; [a, b].into_iter().filter(|p| *p != t).find_map(|p| (view.at)(p).map(|at| (p, at))) } /// The item tie `tie` gives point `t` from the points placed in `view`. fn tie_item(&self, tie: &Tie, t: usize, view: &View) -> Option { let at = |i: usize| (view.at)(i); let rf = |i: usize| self.before(i); let line = |o: Vec2, d: Vec2| Locus::Line { o, d }.checked(); let circle = |c: Vec2, r: f64| Locus::Circle { c, r }.checked(); let on = |anchor: usize, locus: Option, then: Option| locus.map(|locus| Item { anchor, shape: Shape::On { locus, then } }); let point = |anchor: usize, x: Vec2| in_range(x).then_some(Item { anchor, shape: Shape::At(x) }); match *tie { Tie::OnLine { p, l } => { let (a, b) = self.ends(l)?; // `p` along the line from a placed end, or an end along the line from `p`. let (anchor, o) = if t == p { self.line_point(l, t, view)? } else if t == a || t == b { (p, at(p)?) } else { return None; }; let d = self.dir(l, view, DEPTH)?; on(anchor, line(o, d), rf(anchor).zip(self.ref_dir(l)).and_then(|(o, d)| line(o, d))) } Tie::Offset { p, l, len } => { let (a, b) = self.ends(l)?; // `p` is at `s·v` along the line's left normal from it. let (anchor, o, sign) = if t == p { let (q, o) = self.line_point(l, t, view)?; (q, o, 1.0) } else if t == a || t == b { (p, at(p)?, -1.0) } else { return None; }; let v = len.value(&at)?; let rd = self.ref_dir(l)?; let (pb, ab) = (rf(p)?, rf(a)?); let s = if v == 0.0 { 1.0 } else { side(rd.cross(pb - ab), v, mag(pb).max(mag(ab)))? }; let d = self.dir(l, view, DEPTH)?; let shift = sign * s * v; on(anchor, line(o + d.perp() * shift, d), line(rf(anchor)? + rd.perp() * shift, rd)) } Tie::Normal { p, e, l } => { let anchor = partner(t, p, e)?; let o = at(anchor)?; let d = self.dir(l, view, DEPTH)?.perp(); let then = rf(anchor).zip(self.ref_dir(l)).and_then(|(o, d)| line(o, d.perp())); on(anchor, line(o, d), then) } Tie::Midpoint { p, a, b } => { if t == p { point(a.min(b), (at(a)? + at(b)?) * 0.5) } else { let other = partner(t, a, b)?; point(p.min(other), at(p)? * 2.0 - at(other)?) } } Tie::Mirror { p, q, l } => { let from = partner(t, p, q)?; let x = at(from)?; let (_, o) = self.line_point(l, t, view)?; point(from, reflected(x, o, self.dir(l, view, DEPTH)?)) } Tie::SameLength { p1, q1, p2, q2 } => { let (centre, (x, y)) = match (partner(t, p2, q2), partner(t, p1, q1)) { (Some(c), None) => (c, (p1, q1)), (None, Some(c)) => (c, (p2, q2)), _ => return None, }; let r = at(x)?.dist(at(y)?); on(centre, circle(at(centre)?, r), rf(centre).and_then(|c| circle(c, r))) } Tie::Equidistant { c, a, b } => { if t == c { let (pa, pb) = (at(a)?, at(b)?); let n = (pb - pa).normalized()?.perp(); let then = rf(a).zip(rf(b)).and_then(|(ra, rb)| line((ra + rb) * 0.5, (rb - ra).normalized()?.perp())); on(a.min(b), line((pa + pb) * 0.5, n), then) } else { let other = partner(t, a, b)?; let r = at(other)?.dist(at(c)?); on(c, circle(at(c)?, r), rf(c).and_then(|x| circle(x, r))) } } } } /// Does `tie` hold at the positions `pos` (true when a point it needs is unknown)? fn tie_holds(&self, tie: &Tie, pos: &dyn Fn(usize) -> Option) -> bool { let check = |miss: f64, size: f64, pts: &[Vec2]| within(miss, size, pts.iter().fold(0.0, |m, p| m.max(mag(*p)))); let base = |l: usize| self.ends(l).and_then(|(a, b)| Some((pos(a)?, direction(Some((a, b)), pos)?))); match *tie { Tie::OnLine { p, l } => match (pos(p), base(l)) { (Some(x), Some((a, d))) => check(d.cross(x - a).abs(), 0.0, &[x, a]), _ => true, }, Tie::Offset { p, l, len } => match (pos(p), base(l), len.value(pos)) { (Some(x), Some((a, d)), Some(v)) => check((d.cross(x - a).abs() - v).abs(), v, &[x, a]), _ => true, }, Tie::Normal { p, e, l } => match (pos(p), pos(e), base(l)) { (Some(x), Some(y), Some((_, d))) => check((x - y).dot(d).abs(), x.dist(y), &[x, y]), _ => true, }, Tie::Midpoint { p, a, b } => match (pos(p), pos(a), pos(b)) { (Some(x), Some(a), Some(b)) => check(x.dist((a + b) * 0.5), a.dist(b), &[x, a, b]), _ => true, }, Tie::Mirror { p, q, l } => match (pos(p), pos(q), base(l)) { (Some(x), Some(y), Some((o, d))) => check(y.dist(reflected(x, o, d)), x.dist(o), &[x, y, o]), _ => true, }, Tie::SameLength { p1, q1, p2, q2 } => match (pos(p1), pos(q1), pos(p2), pos(q2)) { (Some(a), Some(b), Some(c), Some(d)) => check((a.dist(b) - c.dist(d)).abs(), a.dist(b), &[a, b, c, d]), _ => true, }, Tie::Equidistant { c, a, b } => match (pos(c), pos(a), pos(b)) { (Some(c), Some(a), Some(b)) => check((a.dist(c) - b.dist(c)).abs(), a.dist(c), &[c, a, b]), _ => true, }, } } }