//! Extend (Surface › Extend): boundary edges of a surface body pushed outward across their
//! face, by a distance, up to a plane or a face, or (natural only) back to the surface's own
//! bounds. Three ways to continue past an edge:
//!
//! - natural: the face's own surface continued and the face bounded again on it. Exact for a
//! plane and a surface of revolution (cylinder, cone, sphere, torus); a B-spline surface's end
//! spans continue as their own polynomials. Each point of the edge moves along the parameter
//! line that leaves the edge square to it (in space), by the arc length asked for.
//! - tangent: a ruled run-off face along the edge, in the surface's tangent plane at each point
//! (tangent-continuous, G1).
//! - curvature: a run-off face that also keeps the surface's normal curvature across the edge
//! (curvature-continuous, G2): each section `C + s·R + ½·κ·s²·n`.
//!
//! The corners: where two extended edges meet, both run out along the bisector of their
//! outward directions (a mitre); next to an edge that is not extended, the extension's side
//! continues that neighbour (it is lengthened to meet the extension), or with `neighbours` off
//! runs straight out square to the edge, as it does where the edges meet smoothly. Along the
//! edge the run-out direction turns evenly between its ends, keeping the distance measured
//! square to the edge. Faces of one body extended at a corner they share use one side edge
//! there when their sides coincide, so the body's boundary stays closed.
//!
//! Refused rather than built: an extension that folds over (a concave edge pushed past its
//! centre of curvature), crosses itself, wraps all the way round its surface or runs into a
//! pole of it; edges between two faces, and edges of holes.
use std::collections::HashMap;
use mt::{BoundedCurve, ParametricCurve, ParametricSurface};
use solidcraft_geom::Vec3;
use solidcraft_geom::tess::EvalSurface;
use truck_modeling as mt;
use crate::body::{Body, from_p3, p3};
use crate::face_params::{FaceLoops, iso_curve, natural_span, parameters};
use crate::record::{Rebuild, Was, rebuilt_body};
use crate::splitface::ToolSide;
use crate::surfaces::{edge_near, surface_body};
use crate::{KernelError, Result, SplitTool, guard};
/// Samples along an edge a fitted curve or run-off starts with, and the most it is refined to.
const START_SAMPLES: usize = 17;
const MAX_SAMPLES: usize = 257;
/// Most edge picks.
const MAX_PICKS: usize = 10_000;
/// The run-out direction may lean at most this far from square to its edge (its cosine; about
/// 84°): further, the extension would run nearly along the edge.
const MIN_COS: f64 = 0.1;
/// Edges whose directions agree this closely where they meet (about 2.6°) meet smoothly.
const SMOOTH_COS: f64 = 0.999;
/// Steps a march along the surface takes over the distance it is asked for, and the most it
/// takes.
const MARCH_STEPS: usize = 64;
const MAX_MARCH_STEPS: usize = 8192;
/// Bisection steps that place where a march meets its target.
const BISECT_STEPS: usize = 60;
/// How a surface continues past its edge.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ExtendMode {
/// The surface itself, continued.
#[default]
Natural,
/// A ruled run-off in the surface's tangent plane (G1).
Tangent,
/// A run-off following the surface's normal curvature across the edge (G2).
Curvature,
}
/// How far an extension goes.
#[derive(Clone, Copy, Debug)]
pub enum ExtendLimit<'a> {
/// This far from the edge, measured across the surface square to it.
Distance(f64),
/// To where it meets a plane, or the surface of a face (extended).
UpTo(&'a SplitTool<'a>),
/// Back to the surface's own bounds (natural only), undoing a trim: a free-form surface's
/// parameter box, an analytic surface's the box of the face's own span.
Bounds,
}
/// What [`extend_surface`] does.
#[derive(Clone, Copy, Debug)]
pub struct ExtendOptions<'a> {
pub mode: ExtendMode,
pub limit: ExtendLimit<'a>,
/// Lengthen the edges next to an extended edge to meet the extension (else its sides run
/// square to the edge).
pub neighbours: bool,
}
/// The boundary edges of `body` picked by points on them, extended as `opts` says. A natural
/// extension rebuilds each face on its continued surface; a run-off adds a face along each
/// edge. The result is a surface body whose history is over `[body]`: an extended face is the
/// face it was (trimmed differently) and its moved edges the edges they were moved from; a
/// run-off face is made from its edge.
pub fn extend_surface(body: &Body, at: &[Vec3], opts: &ExtendOptions) -> Result
{
body.require_brep("extend")?;
if at.is_empty() {
return Err(KernelError::Invalid("pick the edges to extend".into()));
}
if at.len() > MAX_PICKS {
return Err(KernelError::Invalid("too many edges picked".into()));
}
if at.iter().any(|p| !p.is_finite()) {
return Err(KernelError::Invalid("an edge point is not finite".into()));
}
match opts.limit {
ExtendLimit::Distance(d) if !(d.is_finite() && d > 0.0 && d <= 1e6) => {
return Err(KernelError::Invalid("the extension distance must be positive".into()));
}
ExtendLimit::Bounds if opts.mode != ExtendMode::Natural => {
return Err(KernelError::Invalid("only a natural extension goes back to the surface's bounds".into()));
}
_ => {}
}
// A plane's straight edges pushed out with their neighbours lengthened: the exact planar
// extension (what designs saved before the other modes build).
if let (ExtendMode::Natural, ExtendLimit::Distance(d), true) = (opts.mode, opts.limit, opts.neighbours)
&& plain_planar(body)
{
return crate::surfaces::extend(body, at, d);
}
let side = match opts.limit {
ExtendLimit::UpTo(tool) => Some(ToolSide::of(tool)?),
_ => None,
};
guard("extend", || Extension::new(body, opts, side).run(at))
}
/// One planar face, one loop of straight edges: what [`crate::surfaces::extend`] takes.
fn plain_planar(body: &Body) -> bool {
let faces: Vec<&mt::Face> = body.solid.face_iter().collect();
let [face] = faces.as_slice() else { return false };
let bounds = face.boundaries();
let [outer] = bounds.as_slice() else { return false };
matches!(crate::splitface::analytic_field(&face.oriented_surface()), Some(crate::splitface::Analytic::Plane { .. }))
&& outer.edge_iter().all(|e| matches!(e.curve(), mt::Curve::Line(_)))
}
/// A point of an edge with the surface's frame there.
#[derive(Clone, Copy, Debug)]
struct Local {
/// On the edge.
p: Vec3,
/// Its parameters on the surface (unwrapped along the loop).
q: [f64; 2],
/// The surface's unit normal (`S_u × S_v`, as the parameters run).
n: Vec3,
/// The edge's unit direction as the loop runs, in the tangent plane.
d: Vec3,
/// Unit, in the tangent plane, square to the edge, away from the face.
out: Vec3,
su: Vec3,
sv: Vec3,
}
/// Where the extension of a point of an edge ends.
#[derive(Clone, Copy, Debug)]
struct Far {
/// The edge's point and frame.
at: Local,
/// The run-out direction (unit, in the tangent plane) and the run-off's length along it.
r: Vec3,
l: f64,
/// The surface's normal curvature along `r` (curvature mode).
kappa: f64,
/// The far point, and (natural) its parameters.
f: Vec3,
fq: [f64; 2],
}
impl Far {
/// The run-off's section at `s` along it (tangent and curvature modes).
fn section(&self, s: f64) -> Vec3 {
self.at.p + self.r * s + self.at.n * (0.5 * self.kappa * s * s)
}
}
/// A corner of the outer loop (where edge `i − 1` ends and edge `i` starts) next to an
/// extended edge: the direction the extension runs out along there, and the loop index of the
/// neighbouring edge it lengthens (when it does).
#[derive(Clone, Copy, Debug)]
struct Corner {
r: Vec3,
lengthens: Option,
}
/// An extended edge: its loop index, curve range and the run-out angles (from square to the
/// edge, toward its direction) at its ends.
#[derive(Clone, Copy, Debug)]
struct Run {
i: usize,
t0: f64,
t1: f64,
phi0: f64,
phi1: f64,
}
/// A face being extended: its loops in parameters and its outer loop's edges.
struct Frame {
face: mt::Face,
surface: mt::Surface,
loops: FaceLoops,
/// +1 when the outer loop runs counter-clockwise in the parameters, else −1.
ccw: f64,
edges: Vec,
chosen: Vec,
}
impl Frame {
fn new(face: &mt::Face, ids: &[mt::EdgeID], accept: f64) -> Result {
let loops = FaceLoops::of(face, accept, "extending")?;
let outer = loops.wires.get(loops.outer).ok_or_else(|| KernelError::Invalid("the face has no boundary".into()))?;
let edges: Vec = outer.edge_iter().cloned().collect();
let chosen: Vec = edges.iter().map(|e| ids.contains(&e.id())).collect();
if ids.iter().any(|id| !edges.iter().any(|e| e.id() == *id)) {
return Err(KernelError::Invalid("not supported yet: extending an edge of a hole (Untrim takes holes away)".into()));
}
let area = loops.loops.get(loops.outer).map_or(0.0, |l| l.area());
if !(area.abs() > 0.0) {
return Err(KernelError::Invalid("the face has no area on its surface".into()));
}
Ok(Frame { face: face.clone(), surface: face.surface(), ccw: area.signum(), loops, edges, chosen })
}
fn m(&self) -> usize {
self.edges.len()
}
/// The outer loop's edge `i` (as the loop runs) and its samples.
fn samples(&self, i: usize) -> Result<&(mt::Curve, Vec<(f64, Vec3, [f64; 2])>)> {
self.loops.loops.get(self.loops.outer).and_then(|l| l.edges.get(i)).ok_or_else(|| KernelError::Failed("extend: a loop edge".into()))
}
/// The curve range of edge `i` (as the loop runs).
fn range(&self, i: usize) -> Result<(f64, f64)> {
Ok(self.samples(i)?.0.range_tuple())
}
/// The point of edge `i` at curve parameter `t`, with the surface's frame there.
fn local(&self, i: usize, t: f64, accept: f64) -> Result {
let (curve, samples) = self.samples(i)?;
let p = from_p3(curve.subs(t));
let hint = samples.iter().min_by(|a, b| (a.0 - t).abs().total_cmp(&(b.0 - t).abs())).map(|s| s.2);
let q = parameters(&self.loops.eval, &self.loops.dom, p, hint, accept)?;
let (_, su, sv) = self.loops.eval.derivs(q[0], q[1]).ok_or_else(|| KernelError::Failed("extend: the surface at an edge".into()))?;
let n = normal(su, sv)?;
let dc = curve.der(t);
let dc = Vec3::new(dc.x, dc.y, dc.z);
let d = (dc - n * dc.dot(n)).normalized().ok_or_else(|| KernelError::Invalid("an edge to extend has no direction".into()))?;
Ok(Local { p, q, n, d, out: d.cross(n) * self.ccw, su, sv })
}
/// The point of the surface at `q`.
fn at(&self, q: [f64; 2]) -> Result {
self.loops.eval.derivs(q[0], q[1]).map(|x| x.0).ok_or_else(|| KernelError::Failed("extend: the surface can't be evaluated there".into()))
}
fn is_plane(&self) -> bool {
matches!(self.loops.eval, EvalSurface::Plane { .. })
}
}
/// The unit normal from the parameter derivatives; refused at a pole (where it has none).
fn normal(su: Vec3, sv: Vec3) -> Result {
let c = su.cross(sv);
if !(c.len() > 1e-12 * (su.len2() + sv.len2())) {
return Err(KernelError::Invalid("the extension runs into a pole of the surface (a point with no normal)".into()));
}
c.normalized().ok_or_else(|| KernelError::Invalid("the extension runs into a pole of the surface".into()))
}
/// The parameter step `(a, b)` with `a·S_u + b·S_v = w` for `w` in the tangent plane, its
/// near-zero parts snapped to zero (a run along a parameter line stays on it).
fn tangent_coords(su: Vec3, sv: Vec3, w: Vec3) -> Result<[f64; 2]> {
let (e, f, g) = (su.dot(su), su.dot(sv), sv.dot(sv));
let det = e * g - f * f;
if !(det.abs() > 1e-24 * (e * g).max(1e-300)) {
return Err(KernelError::Invalid("the extension runs into a pole of the surface".into()));
}
let (x, y) = (w.dot(su), w.dot(sv));
let mut ab = [(g * x - f * y) / det, (e * y - f * x) / det];
let big = ab[0].abs().max(ab[1].abs());
for c in &mut ab {
if c.abs() <= 1e-12 * big {
*c = 0.0;
}
}
Ok(ab)
}
/// An extension in progress: the body, what to do, and what faces extended at shared corners
/// share.
struct Extension<'a> {
body: &'a Body,
opts: &'a ExtendOptions<'a>,
side: Option,
size: f64,
/// How far a fitted curve or run-off may stray.
tol: f64,
/// How far a boundary point may be from its surface.
accept: f64,
/// Faces using each edge.
users: HashMap,
/// The far corners made at each original vertex, and the side edges run out from it.
corners: HashMap>,
sides: HashMap>,
rec: Rebuild,
}
impl<'a> Extension<'a> {
fn new(body: &'a Body, opts: &'a ExtendOptions<'a>, side: Option) -> Extension<'a> {
let size = body.size().max(1e-6);
let mut users: HashMap = HashMap::new();
for f in body.solid.face_iter() {
for w in f.boundaries().iter() {
for e in w.edge_iter() {
*users.entry(e.id()).or_default() += 1;
}
}
}
Extension {
body,
opts,
side,
size,
tol: (size * 1e-6).max(1e-7),
accept: (size * 1e-5).max(1e-6),
users,
corners: HashMap::new(),
sides: HashMap::new(),
rec: Rebuild::default(),
}
}
fn run(mut self, at: &[Vec3]) -> Result {
let faces: Vec = self.body.solid.face_iter().cloned().collect();
// The picked edges, by the face they bound.
let mut picks: Vec<(usize, Vec)> = Vec::new();
for p in at {
let e = edge_near(self.body, *p).ok_or_else(|| KernelError::Invalid(format!("no edge at {p:?}")))?;
if self.users.get(&e.id()).copied().unwrap_or(0) != 1 {
return Err(KernelError::Invalid("an edge between two faces can't be extended: pick edges on the surface's boundary".into()));
}
let fi = faces
.iter()
.position(|f| f.boundaries().iter().any(|w| w.edge_iter().any(|x| x.id() == e.id())))
.ok_or_else(|| KernelError::Failed("extend: the face of an edge".into()))?;
match picks.iter_mut().find(|(f, _)| *f == fi) {
Some((_, ids)) if !ids.contains(&e.id()) => ids.push(e.id()),
Some(_) => {}
None => picks.push((fi, vec![e.id()])),
}
}
picks.sort_by_key(|(f, _)| *f);
let mut out = faces.clone();
let mut added = Vec::new();
for (fi, ids) in &picks {
let face = faces.get(*fi).ok_or_else(|| KernelError::Failed("extend: a face".into()))?;
let frame = Frame::new(face, ids, self.accept)?;
match self.opts.mode {
ExtendMode::Natural => {
let extended = self.natural(&frame)?;
if let Some(slot) = out.get_mut(*fi) {
*slot = extended;
}
}
ExtendMode::Tangent | ExtendMode::Curvature => added.extend(self.run_offs(&frame)?),
}
}
out.extend(added);
Ok(rebuilt_body(surface_body(out), &[&self.body.solid], &self.rec))
}
/// The corners of the frame's outer loop next to an extended edge (`None` elsewhere).
fn corners_of(&self, fr: &Frame) -> Result>> {
let m = fr.m();
let mut out = Vec::with_capacity(m);
for i in 0..m {
let pi = (i + m - 1) % m;
let (pc, nc) = (fr.chosen.get(pi).copied().unwrap_or(false), fr.chosen.get(i).copied().unwrap_or(false));
if !pc && !nc {
out.push(None);
continue;
}
let prev = fr.local(pi, fr.range(pi)?.1, self.accept)?;
let next = fr.local(i, fr.range(i)?.0, self.accept)?;
let smooth = prev.d.dot(next.d) >= SMOOTH_COS;
let (r, lengthens) = if pc && nc {
let r = (prev.out + next.out)
.normalized()
.ok_or_else(|| KernelError::Invalid("two extended edges turn back on each other at a corner".into()))?;
(r, None)
} else if pc {
if !self.opts.neighbours || smooth { (prev.out, None) } else { (-next.d, Some(i)) }
} else if !self.opts.neighbours || smooth {
(next.out, None)
} else {
(prev.d, Some(pi))
};
for (chosen, l) in [(pc, &prev), (nc, &next)] {
if chosen && r.dot(l.out) < MIN_COS {
return Err(KernelError::Invalid(if lengthens.is_some() {
"an edge next to the extended edge meets it at too sharp an angle to be lengthened (extend without lengthening the neighbouring edges)".into()
} else {
"two extended edges meet at too sharp a corner".into()
}));
}
}
out.push(Some(Corner { r, lengthens }));
}
Ok(out)
}
/// The extended edge `i` of the frame with its run-out angles from the corners at its ends.
fn run_of(&self, fr: &Frame, corners: &[Option], i: usize) -> Result {
let m = fr.m();
let (t0, t1) = fr.range(i)?;
let (Some(Some(c0)), Some(Some(c1))) = (corners.get(i), corners.get((i + 1) % m)) else {
return Err(KernelError::Failed("extend: the corners of an edge".into()));
};
let (a, b) = (fr.local(i, t0, self.accept)?, fr.local(i, t1, self.accept)?);
Ok(Run { i, t0, t1, phi0: c0.r.dot(a.d).atan2(c0.r.dot(a.out)), phi1: c1.r.dot(b.d).atan2(c1.r.dot(b.out)) })
}
/// Where the extension of edge `run` at curve parameter `t` ends.
fn far(&self, fr: &Frame, run: &Run, t: f64) -> Result {
let at = fr.local(run.i, t, self.accept)?;
let w = if run.t1 != run.t0 { (t - run.t0) / (run.t1 - run.t0) } else { 0.0 };
let phi = run.phi0 + (run.phi1 - run.phi0) * w;
let r = at.out * phi.cos() + at.d * phi.sin();
let reach = |cos: f64| -> f64 {
match self.opts.limit {
ExtendLimit::Distance(d) => d / cos.max(MIN_COS),
_ => 0.0,
}
};
let ab = tangent_coords(at.su, at.sv, r)?;
match self.opts.mode {
ExtendMode::Tangent | ExtendMode::Curvature => {
let kappa = if self.opts.mode == ExtendMode::Curvature { normal_curvature(&fr.surface, at.q, ab, at.n) } else { 0.0 };
let mut far = Far { at, r, l: 0.0, kappa, f: at.p, fq: at.q };
far.l = match self.opts.limit {
ExtendLimit::Distance(_) => reach(phi.cos()),
ExtendLimit::UpTo(_) => self.meet(|s| Ok(far.section(s)), at.p)?,
ExtendLimit::Bounds => return Err(KernelError::Invalid("only a natural extension goes back to the surface's bounds".into())),
};
far.f = far.section(far.l);
Ok(far)
}
ExtendMode::Natural => {
let line = |s: f64| [at.q[0] + ab[0] * s, at.q[1] + ab[1] * s];
let s = match self.opts.limit {
ExtendLimit::Distance(_) => arc_param(fr, at.q, ab, reach(phi.cos()))?,
ExtendLimit::UpTo(_) => self.meet(|s| fr.at(line(s)), at.p)?,
ExtendLimit::Bounds => self.to_bounds(fr, at.q, ab)?,
};
let fq = line(s);
let (f, su, sv) =
fr.loops.eval.derivs(fq[0], fq[1]).ok_or_else(|| KernelError::Failed("extend: the surface past its edge".into()))?;
normal(su, sv)?;
Ok(Far { at, r, l: s, kappa: 0.0, f, fq })
}
}
}
/// Where a run `point(s)` (s ≥ 0, from `start`) first crosses the target: stepped out until
/// it changes sides, then bisected; zero from a point on the target. Refused when the run
/// doesn't reach it within a generous length.
fn meet(&self, point: impl Fn(f64) -> Result, start: Vec3) -> Result {
let side = self.side.as_ref().ok_or_else(|| KernelError::Failed("extend: no target".into()))?;
let f0 = side.at(start);
if !f0.is_finite() {
return Err(KernelError::Invalid("the edge's side of the target can't be told".into()));
}
if f0.abs() <= self.tol {
return Ok(0.0);
}
let h = self.size / 16.0;
let reach = self.size * 50.0;
let (mut s, mut prev, mut walked) = (0.0, start, 0.0);
for _ in 0..MAX_MARCH_STEPS {
let next = point(s + h)?;
walked += next.dist(prev);
let f1 = side.at(next);
if f1.is_finite() && f1 * f0 <= 0.0 {
let (mut a, mut b) = (s, s + h);
for _ in 0..BISECT_STEPS {
let m = 0.5 * (a + b);
let fm = side.at(point(m)?);
if fm.is_finite() && fm * f0 > 0.0 { a = m } else { b = m }
}
return Ok(0.5 * (a + b));
}
if walked > reach {
break;
}
(s, prev) = (s + h, next);
}
Err(KernelError::Invalid("the extension does not reach the target".into()))
}
/// How far along the parameter line `q + s·ab` the surface's own bounds are (zero from a
/// point on them).
fn to_bounds(&self, fr: &Frame, q: [f64; 2], ab: [f64; 2]) -> Result {
let outer = fr.loops.loops.get(fr.loops.outer).ok_or_else(|| KernelError::Invalid("the face has no boundary".into()))?;
let analytic = crate::offset::surf_of(&fr.face, (self.size * 1e-7).max(1e-9) * 100.0).is_some();
let span = natural_span(&fr.loops.eval, &fr.loops.dom, outer, analytic, self.accept)?;
let big = ab[0].abs().max(ab[1].abs());
let mut exit = f64::INFINITY;
for (d, (lo, hi)) in span.iter().enumerate() {
let (Some(&x), Some(&dx)) = (q.get(d), ab.get(d)) else { continue };
if dx.abs() <= 1e-9 * big {
continue;
}
let bound = if dx > 0.0 { *hi } else { *lo };
exit = exit.min((bound - x) / dx);
}
if !exit.is_finite() {
return Err(KernelError::Invalid("the extension does not reach its surface's bounds".into()));
}
let scale = (span[0].1 - span[0].0).abs().max((span[1].1 - span[1].0).abs()) / big.max(1e-300);
Ok(if exit > 1e-9 * scale { exit } else { 0.0 })
}
/// The extension's far ends of edge `run` at `n` even curve parameters; refused when none
/// moves (the edge lies on its target already).
fn fars(&self, fr: &Frame, run: &Run, n: usize) -> Result> {
let fars: Vec = (0..n).map(|k| self.far(fr, run, run.t0 + (run.t1 - run.t0) * k as f64 / (n - 1) as f64)).collect::>()?;
if fars.iter().all(|f| !self.moved(f)) {
return Err(KernelError::Invalid(match self.opts.limit {
ExtendLimit::Bounds => "the edge already lies on its surface's bounds".into(),
_ => "the edge already lies on the target".into(),
}));
}
Ok(fars)
}
/// Whether the extension moves the point at all.
fn moved(&self, f: &Far) -> bool {
f.f.dist(f.at.p) > self.tol * 10.0
}
/// The far corner vertex at `v` (an original vertex) for the point `p`: one made there
/// before (by this face or a neighbour) when it is at the same place.
fn corner_vertex(&mut self, v: &mt::Vertex, p: Vec3) -> mt::Vertex {
let near = self.tol * 10.0;
if from_p3(v.point()).dist(p) <= near {
return v.clone();
}
let list = self.corners.entry(v.id()).or_default();
if let Some(found) = list.iter().find(|x| from_p3(x.point()).dist(p) <= near) {
return found.clone();
}
let made = mt::Vertex::new(p3(p));
list.push(made.clone());
made
}
/// The side edge from the original vertex `v` to the far corner `f`, through `mid`: one made
/// before (by this face or a neighbour) along the same path, else `make`'s curve.
fn side_edge(&mut self, v: &mt::Vertex, f: &mt::Vertex, mid: Vec3, make: impl FnOnce() -> Result) -> Result {
let near = self.tol * 10.0;
if let Some(found) = self.sides.get(&v.id()).and_then(|list| {
list.iter().find(|e| {
let c = e.curve();
let (t0, t1) = c.range_tuple();
e.back().id() == f.id() && from_p3(c.subs(0.5 * (t0 + t1))).dist(mid) <= near * 10.0
})
}) {
return Ok(found.clone());
}
let e = mt::Edge::try_new(v, f, make()?).map_err(|e| KernelError::Failed(format!("extend: a side edge: {e}")))?;
self.sides.entry(v.id()).or_default().push(e.clone());
Ok(e)
}
/// The face extended naturally: its surface continued and its outer loop bounded anew.
fn natural(&mut self, fr: &Frame) -> Result {
let m = fr.m();
let corners = self.corners_of(fr)?;
let chosen = |i: usize| fr.chosen.get(i).copied().unwrap_or(false);
let runs: Vec