1use std::collections::{BTreeMap, BTreeSet};
15
16use geop_core_geometry::{
17 intersection::curve_curve_overlaps_and_crossings, nurb_curve::NurbCurve2D, search::MAX_NODES,
18};
19use geop_core_math::{
20 geop_error::{GeopError, GeopResult, WithContext},
21 scalars::Scalar,
22 solvers::{
23 probe::{Probe, attempts},
24 walk::{Walked, walk},
25 },
26 vector::{Vector2, Vector3},
27 with_context,
28};
29use geop_core_topology::Sense;
30use geop_ops_model::Namer;
31
32use crate::sheet::Sheet;
33
34fn min_subdivision_size() -> Scalar {
39 Scalar::from_f64(1e-9)
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum Home {
46 Flat(usize),
47 Bend(usize),
48}
49
50#[derive(Clone, Debug)]
52pub struct LayoutVertex {
53 pub at: Vector2,
55 pub name: Option<Namer>,
58 pub own: Option<(usize, Vector2)>,
61}
62
63#[derive(Clone, Debug)]
65pub struct LayoutEdge {
66 pub curve: NurbCurve2D,
68 pub start: usize,
69 pub end: usize,
70 pub name: Namer,
72 pub home: Home,
73 pub own: Option<NurbCurve2D>,
76 pub origin: Option<String>,
79}
80
81#[derive(Clone, Debug)]
84pub struct LayoutFace {
85 pub home: Home,
86 pub outer: Vec<(usize, Sense)>,
87 pub holes: Vec<Vec<(usize, Sense)>>,
88 pub name: Namer,
90}
91
92impl LayoutFace {
93 pub fn loops(&self) -> impl Iterator<Item = &Vec<(usize, Sense)>> {
94 std::iter::once(&self.outer).chain(&self.holes)
95 }
96}
97
98#[derive(Clone, Debug, Default)]
101pub struct Layout {
102 pub vertices: Vec<LayoutVertex>,
103 pub edges: Vec<LayoutEdge>,
104 pub faces: Vec<LayoutFace>,
105 pub changed: BTreeSet<String>,
107 pub corners: Vec<[usize; 4]>,
111}
112
113#[derive(Clone, Debug)]
116pub struct CutCurve {
117 pub curve: NurbCurve2D,
118 pub name: Namer,
119}
120
121impl Sheet {
122 pub fn layout(&self) -> GeopResult<Layout> {
124 let shifts = self.flat_shifts()?;
125 let mut layout = Layout::default();
126 let mut edge_of: BTreeMap<String, usize> = BTreeMap::new();
127 for (f, flat) in self.flats.iter().enumerate() {
128 let ctx = with_context!("flat {}", flat.name.root());
129 let mut loops = Vec::new();
130 for lp in std::iter::once(&flat.outer).chain(&flat.holes) {
131 let first = layout.vertices.len();
132 let n = lp.len();
133 let mut coedges = Vec::new();
134 for (k, e) in lp.iter().enumerate() {
135 let curve = e.curve.translate(shifts[f]);
136 layout.vertices.push(LayoutVertex {
137 at: curve.start()?,
138 name: Some(e.name.scoped("v")),
139 own: Some((f, e.curve.start()?)),
140 });
141 layout.edges.push(LayoutEdge {
142 curve,
143 start: first + k,
144 end: first + (k + 1) % n,
145 name: e.name.clone(),
146 home: Home::Flat(f),
147 own: Some(e.curve.clone()),
148 origin: Some(e.key()),
149 });
150 let index = layout.edges.len() - 1;
151 if edge_of.insert(e.key(), index).is_some() {
152 return Err(GeopError::new(format!("two edges are named {}", e.key())))
153 .with_context(ctx);
154 }
155 coedges.push((index, Sense::Forward));
156 }
157 loops.push(coedges);
158 }
159 let outer = loops.remove(0);
160 layout.faces.push(LayoutFace {
161 home: Home::Flat(f),
162 outer,
163 holes: loops,
164 name: flat.name.clone(),
165 });
166 }
167 for (b, bend) in self.bends.iter().enumerate() {
168 let edge = |key: &str| {
169 edge_of.get(key).copied().ok_or_else(|| {
170 GeopError::new(format!("bend {} meets no edge {key}", bend.name.root()))
171 })
172 };
173 let (pe, ce) = (edge(&bend.parent_edge)?, edge(&bend.child_edge)?);
174 let (pa, pb) = (layout.edges[pe].start, layout.edges[pe].end);
175 let (cb, ca) = (layout.edges[ce].start, layout.edges[ce].end);
176 layout.corners.push([pa, pb, ca, cb]);
177 for (side, start, end) in [("s0", pa, ca), ("s1", pb, cb)] {
178 let name = bend.name.scoped(side);
179 layout.edges.push(LayoutEdge {
180 curve: NurbCurve2D::line(layout.vertices[start].at, layout.vertices[end].at)?,
181 start,
182 end,
183 origin: Some(name.root()),
184 name,
185 home: Home::Bend(b),
186 own: None,
187 });
188 }
189 let n = layout.edges.len();
190 layout.faces.push(LayoutFace {
191 home: Home::Bend(b),
192 outer: vec![
193 (n - 2, Sense::Forward),
194 (ce, Sense::Reversed),
195 (n - 1, Sense::Reversed),
196 (pe, Sense::Reversed),
197 ],
198 holes: Vec::new(),
199 name: bend.name.clone(),
200 });
201 }
202 for cut in &self.cuts {
203 let ctx = with_context!("cut {}", cut.name.root());
204 let loops = cut
205 .loops
206 .iter()
207 .map(|lp| {
208 lp.iter()
209 .map(|e| {
210 Ok(CutCurve {
211 curve: e.curve.translate(shifts[cut.flat]),
212 name: e.name.clone(),
213 })
214 })
215 .collect::<GeopResult<Vec<_>>>()
216 })
217 .collect::<GeopResult<Vec<_>>>()
218 .with_context(ctx)?;
219 layout.subtract(&loops).with_context(ctx)?;
220 }
221 Ok(layout)
222 }
223}
224
225impl Layout {
226 pub fn used_edges(&self) -> Vec<usize> {
228 let used: BTreeSet<usize> = self
229 .faces
230 .iter()
231 .flat_map(|f| f.loops().flatten().map(|&(e, _)| e))
232 .collect();
233 used.into_iter().collect()
234 }
235
236 pub fn subtract(&mut self, cut: &[Vec<CutCurve>]) -> GeopResult<()> {
244 let used = self.used_edges();
245 let segs: BTreeMap<usize, Seg> = used
246 .iter()
247 .map(|&e| Ok((e, Seg::of(&self.edges[e].curve)?)))
248 .collect::<GeopResult<_>>()?;
249 let cut_segs: Vec<Vec<Seg>> = cut
250 .iter()
251 .map(|lp| lp.iter().map(|c| Seg::of(&c.curve)).collect())
252 .collect::<GeopResult<_>>()?;
253 let regions: Vec<Vec<Vec<Piece<'_>>>> = self
255 .faces
256 .iter()
257 .map(|face| {
258 face.loops()
259 .map(|lp| {
260 lp.iter()
261 .map(|&(e, _)| Piece {
262 seg: &segs[&e],
263 curve: &self.edges[e].curve,
264 })
265 .collect()
266 })
267 .collect()
268 })
269 .collect();
270 let cut_region: Vec<Vec<Piece<'_>>> = cut
271 .iter()
272 .zip(&cut_segs)
273 .map(|(lp, segs)| {
274 lp.iter()
275 .zip(segs)
276 .map(|(c, seg)| Piece {
277 seg,
278 curve: &c.curve,
279 })
280 .collect()
281 })
282 .collect();
283
284 let mut vertices = self.vertices.clone();
288 let joints: Vec<Vec<usize>> = cut
289 .iter()
290 .map(|lp| {
291 lp.iter()
292 .map(|c| {
293 vertices.push(LayoutVertex {
294 at: c.curve.start()?,
295 name: None,
296 own: None,
297 });
298 Ok(vertices.len() - 1)
299 })
300 .collect()
301 })
302 .collect::<GeopResult<_>>()?;
303 let mut on_edge: BTreeMap<usize, Vec<(Scalar, usize)>> = BTreeMap::new();
306 let mut on_cut: Vec<Vec<Vec<(Scalar, usize)>>> =
307 cut.iter().map(|lp| vec![Vec::new(); lp.len()]).collect();
308 for &e in &used {
309 let edge = &self.edges[e];
310 for (l, lp) in cut.iter().enumerate() {
311 for (i, c) in lp.iter().enumerate() {
312 if !edge.curve.could_overlap(&c.curve) {
313 continue;
314 }
315 let found = crossings(&segs[&e], &cut_segs[l][i], &edge.curve, &c.curve)
316 .map_err(|why| {
317 GeopError::new(format!(
318 "the cut's curve {} {why} the edge {}",
319 c.name.root(),
320 edge.name.root()
321 ))
322 })?;
323 for (along_edge, along_cut, at) in found {
324 vertices.push(LayoutVertex {
325 at,
326 name: None,
327 own: None,
328 });
329 on_edge
330 .entry(e)
331 .or_default()
332 .push((along_edge, vertices.len() - 1));
333 on_cut[l][i].push((along_cut, vertices.len() - 1));
334 }
335 }
336 }
337 }
338 for &e in &used {
340 let edge = &self.edges[e];
341 for (l, lp) in cut.iter().enumerate() {
342 for (i, c) in lp.iter().enumerate() {
343 let into = &lp[(i + lp.len() - 1) % lp.len()];
344 let joint = joints[l][i];
345 let along = joint_on(&segs[&e], &vertices[joint].at, &into.curve, &c.curve)
346 .map_err(|why| {
347 GeopError::new(format!(
348 "the cut's curves {} and {} {why} the edge {}, where they meet",
349 into.name.root(),
350 c.name.root(),
351 edge.name.root()
352 ))
353 })?;
354 if let Some(along) = along {
355 on_edge.entry(e).or_default().push((along, joint));
356 }
357 }
358 }
359 }
360
361 let mut edges = self.edges.clone();
363 let mut pieces_of: BTreeMap<usize, Vec<usize>> = BTreeMap::new();
364 let mut changed = self.changed.clone();
365 for (&e, crossed) in &mut on_edge {
366 let edge = &self.edges[e];
367 let points = ordered(crossed, &edge.name)?;
368 let mut stops = vec![edge.start];
369 stops.extend(points);
370 stops.push(edge.end);
371 let ats: Vec<Vector2> = stops.iter().map(|&v| vertices[v].at).collect();
372 let curves = segs[&e].split(&ats)?;
373 let mut pieces = Vec::new();
374 for (k, curve) in curves.into_iter().enumerate() {
375 edges.push(LayoutEdge {
376 curve,
377 start: stops[k],
378 end: stops[k + 1],
379 name: edge.name.scoped(&k.to_string()),
380 home: edge.home,
381 own: None,
382 origin: edge.origin.clone(),
383 });
384 pieces.push(edges.len() - 1);
385 }
386 changed.extend(edge.origin.clone());
387 pieces_of.insert(e, pieces);
388 }
389
390 let mut added: Vec<Vec<usize>> = vec![Vec::new(); self.faces.len()];
394 for (l, lp) in cut.iter().enumerate() {
395 let joints = &joints[l];
396 for (i, c) in lp.iter().enumerate() {
397 let points = ordered(&mut on_cut[l][i], &c.name)?;
398 let mut stops = vec![joints[i]];
399 stops.extend(points);
400 stops.push(joints[(i + 1) % lp.len()]);
401 let ats: Vec<Vector2> = stops.iter().map(|&v| vertices[v].at).collect();
402 let curves = cut_segs[l][i].split(&ats)?;
403 let split = curves.len() > 1;
404 for (k, curve) in curves.into_iter().enumerate() {
405 let seg = Seg::of(&curve)?;
406 let middle = seg.middle(&curve)?;
407 let mut home = None;
408 for (f, region) in regions.iter().enumerate() {
409 if contains(region, &middle).map_err(|why| {
410 GeopError::new(format!(
411 "the cut's curve {} {why} face {}",
412 c.name.root(),
413 self.faces[f].name.root()
414 ))
415 })? {
416 home = Some(f);
417 break;
418 }
419 }
420 let Some(f) = home else {
421 continue;
422 };
423 edges.push(LayoutEdge {
424 curve: curve.reverse(),
425 start: stops[k + 1],
426 end: stops[k],
427 name: if split {
428 c.name.scoped(&k.to_string())
429 } else {
430 c.name.clone()
431 },
432 home: self.faces[f].home,
433 own: None,
434 origin: None,
435 });
436 added[f].push(edges.len() - 1);
437 }
438 }
439 }
440
441 let mut kept: BTreeMap<usize, bool> = BTreeMap::new();
444 let mut faces = Vec::new();
445 let mut any = false;
446 for (f, face) in self.faces.iter().enumerate() {
447 let mut pool: Vec<(usize, Sense)> = Vec::new();
448 let mut touched = !added[f].is_empty();
449 for &(e, sense) in face.loops().flatten() {
450 let pieces = pieces_of.get(&e).cloned().unwrap_or_else(|| vec![e]);
451 touched |= pieces.len() > 1;
452 let ordered: Vec<usize> = match sense {
453 Sense::Forward => pieces,
454 Sense::Reversed => pieces.into_iter().rev().collect(),
455 };
456 for p in ordered {
457 let keep = match kept.get(&p) {
458 Some(&k) => k,
459 None => {
460 let seg = Seg::of(&edges[p].curve)?;
461 let middle = seg.middle(&edges[p].curve)?;
462 let inside = contains(&cut_region, &middle).map_err(|why| {
463 GeopError::new(format!(
464 "the edge {} {why} the cut",
465 edges[p].name.root()
466 ))
467 })?;
468 kept.insert(p, !inside);
469 !inside
470 }
471 };
472 if keep {
473 pool.push((p, sense));
474 } else {
475 touched = true;
476 changed.extend(edges[p].origin.clone());
477 }
478 }
479 }
480 if !touched {
481 faces.push(face.clone());
482 continue;
483 }
484 any = true;
485 pool.extend(added[f].iter().map(|&e| (e, Sense::Forward)));
486 faces.extend(relink(face, &pool, &edges, &vertices)?);
487 }
488 if !any {
489 return Err(GeopError::new(
490 "the cut lies off the sheet: it takes nothing away",
491 ));
492 }
493
494 for face in &faces {
496 for &(e, sense) in face.loops().flatten() {
497 let v = match sense {
498 Sense::Forward => edges[e].start,
499 Sense::Reversed => edges[e].end,
500 };
501 if vertices[v].name.is_none() {
502 vertices[v].name = Some(edges[e].name.scoped("v"));
503 }
504 }
505 }
506 self.vertices = vertices;
507 self.edges = edges;
508 self.faces = faces;
509 self.changed = changed;
510 Ok(())
511 }
512}
513
514fn relink(
519 face: &LayoutFace,
520 pool: &[(usize, Sense)],
521 edges: &[LayoutEdge],
522 vertices: &[LayoutVertex],
523) -> GeopResult<Vec<LayoutFace>> {
524 let ends = |&(e, sense): &(usize, Sense)| match sense {
525 Sense::Forward => (edges[e].start, edges[e].end),
526 Sense::Reversed => (edges[e].end, edges[e].start),
527 };
528 let mut leaving: BTreeMap<usize, usize> = BTreeMap::new();
529 for (k, coedge) in pool.iter().enumerate() {
530 let (from, _) = ends(coedge);
531 if leaving.insert(from, k).is_some() {
532 return Err(GeopError::new(format!(
533 "the cut leaves face {} touching itself at a point",
534 face.name.root()
535 )));
536 }
537 }
538 let mut used = vec![false; pool.len()];
539 let mut outer: Vec<Vec<(usize, Sense)>> = Vec::new();
540 let mut holes = Vec::new();
541 for first in 0..pool.len() {
542 if used[first] {
543 continue;
544 }
545 let around = walk(first, |&k| {
548 let (_, to) = ends(&pool[k]);
549 let next = leaving.get(&to).ok_or_else(|| {
550 GeopError::new(format!(
551 "the cut leaves face {} open at {:?}",
552 face.name.root(),
553 vertices[to].at
554 ))
555 })?;
556 Ok::<_, GeopError>(Some(*next))
557 })?;
558 if around.end != Walked::Closed {
559 return Err(GeopError::new(format!(
560 "the cut leaves face {}'s boundary tangled",
561 face.name.root()
562 )));
563 }
564 for &k in &around.nodes {
565 used[k] = true;
566 }
567 let lp: Vec<(usize, Sense)> = around.nodes.iter().map(|&k| pool[k]).collect();
568 let area = loop_area(&lp, edges)?;
569 if area.definitely_greater(Scalar::ZERO) {
570 outer.push(lp);
571 } else if area.definitely_less(Scalar::ZERO) {
572 holes.push(lp);
573 } else {
574 return Err(GeopError::new(format!(
575 "the cut leaves a sliver of face {}: a loop enclosing {area:?}",
576 face.name.root()
577 )));
578 }
579 }
580 match (outer.len(), face.home) {
581 (0, _) => Err(GeopError::new(format!(
582 "the cut takes face {} away entirely: cut less, or cut before the flange it belongs to",
583 face.name.root()
584 ))),
585 (1, _) => Ok(vec![LayoutFace {
586 home: face.home,
587 outer: outer.remove(0),
588 holes,
589 name: face.name.clone(),
590 }]),
591 (n, Home::Bend(_)) => {
594 let mut pieces: Vec<LayoutFace> = outer
595 .into_iter()
596 .enumerate()
597 .map(|(k, lp)| LayoutFace {
598 home: face.home,
599 outer: lp,
600 holes: Vec::new(),
601 name: face.name.scoped(&k.to_string()),
602 })
603 .collect();
604 for hole in holes {
605 let (e, sense) = hole[0];
606 let probe = vertices[match sense {
607 Sense::Forward => edges[e].start,
608 Sense::Reversed => edges[e].end,
609 }]
610 .at;
611 let mut home = None;
612 for (k, piece) in pieces.iter().enumerate() {
613 let segs = piece
614 .outer
615 .iter()
616 .map(|&(e, _)| Seg::of(&edges[e].curve))
617 .collect::<GeopResult<Vec<_>>>()?;
618 let boundary = vec![
619 piece
620 .outer
621 .iter()
622 .zip(&segs)
623 .map(|(&(e, _), seg)| Piece {
624 seg,
625 curve: &edges[e].curve,
626 })
627 .collect::<Vec<_>>(),
628 ];
629 if contains(&boundary, &probe).map_err(|why| {
630 GeopError::new(format!(
631 "a hole of the cut {why} a piece of face {}",
632 face.name.root()
633 ))
634 })? {
635 home = Some(k);
636 break;
637 }
638 }
639 let k = home.ok_or_else(|| {
640 GeopError::new(format!(
641 "a hole the cut leaves in face {} lies in none of its {n} pieces",
642 face.name.root()
643 ))
644 })?;
645 pieces[k].holes.push(hole);
646 }
647 Ok(pieces)
648 }
649 (n, Home::Flat(_)) => Err(GeopError::new(format!(
650 "the cut splits face {} into {n} pieces: a flat stays in one piece — cut a slot short of its far side",
651 face.name.root()
652 ))),
653 }
654}
655
656fn ordered(crossed: &mut [(Scalar, usize)], name: &Namer) -> GeopResult<Vec<usize>> {
659 crossed.sort_by(|a, b| a.0.to_f64().total_cmp(&b.0.to_f64()));
660 for w in crossed.windows(2) {
661 if !w[1].0.definitely_greater(w[0].0) {
662 return Err(GeopError::new(format!(
663 "the cut crosses {} twice too close together to tell apart",
664 name.root()
665 )));
666 }
667 }
668 Ok(crossed.iter().map(|&(_, v)| v).collect())
669}
670
671fn loop_area(lp: &[(usize, Sense)], edges: &[LayoutEdge]) -> GeopResult<Scalar> {
673 let mut area = Scalar::ZERO;
674 for &(e, sense) in lp {
675 let curve = &edges[e].curve;
676 let swept = Seg::of(curve)?.swept_area(curve)?;
677 area = area.add(match sense {
678 Sense::Forward => swept,
679 Sense::Reversed => swept.neg(),
680 });
681 }
682 Ok(area)
683}
684
685#[derive(Clone, Debug)]
688pub enum Seg {
689 Line {
690 a: Vector2,
691 b: Vector2,
692 },
693 Arc {
695 center: Vector2,
696 radius: Scalar,
697 a: Vector2,
698 b: Vector2,
699 ccw: bool,
700 },
701 Other,
702}
703
704enum Along {
707 Inside(Scalar),
709 Outside,
710 Undecided,
712}
713
714impl Seg {
715 pub fn of(curve: &NurbCurve2D) -> GeopResult<Self> {
717 let (a, b) = (curve.start()?, curve.end()?);
718 if curve.degree == 1 && curve.control_points.len() == 2 {
719 return Ok(Seg::Line { a, b });
720 }
721 let flat = curve.embed(&Vector3::zero(), &Vector3::axis(0), &Vector3::axis(1))?;
722 let Some(arc) = flat.as_arc()? else {
723 return Ok(Seg::Other);
724 };
725 let ccw = arc.circle.normal[2].definitely_greater(Scalar::ZERO);
726 let center = Vector2::from_array([arc.circle.center[0], arc.circle.center[1]]);
727 let sigma = if ccw { Scalar::ONE } else { Scalar::ONE.neg() };
728 let turn = a.sub(¢er).prod_cross(&b.sub(¢er)).mul(sigma);
730 if !turn.definitely_greater(Scalar::ZERO) {
731 return Ok(Seg::Other);
732 }
733 Ok(Seg::Arc {
734 center,
735 radius: arc.circle.radius,
736 a,
737 b,
738 ccw,
739 })
740 }
741
742 fn along(&self, p: &Vector2) -> Along {
744 let (a, b, outside) = match self {
745 Seg::Line { a, b } => (a, b, Scalar::ZERO),
746 Seg::Arc { a, b, ccw, .. } => {
747 let side = b.sub(a).prod_cross(&p.sub(a));
750 (a, b, if *ccw { side } else { side.neg() })
751 }
752 Seg::Other => return Along::Undecided,
753 };
754 if outside.definitely_greater(Scalar::ZERO) {
755 return Along::Outside;
756 }
757 let chord = b.sub(a);
758 let key = p.sub(a).prod_dot(&chord);
759 let length = chord.norm_sq();
760 if key.definitely_less(Scalar::ZERO) || key.definitely_greater(length) {
761 Along::Outside
762 } else if key.definitely_greater(Scalar::ZERO)
763 && key.definitely_less(length)
764 && (matches!(self, Seg::Line { .. }) || outside.definitely_less(Scalar::ZERO))
765 {
766 Along::Inside(key)
767 } else {
768 Along::Undecided
769 }
770 }
771
772 fn ends(&self) -> Vec<Vector2> {
774 match self {
775 Seg::Line { a, b } | Seg::Arc { a, b, .. } => vec![*a, *b],
776 Seg::Other => Vec::new(),
777 }
778 }
779
780 pub fn middle(&self, curve: &NurbCurve2D) -> GeopResult<Vector2> {
782 match self {
783 Seg::Line { a, b } => Ok(a.add(b).prod_scalar(Scalar::ONE.div(Scalar::TWO)?)),
784 Seg::Arc {
785 center,
786 radius,
787 a,
788 b,
789 ..
790 } => {
791 let bisector = a.sub(center).add(&b.sub(center)).normalize()?;
792 Ok(center.add(&bisector.prod_scalar(*radius)))
793 }
794 Seg::Other => curve.evaluate(curve.mid_parameter()?),
795 }
796 }
797
798 fn swept_area(&self, curve: &NurbCurve2D) -> GeopResult<Scalar> {
800 let half = Scalar::ONE.div(Scalar::TWO)?;
801 Ok(match self {
802 Seg::Line { a, b } => a.prod_cross(b).mul(half),
803 Seg::Arc {
804 center,
805 radius,
806 a,
807 b,
808 ccw,
809 } => {
810 let (u, v) = (a.sub(center), b.sub(center));
811 let angle = u.prod_cross(&v).abs().atan2(u.prod_dot(&v));
812 let segment = radius.mul(*radius).mul(angle.sub(angle.sin())).mul(half);
815 let chord = a.prod_cross(b).mul(half);
816 if *ccw {
817 chord.add(segment)
818 } else {
819 chord.sub(segment)
820 }
821 }
822 Seg::Other => curve.area_term(&Vector2::zero())?,
825 })
826 }
827
828 fn split(&self, stops: &[Vector2]) -> GeopResult<Vec<NurbCurve2D>> {
831 stops
832 .windows(2)
833 .map(|w| match self {
834 Seg::Line { .. } => NurbCurve2D::line(w[0], w[1]),
835 Seg::Arc { center, radius, .. } => arc_between(center, *radius, &w[0], &w[1]),
836 Seg::Other => Err(GeopError::new(
837 "a curve that is no line or arc cannot be split",
838 )),
839 })
840 .collect()
841 }
842}
843
844fn arc_between(
849 center: &Vector2,
850 radius: Scalar,
851 p: &Vector2,
852 q: &Vector2,
853) -> GeopResult<NurbCurve2D> {
854 let (u, v) = (p.sub(center), q.sub(center));
855 let r2 = radius.mul(radius);
856 let dot = u.prod_dot(&v);
857 let apex = center.add(&u.add(&v).prod_scalar(r2.div(r2.add(dot))?));
858 let weight = r2.add(dot).div(Scalar::TWO.mul(r2))?.sqrt()?;
859 NurbCurve2D::arc(*p, apex, *q, weight)
860}
861
862fn crossings(
866 e: &Seg,
867 c: &Seg,
868 e_curve: &NurbCurve2D,
869 c_curve: &NurbCurve2D,
870) -> Result<Vec<(Scalar, Scalar, Vector2)>, String> {
871 if matches!(e, Seg::Other) || matches!(c, Seg::Other) {
872 let (overlaps, crossings) = curve_curve_overlaps_and_crossings(
873 e_curve,
874 c_curve,
875 Scalar::ZERO,
876 MAX_NODES,
877 min_subdivision_size(),
878 )
879 .map_err(|err| format!("could not be checked against ({err})"))?;
880 if overlaps.is_empty() && crossings.is_empty() {
881 return Ok(Vec::new());
882 }
883 return Err("meets, and a cut can only cross lines and arcs, not other curves, as".into());
884 }
885 let candidates: Vec<(Vector2, bool)> = match (e, c) {
886 (Seg::Line { a, b }, Seg::Line { a: p, b: q }) => {
887 let (d1, d2) = (b.sub(a), q.sub(p));
888 let den = d1.prod_cross(&d2);
889 if den.could_be_equal(Scalar::ZERO) {
890 if !p.sub(a).prod_cross(&d1).could_be_equal(Scalar::ZERO) {
892 return Ok(Vec::new());
893 }
894 let reach = |x: &Vector2| x.sub(a).prod_dot(&d1);
895 let (lo, hi) = (reach(p).min(reach(q)), reach(p).max(reach(q)));
896 if hi.definitely_less(Scalar::ZERO) || lo.definitely_greater(d1.norm_sq()) {
897 return Ok(Vec::new());
898 }
899 return Err("runs along".into());
900 }
901 let s = p
902 .sub(a)
903 .prod_cross(&d2)
904 .div(den)
905 .map_err(|e| e.to_string())?;
906 vec![(a.add(&d1.prod_scalar(s)), false)]
907 }
908 (Seg::Line { a, b }, Seg::Arc { center, radius, .. })
909 | (Seg::Arc { center, radius, .. }, Seg::Line { a, b }) => {
910 line_circle(a, b, center, *radius).map_err(|e| e.to_string())?
911 }
912 (
913 Seg::Arc {
914 center: c1,
915 radius: r1,
916 ..
917 },
918 Seg::Arc {
919 center: c2,
920 radius: r2,
921 ..
922 },
923 ) => circle_circle(c1, *r1, c2, *r2, e, c)?,
924 _ => unreachable!("other curves are handled above"),
925 };
926 let mut found = Vec::new();
927 for (p, tangent) in candidates {
928 match (e.along(&p), c.along(&p)) {
929 (Along::Outside, _) | (_, Along::Outside) => {}
930 (Along::Inside(ke), Along::Inside(kc)) if !tangent => found.push((ke, kc, p)),
931 (Along::Inside(_), Along::Inside(_)) => return Err("touches".into()),
932 (Along::Inside(_), Along::Undecided)
934 if c.ends().iter().any(|q| q.could_be_equal(&p)) => {}
935 _ => return Err("crosses, at an end of one of them,".into()),
936 }
937 }
938 Ok(found)
939}
940
941fn joint_on(
948 e: &Seg,
949 at: &Vector2,
950 into: &NurbCurve2D,
951 out: &NurbCurve2D,
952) -> Result<Option<Scalar>, String> {
953 let direction = match e {
955 Seg::Line { a, b } => {
956 let d = b.sub(a);
957 if !d.prod_cross(&at.sub(a)).could_be_equal(Scalar::ZERO) {
958 return Ok(None);
959 }
960 d
961 }
962 Seg::Arc { center, radius, .. } => {
963 let r = at.sub(center);
964 if !r.norm_sq().could_be_equal(radius.mul(*radius)) {
965 return Ok(None);
966 }
967 r.perp()
968 }
969 Seg::Other => return Ok(None),
970 };
971 let along = match e.along(at) {
972 Along::Outside => return Ok(None),
973 Along::Inside(along) => along,
974 Along::Undecided => return Err("meet, at an end of".into()),
975 };
976 let tangent = |curve: &NurbCurve2D, t: Scalar| curve.tangent(t).map_err(|e| e.to_string());
979 let side_in = direction.prod_cross(&tangent(into, into.domain().1)?);
980 let side_out = direction.prod_cross(&tangent(out, out.domain().0)?);
981 let zero = Scalar::ZERO;
982 if (side_in.definitely_greater(zero) && side_out.definitely_greater(zero))
983 || (side_in.definitely_less(zero) && side_out.definitely_less(zero))
984 {
985 Ok(Some(along))
986 } else {
987 Err("touch".into())
988 }
989}
990
991fn line_circle(
994 a: &Vector2,
995 b: &Vector2,
996 center: &Vector2,
997 radius: Scalar,
998) -> GeopResult<Vec<(Vector2, bool)>> {
999 let d = b.sub(a);
1000 let w = a.sub(center);
1001 let qa = d.norm_sq();
1002 let qb = d.prod_dot(&w);
1003 let qc = w.norm_sq().sub(radius.mul(radius));
1004 let disc = qb.mul(qb).sub(qa.mul(qc));
1006 if disc.definitely_less(Scalar::ZERO) {
1007 return Ok(Vec::new());
1008 }
1009 if disc.could_be_equal(Scalar::ZERO) {
1010 let s = qb.neg().div(qa)?;
1011 return Ok(vec![(a.add(&d.prod_scalar(s)), true)]);
1012 }
1013 let root = disc.sqrt()?;
1014 [qb.neg().sub(root), qb.neg().add(root)]
1015 .into_iter()
1016 .map(|num| Ok((a.add(&d.prod_scalar(num.div(qa)?)), false)))
1017 .collect()
1018}
1019
1020fn circle_circle(
1023 c1: &Vector2,
1024 r1: Scalar,
1025 c2: &Vector2,
1026 r2: Scalar,
1027 e: &Seg,
1028 c: &Seg,
1029) -> Result<Vec<(Vector2, bool)>, String> {
1030 let d = c2.sub(c1);
1031 let d2 = d.norm_sq();
1032 if d2.could_be_equal(Scalar::ZERO) {
1033 if !r1.could_be_equal(r2) {
1034 return Ok(Vec::new());
1035 }
1036 let apart = c
1038 .ends()
1039 .iter()
1040 .all(|p| matches!(e.along(p), Along::Outside))
1041 && e.ends()
1042 .iter()
1043 .all(|p| matches!(c.along(p), Along::Outside));
1044 return if apart {
1045 Ok(Vec::new())
1046 } else {
1047 Err("runs along".into())
1048 };
1049 }
1050 let err = |e: GeopError| e.to_string();
1051 let k = r1
1053 .mul(r1)
1054 .sub(r2.mul(r2))
1055 .add(d2)
1056 .div(Scalar::TWO.mul(d2))
1057 .map_err(err)?;
1058 let h2 = r1.mul(r1).div(d2).map_err(err)?.sub(k.mul(k));
1059 if h2.definitely_less(Scalar::ZERO) {
1060 return Ok(Vec::new());
1061 }
1062 let base = c1.add(&d.prod_scalar(k));
1063 let across = d.perp();
1064 if h2.could_be_equal(Scalar::ZERO) {
1065 return Ok(vec![(base, true)]);
1066 }
1067 let h = h2.sqrt().map_err(err)?;
1068 Ok(vec![
1069 (base.add(&across.prod_scalar(h)), false),
1070 (base.sub(&across.prod_scalar(h)), false),
1071 ])
1072}
1073
1074struct Piece<'a> {
1076 seg: &'a Seg,
1077 curve: &'a NurbCurve2D,
1078}
1079
1080fn ray_direction(k: usize) -> Vector2 {
1084 let angle = 0.7 + 2.399_963_229_728_653 * k as f64;
1085 Vector2::from_array([Scalar::from_f64(angle.cos()), Scalar::from_f64(angle.sin())])
1086}
1087
1088const RAY_ATTEMPTS: usize = 16;
1090
1091fn contains(loops: &[Vec<Piece<'_>>], p: &Vector2) -> Result<bool, String> {
1095 let reach = loops
1096 .iter()
1097 .flatten()
1098 .map(|piece| piece.curve.aabb())
1099 .map(|b| {
1100 [0, 1]
1101 .map(|k| b[k].sub(p[k]).abs().upper().to_f64())
1102 .iter()
1103 .sum::<f64>()
1104 })
1105 .fold(1.0, f64::max);
1106 let found = attempts(RAY_ATTEMPTS, |&k| -> Result<_, String> {
1108 let d = ray_direction(k);
1109 let mut count = 0usize;
1110 for piece in loops.iter().flatten() {
1111 match ray_crossings(piece, p, &d, reach) {
1112 Some(n) => count += n,
1113 None => return Ok(Probe::Next),
1114 }
1115 }
1116 Ok(Probe::Found(count % 2 == 1))
1117 })?;
1118 found.ok_or_else(|| "cannot be told inside or outside, lying on the boundary of,".into())
1119}
1120
1121fn ray_crossings(piece: &Piece<'_>, p: &Vector2, d: &Vector2, reach: f64) -> Option<usize> {
1124 let ahead = |q: &Vector2| -> Option<bool> {
1125 let t = q.sub(p).prod_dot(d);
1126 if t.definitely_greater(Scalar::ZERO) {
1127 Some(true)
1128 } else if t.definitely_less(Scalar::ZERO) {
1129 Some(false)
1130 } else {
1131 None
1132 }
1133 };
1134 let far = p.add(&d.prod_scalar(Scalar::from_f64(2.0 * reach)));
1135 let points: Vec<(Vector2, bool)> = match piece.seg {
1136 Seg::Line { a, b } => {
1137 let e = b.sub(a);
1138 let den = d.prod_cross(&e);
1139 if den.could_be_equal(Scalar::ZERO) {
1140 return if a.sub(p).prod_cross(d).could_be_equal(Scalar::ZERO) {
1141 None
1142 } else {
1143 Some(0)
1144 };
1145 }
1146 let s = p.sub(a).prod_cross(d).div(den).ok()?.neg();
1147 vec![(a.add(&e.prod_scalar(s)), false)]
1148 }
1149 Seg::Arc { center, radius, .. } => line_circle(p, &far, center, *radius).ok()?,
1150 Seg::Other => {
1151 let ray = NurbCurve2D::line(*p, far).ok()?;
1152 let (overlaps, crossings) = curve_curve_overlaps_and_crossings(
1153 &ray,
1154 piece.curve,
1155 Scalar::ZERO,
1156 MAX_NODES,
1157 min_subdivision_size(),
1158 )
1159 .ok()?;
1160 if !overlaps.is_empty() {
1161 return None;
1162 }
1163 let (t0, t1) = piece.curve.domain();
1164 let mut n = 0;
1165 for (_, t) in crossings {
1166 if t.could_be_equal(t0) || t.could_be_equal(t1) {
1167 return None;
1168 }
1169 n += 1;
1170 }
1171 return Some(n);
1172 }
1173 };
1174 let mut n = 0;
1175 for (q, tangent) in points {
1176 match (piece.seg.along(&q), ahead(&q)) {
1177 (Along::Outside, _) | (_, Some(false)) => {}
1178 (Along::Inside(_), Some(true)) if !tangent => n += 1,
1179 _ => return None,
1180 }
1181 }
1182 Some(n)
1183}