1use crate::spatial::{Aabb, Bvh};
2use crate::Vec3;
3use rustc_hash::FxHashMap;
4use serde::{Deserialize, Serialize};
5
6const PARALLEL_THRESHOLD: f64 = 1e-12;
7
8#[derive(Clone, Copy, Debug, Deserialize, Serialize)]
9pub struct Vec2 {
10 pub x: f64,
11 pub y: f64,
12}
13
14impl Vec2 {
15 #[inline]
17 pub fn distance_to_segment(self, start: Self, end: Self, guard: f64) -> f64 {
18 let segment = end.sub(start);
19 let length_squared = segment.dot(segment);
20 if length_squared <= guard {
21 return self.sub(start).length();
22 }
23 let parameter = (self.sub(start).dot(segment) / length_squared).clamp(0.0, 1.0);
24 self.sub(start.add(segment.scale(parameter))).length()
25 }
26
27 #[inline]
29 pub fn distance_to_segment_squared(self, start: Self, end: Self) -> f64 {
30 let segment = end.sub(start);
31 let length_squared = segment.dot(segment);
32 let parameter = if length_squared > 0.0 {
33 (self.sub(start).dot(segment) / length_squared).clamp(0.0, 1.0)
34 } else {
35 0.0
36 };
37 let residual = self.sub(start.add(segment.scale(parameter)));
38 residual.dot(residual)
39 }
40
41 pub(crate) fn sub(self, other: Self) -> Self {
42 Self {
43 x: self.x - other.x,
44 y: self.y - other.y,
45 }
46 }
47 pub(crate) fn add(self, other: Self) -> Self {
48 Self {
49 x: self.x + other.x,
50 y: self.y + other.y,
51 }
52 }
53 pub(crate) fn scale(self, factor: f64) -> Self {
54 Self {
55 x: self.x * factor,
56 y: self.y * factor,
57 }
58 }
59 fn distance(self, other: Self) -> f64 {
60 self.sub(other).length()
61 }
62 pub(crate) fn length(self) -> f64 {
63 (self.x * self.x + self.y * self.y).sqrt()
64 }
65 fn cross(self, other: Self) -> f64 {
66 self.x * other.y - self.y * other.x
67 }
68 pub(crate) fn dot(self, other: Self) -> f64 {
69 self.x * other.x + self.y * other.y
70 }
71}
72
73#[derive(Clone, Debug, Deserialize, Serialize)]
74pub struct Segment2 {
75 pub a: Vec2,
76 pub b: Vec2,
77 #[serde(default)]
78 pub tag: serde_json::Value,
79}
80
81#[derive(Clone, Debug, Serialize)]
82pub struct ArrangementPiece {
83 pub a: Vec2,
84 pub b: Vec2,
85 pub parent: Segment2,
86}
87
88#[derive(Clone, Debug, Serialize)]
89pub struct CycleUse {
90 pub piece: ArrangementPiece,
91 pub forward: bool,
92}
93
94#[derive(Clone, Debug, Serialize)]
95pub struct ArrangementRegion {
96 pub outer: Vec<CycleUse>,
97 pub holes: Vec<Vec<CycleUse>>,
98 pub area: f64,
99 pub outer_area: f64,
102}
103
104#[derive(Clone)]
105struct Node {
106 point: Vec2,
107 outgoing: Vec<usize>,
108}
109
110struct NodeLookup {
114 tolerance: f64,
115 width: f64,
116 cells: Option<FxHashMap<[i64; 2], Vec<usize>>>,
117}
118
119impl NodeLookup {
120 fn new(tolerance: f64, indexed: bool) -> Self {
121 let width = tolerance * 2.0;
122 Self {
123 tolerance,
124 width,
125 cells: (indexed && tolerance >= 1e-150 && width.is_finite()).then(FxHashMap::default),
128 }
129 }
130
131 fn key(&self, point: Vec2) -> Option<[i64; 2]> {
132 let q = [point.x / self.width, point.y / self.width];
133 q.iter()
137 .all(|v| v.is_finite() && v.abs() <= (1u64 << 48) as f64)
138 .then(|| q.map(|v| v.floor() as i64))
139 }
140
141 fn find_or_insert(&mut self, point: Vec2, nodes: &mut Vec<Node>) -> usize {
142 let key = self.cells.as_ref().and_then(|_| self.key(point));
143 if key.is_none() {
144 self.cells = None;
147 }
148 let found = if let (Some(cells), Some([x, y])) = (&self.cells, key) {
149 let mut first = None;
150 for dx in -1..=1 {
151 for dy in -1..=1 {
152 if let Some(indices) = cells.get(&[x + dx, y + dy]) {
153 for &index in indices {
156 if first.is_some_and(|first| index >= first) {
157 break;
158 }
159 if nodes[index].point.distance(point) <= self.tolerance {
160 first = Some(index);
161 break;
162 }
163 }
164 }
165 }
166 }
167 first
168 } else {
169 nodes
170 .iter()
171 .position(|node| node.point.distance(point) <= self.tolerance)
172 };
173 if let Some(index) = found {
174 return index;
175 }
176 let index = nodes.len();
177 nodes.push(Node {
178 point,
179 outgoing: Vec::new(),
180 });
181 if let (Some(cells), Some(key)) = (&mut self.cells, key) {
182 cells.entry(key).or_default().push(index);
183 }
184 index
185 }
186}
187
188fn tolerance_parameter(segment: &Segment2, tolerance: f64) -> f64 {
189 let length = segment.a.distance(segment.b);
190 if length <= tolerance {
191 0.5
192 } else {
193 tolerance / length
194 }
195}
196
197fn interpolate(a: Vec2, b: Vec2, parameter: f64) -> Vec2 {
198 Vec2 {
199 x: a.x + (b.x - a.x) * parameter,
200 y: a.y + (b.y - a.y) * parameter,
201 }
202}
203
204pub fn segment_intersection(
205 a1: Vec2,
206 b1: Vec2,
207 a2: Vec2,
208 b2: Vec2,
209 tolerance: f64,
210) -> Option<[f64; 2]> {
211 let d1 = b1.sub(a1);
212 let d2 = b2.sub(a2);
213 let denominator = d1.cross(d2);
214 let length1 = d1.length();
215 let length2 = d2.length();
216 if denominator.abs() <= PARALLEL_THRESHOLD * length1 * length2 {
217 return None;
218 }
219 let offset = a2.sub(a1);
220 let t1 = offset.cross(d2) / denominator;
221 let t2 = offset.cross(d1) / denominator;
222 let epsilon1 = tolerance / length1.max(tolerance);
223 let epsilon2 = tolerance / length2.max(tolerance);
224 if t1 < -epsilon1 || t1 > 1.0 + epsilon1 || t2 < -epsilon2 || t2 > 1.0 + epsilon2 {
225 None
226 } else {
227 Some([t1.clamp(0.0, 1.0), t2.clamp(0.0, 1.0)])
228 }
229}
230
231fn end_on_segment_parameters(end: &Segment2, bar: &Segment2, tolerance: f64) -> Vec<(Vec2, f64)> {
243 let (stem, span) = (end.b.sub(end.a), bar.b.sub(bar.a));
244 let squared = span.dot(span);
245 if squared <= 0.0 || stem.cross(span).abs() <= PARALLEL_THRESHOLD * stem.length() * span.length() {
246 return Vec::new();
247 }
248 let margin = tolerance_parameter(bar, tolerance);
249 [end.a, end.b]
250 .into_iter()
251 .filter_map(|point| {
252 let parameter = point.sub(bar.a).dot(span) / squared;
253 (parameter > margin
254 && parameter < 1.0 - margin
255 && point_segment_distance(point, bar.a, bar.b) <= tolerance)
256 .then_some((point, parameter))
257 })
258 .collect()
259}
260
261pub use crate::polygon::PolygonClass;
262
263fn point_segment_distance(point: Vec2, a: Vec2, b: Vec2) -> f64 {
264 point.distance_to_segment(a, b, 1e-300)
265}
266
267fn polygon_reject_box(polygon: &[Vec2], tolerance: f64) -> [f64; 5] {
281 let mut bounds = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
282 let mut magnitude = 1.0f64;
283 for point in polygon {
284 bounds[0] = bounds[0].min(point.x);
285 bounds[1] = bounds[1].min(point.y);
286 bounds[2] = bounds[2].max(point.x);
287 bounds[3] = bounds[3].max(point.y);
288 magnitude = magnitude.max(point.x.abs()).max(point.y.abs());
289 }
290 let finite = !polygon.is_empty() && bounds.iter().all(|value| value.is_finite()) && tolerance.is_finite() && tolerance >= 0.0;
291 let reach = if finite { tolerance * (1.0 + 1e-9) + 64.0 * f64::EPSILON * magnitude } else { f64::NAN };
292 [bounds[0], bounds[1], bounds[2], bounds[3], reach]
293}
294
295struct BoundaryBand {
307 origin: [f64; 2],
308 cell: [f64; 2],
309 side: usize,
310 reach: f64,
311 starts: Vec<u32>,
312 items: Vec<u32>,
313 usable: bool,
314}
315
316impl BoundaryBand {
317 fn new(polygon: &[Vec2], tolerance: f64) -> Self {
318 let reject = polygon_reject_box(polygon, tolerance);
319 let reach = reject[4];
320 let count = polygon.len();
321 let usable = reach.is_finite() && count >= 16 && count <= (1 << 24);
322 let side = if usable { ((count as f64).sqrt() as usize).clamp(4, 512) } else { 0 };
323 let origin = [reject[0] - reach, reject[1] - reach];
324 let extent = [(reject[2] + reach) - origin[0], (reject[3] + reach) - origin[1]];
325 let cell = [(extent[0] / side.max(1) as f64).max(f64::MIN_POSITIVE), (extent[1] / side.max(1) as f64).max(f64::MIN_POSITIVE)];
326 let mut band = Self { origin, cell, side, reach, starts: Vec::new(), items: Vec::new(), usable };
327 if !band.usable {
328 return band;
329 }
330 let mut counts = vec![0u32; side * side + 1];
331 let ranges: Vec<[usize; 4]> = (0..count)
332 .map(|index| {
333 let (a, b) = (polygon[index], polygon[(index + 1) % count]);
334 let pad = reach + 64.0 * f64::EPSILON * a.x.abs().max(a.y.abs()).max(b.x.abs()).max(b.y.abs());
335 [band.column(a.x.min(b.x) - pad), band.column(a.x.max(b.x) + pad), band.row(a.y.min(b.y) - pad), band.row(a.y.max(b.y) + pad)]
336 })
337 .collect();
338 for range in &ranges {
339 for y in range[2]..=range[3] {
340 for x in range[0]..=range[1] {
341 counts[y * side + x + 1] += 1;
342 }
343 }
344 }
345 for index in 1..counts.len() {
346 counts[index] += counts[index - 1];
347 }
348 if counts[side * side] as usize > 64 * count + 4096 {
350 band.usable = false;
351 return band;
352 }
353 let mut cursor = counts.clone();
354 band.items = vec![0u32; counts[side * side] as usize];
355 for (edge, range) in ranges.iter().enumerate() {
356 for y in range[2]..=range[3] {
357 for x in range[0]..=range[1] {
358 let slot = &mut cursor[y * side + x];
359 band.items[*slot as usize] = edge as u32;
360 *slot += 1;
361 }
362 }
363 }
364 band.starts = counts;
365 band
366 }
367 fn column(&self, x: f64) -> usize {
368 (((x - self.origin[0]) / self.cell[0]).floor().max(0.0) as usize).min(self.side - 1)
369 }
370 fn row(&self, y: f64) -> usize {
371 (((y - self.origin[1]) / self.cell[1]).floor().max(0.0) as usize).min(self.side - 1)
372 }
373 fn classify(&self, point: Vec2, polygon: &[Vec2], tolerance: f64) -> PolygonClass {
374 if !self.usable || !point.x.is_finite() || !point.y.is_finite() {
375 return point_in_polygon_class(point, polygon, tolerance);
376 }
377 let cell = self.row(point.y) * self.side + self.column(point.x);
378 let count = polygon.len();
379 for &edge in &self.items[self.starts[cell] as usize..self.starts[cell + 1] as usize] {
380 let edge = edge as usize;
381 if point.distance_to_segment(polygon[edge], polygon[(edge + 1) % count], 1e-300) <= tolerance {
382 return PolygonClass::Boundary;
383 }
384 }
385 if crate::polygon::parity_inside([point.x, point.y], count, |i| [polygon[i].x, polygon[i].y]) {
386 PolygonClass::Inside
387 } else {
388 PolygonClass::Outside
389 }
390 }
391}
392
393fn outside_reject_box(point: Vec2, reject: &[f64; 5]) -> bool {
394 let reach = reject[4] + 64.0 * f64::EPSILON * point.x.abs().max(point.y.abs());
395 point.x < reject[0] - reach || point.y < reject[1] - reach || point.x > reject[2] + reach || point.y > reject[3] + reach
397}
398
399fn digon_order_enabled() -> bool {
402 switch!("BREP_ARRANGE_DIGON_ORDER", true)
403}
404
405fn hole_box_reject_enabled() -> bool {
406 switch!("BREP_ARRANGE_HOLE_BOX", true)
407}
408
409
410fn point_in_polygon_class(point: Vec2, polygon: &[Vec2], tolerance: f64) -> PolygonClass {
411 crate::polygon::classify(point, polygon, tolerance, 1e-300)
412}
413
414pub fn point_in_polygon(point: Vec2, polygon: &[Vec2], tolerance: f64) -> &'static str {
415 match point_in_polygon_class(point, polygon, tolerance) {
416 PolygonClass::Inside => "in",
417 PolygonClass::Outside => "out",
418 PolygonClass::Boundary => "boundary",
419 }
420}
421
422struct PieceInput<'a> {
426 a: Vec2,
427 b: Vec2,
428 parent: std::borrow::Cow<'a, Segment2>,
429}
430impl PieceInput<'_> {
431 fn materialize(&self, forward: bool) -> CycleUse {
432 CycleUse { piece: ArrangementPiece { a: self.a, b: self.b, parent: self.parent.as_ref().clone() }, forward }
433 }
434}
435
436#[derive(Clone)]
437enum CycleUses {
438 Eager(Vec<CycleUse>),
439 Deferred(Vec<usize>),
440}
441impl CycleUses {
442 fn push(&mut self, half: usize, pieces: &[PieceInput<'_>]) {
443 match self {
444 Self::Eager(uses) => uses.push(pieces[half >> 1].materialize(half % 2 == 0)),
445 Self::Deferred(halves) => halves.push(half),
446 }
447 }
448 fn into_uses(self, pieces: &[PieceInput<'_>]) -> Vec<CycleUse> {
449 match self {
450 Self::Eager(uses) => uses,
451 Self::Deferred(halves) => halves.into_iter().map(|h| pieces[h >> 1].materialize(h % 2 == 0)).collect(),
452 }
453 }
454 fn copy_uses(&self, pieces: &[PieceInput<'_>]) -> Vec<CycleUse> {
455 match self {
456 Self::Eager(uses) => uses.clone(),
457 Self::Deferred(halves) => halves.iter().map(|&h| pieces[h >> 1].materialize(h % 2 == 0)).collect(),
458 }
459 }
460}
461
462#[derive(Clone)]
463struct Cycle {
464 uses: CycleUses,
465 area: f64,
466 node_indices: Vec<usize>,
467}
468
469fn visit_segment_pairs(segments: &[Segment2], tolerance: f64, mut visit: impl FnMut(usize, usize)) {
472 let all_pairs = |visit: &mut dyn FnMut(usize, usize)| {
473 for first in 0..segments.len() {
474 for second in first + 1..segments.len() {
475 visit(first, second);
476 }
477 }
478 };
479 if segments.len() <= 32
480 || segments.len() > u32::MAX as usize
481 || !tolerance.is_finite()
482 || tolerance < 0.0
483 || tolerance > 1e100
484 {
485 all_pairs(&mut visit);
486 return;
487 }
488 let mut scale = 0.0f64;
489 let mut boxes = Vec::with_capacity(segments.len());
490 let mut extent = Aabb::empty();
491 for segment in segments {
492 let coordinates = [segment.a.x, segment.a.y, segment.b.x, segment.b.y];
493 let length = segment.a.distance(segment.b);
494 if coordinates
497 .iter()
498 .any(|v| !v.is_finite() || v.abs() > 1e100)
499 || !(1e-100..=1e100).contains(&length)
500 {
501 all_pairs(&mut visit);
502 return;
503 }
504 for coordinate in coordinates {
505 scale = scale.max(coordinate.abs());
506 }
507 let bounds = Aabb {
508 minimum: Vec3::new(
509 segment.a.x.min(segment.b.x),
510 segment.a.y.min(segment.b.y),
511 0.0,
512 ),
513 maximum: Vec3::new(
514 segment.a.x.max(segment.b.x),
515 segment.a.y.max(segment.b.y),
516 0.0,
517 ),
518 };
519 extent.include(bounds);
520 boxes.push(bounds);
521 }
522 let padding = 2.0 * tolerance + 128.0 * f64::EPSILON / PARALLEL_THRESHOLD * scale;
530 if padding >= extent.diagonal() {
533 all_pairs(&mut visit);
534 return;
535 }
536 let direction_tiers = direction_index_on();
537 let small_padding = 2.0 * tolerance + 128.0 * f64::EPSILON / DIRECTION_SINE * scale;
541 let swept = if direction_tiers && arrange_sweep_on() { swept_pairs(&boxes, small_padding) } else { None };
542 let tree = swept.is_none().then(|| Bvh::build(&boxes));
543 let mut candidates = Vec::new();
544 if direction_tiers {
545 let width = 2.0 * DIRECTION_SINE;
560 let bucket_count = ((std::f64::consts::PI / width).ceil() as usize).max(1);
561 let mut buckets: FxHashMap<usize, Vec<usize>> = FxHashMap::default();
562 let bucket_of: Vec<usize> = segments
563 .iter()
564 .map(|segment| {
565 let angle = (segment.b.y - segment.a.y).atan2(segment.b.x - segment.a.x).rem_euclid(std::f64::consts::PI);
566 ((angle / width) as usize).min(bucket_count - 1)
567 })
568 .collect();
569 for (index, bucket) in bucket_of.iter().enumerate() {
570 buckets.entry(*bucket).or_default().push(index);
571 }
572 let near = |a: &Aabb, b: &Aabb| {
573 a.minimum.x - padding <= b.maximum.x
574 && b.minimum.x - padding <= a.maximum.x
575 && a.minimum.y - padding <= b.maximum.y
576 && b.minimum.y - padding <= a.maximum.y
577 };
578 for (first, &bounds) in boxes.iter().enumerate() {
579 candidates.clear();
580 match (&swept, &tree) {
581 (Some(lists), _) => candidates.extend_from_slice(&lists[first]),
582 (None, Some(tree)) => tree.overlapping(bounds, small_padding, &mut candidates),
583 (None, None) => unreachable!("the tree is built whenever the sweep is not"),
584 }
585 let bucket = bucket_of[first];
586 for neighbour in [(bucket + bucket_count - 1) % bucket_count, bucket, (bucket + 1) % bucket_count] {
587 if let Some(members) = buckets.get(&neighbour) {
588 candidates.extend(members.iter().copied().filter(|&second| second > first && near(&bounds, &boxes[second])));
589 }
590 if bucket_count < 3 {
591 break;
592 }
593 }
594 candidates.retain(|&second| second > first);
595 candidates.sort_unstable();
596 candidates.dedup();
597 for &second in &candidates {
598 visit(first, second);
599 }
600 }
601 return;
602 }
603 let Some(tree) = tree else { unreachable!("the sweep runs only under the direction tiers") };
604 for (first, &bounds) in boxes.iter().enumerate() {
605 candidates.clear();
606 tree.overlapping(bounds, padding, &mut candidates);
607 candidates.retain(|&second| second > first);
608 candidates.sort_unstable();
609 for &second in &candidates {
610 visit(first, second);
611 }
612 }
613}
614
615const DIRECTION_SINE: f64 = 1e-3;
618
619fn swept_pairs(boxes: &[Aabb], padding: f64) -> Option<Vec<Vec<usize>>> {
639 let reach = 2.0 * padding;
640 let low = |index: usize, axis: usize| if axis == 0 { boxes[index].minimum.x } else { boxes[index].minimum.y };
641 let high = |index: usize, axis: usize| if axis == 0 { boxes[index].maximum.x } else { boxes[index].maximum.y };
642 let mut best: Option<(usize, Vec<usize>, usize)> = None;
643 for axis in [0, 1] {
644 let mut order: Vec<usize> = (0..boxes.len()).collect();
645 order.sort_by(|&a, &b| low(a, axis).total_cmp(&low(b, axis)));
646 let lows: Vec<f64> = order.iter().map(|&index| low(index, axis)).collect();
647 let windows: usize = order
648 .iter()
649 .enumerate()
650 .map(|(position, &a)| {
651 let limit = high(a, axis) + reach;
652 lows.partition_point(|&value| value <= limit).saturating_sub(position + 1)
653 })
654 .sum();
655 if best.as_ref().is_none_or(|(least, _, _)| windows < *least) {
656 best = Some((windows, order, axis));
657 }
658 }
659 let (windows, order, axis) = best?;
660 if windows > 32 * boxes.len() {
661 return None;
662 }
663 let mut lists = vec![Vec::new(); boxes.len()];
664 for (position, &a) in order.iter().enumerate() {
665 let limit = high(a, axis) + reach;
666 for &b in &order[position + 1..] {
667 if low(b, axis) > limit {
668 break;
669 }
670 let (first, second) = if a < b { (a, b) } else { (b, a) };
671 if boxes[second].intersects(boxes[first], padding) {
672 lists[first].push(second);
673 }
674 }
675 }
676 for list in &mut lists {
677 list.sort_unstable();
678 }
679 Some(lists)
680}
681
682fn arrange_sweep_on() -> bool {
684 switch!("BREP_ARRANGE_SWEEP", true)
685}
686
687
688fn direction_index_on() -> bool {
691 switch!("BREP_ARRANGE_DIRECTION_INDEX", true)
692}
693
694pub fn arrange_segments(
695 segments: &[Segment2],
696 tolerance: f64,
697) -> Result<Vec<ArrangementRegion>, String> {
698 arrange_segments_impl(segments, tolerance, true, true)
699}
700
701fn arrange_segments_impl(
702 segments: &[Segment2],
703 tolerance: f64,
704 indexed: bool,
705 indexed_nodes: bool,
706) -> Result<Vec<ArrangementRegion>, String> {
707 let defer_tags = switch!("BREP_ARRANGE_DEFER_TAGS", true);
708 let end_on_segment = switch!("BREP_ARRANGE_END_ON_SEGMENT", true);
709 let mut cuts = vec![Vec::<f64>::new(); segments.len()];
710 let mut intersect_pair = |first: usize, second: usize| {
711 let Some([first_parameter, second_parameter]) = segment_intersection(
712 segments[first].a,
713 segments[first].b,
714 segments[second].a,
715 segments[second].b,
716 tolerance,
717 ) else {
718 if end_on_segment {
719 for (end, bar) in [(first, second), (second, first)] {
720 for (point, parameter) in end_on_segment_parameters(&segments[end], &segments[bar], tolerance) {
721 let shared = segments.iter().enumerate().any(|(index, other)| {
722 index != end && (other.a.distance(point) <= tolerance || other.b.distance(point) <= tolerance)
723 });
724 if !shared {
725 cuts[bar].push(parameter);
726 }
727 }
728 }
729 }
730 return;
731 };
732 let first_tolerance = tolerance_parameter(&segments[first], tolerance);
733 let second_tolerance = tolerance_parameter(&segments[second], tolerance);
734 if first_parameter > first_tolerance && first_parameter < 1.0 - first_tolerance {
735 cuts[first].push(first_parameter);
736 }
737 if second_parameter > second_tolerance && second_parameter < 1.0 - second_tolerance {
738 cuts[second].push(second_parameter);
739 }
740 };
741 if indexed {
742 visit_segment_pairs(segments, tolerance, &mut intersect_pair);
743 } else {
744 for first in 0..segments.len() {
745 for second in first + 1..segments.len() {
746 intersect_pair(first, second);
747 }
748 }
749 }
750
751 let mut pieces = Vec::new();
752 for (index, segment) in segments.iter().enumerate() {
753 let mut parameters = vec![0.0];
754 cuts[index].sort_by(f64::total_cmp);
755 parameters.extend(cuts[index].iter().copied());
756 parameters.push(1.0);
757 let parameter_tolerance = tolerance_parameter(segment, tolerance);
758 let mut deduplicated: Vec<f64> = Vec::new();
759 for parameter in parameters {
760 if deduplicated
761 .last()
762 .is_none_or(|previous| parameter - *previous > parameter_tolerance)
763 {
764 deduplicated.push(parameter);
765 } else if parameter == 1.0 {
766 *deduplicated.last_mut().unwrap() = 1.0;
767 }
768 }
769 for pair in deduplicated.windows(2) {
770 let a = interpolate(segment.a, segment.b, pair[0]);
771 let b = interpolate(segment.a, segment.b, pair[1]);
772 if a.distance(b) <= tolerance {
773 continue;
774 }
775 pieces.push(PieceInput {
776 a,
777 b,
778 parent: if defer_tags { std::borrow::Cow::Borrowed(segment) } else { std::borrow::Cow::Owned(segment.clone()) },
779 });
780 }
781 }
782
783 let mut nodes: Vec<Node> = Vec::new();
784 let mut lookup = NodeLookup::new(tolerance, indexed_nodes && pieces.len() > 64);
785 let mut piece_start = Vec::with_capacity(pieces.len());
786 let mut piece_end = Vec::with_capacity(pieces.len());
787 for piece in &pieces {
788 piece_start.push(lookup.find_or_insert(piece.a, &mut nodes));
789 piece_end.push(lookup.find_or_insert(piece.b, &mut nodes));
790 }
791
792 let mut alive = vec![true; pieces.len()];
793 loop {
794 let mut pruned = false;
795 let mut degree = vec![0usize; nodes.len()];
796 for index in 0..pieces.len() {
797 if !alive[index] {
798 continue;
799 }
800 if piece_start[index] == piece_end[index] {
801 alive[index] = false;
802 pruned = true;
803 continue;
804 }
805 degree[piece_start[index]] += 1;
806 degree[piece_end[index]] += 1;
807 }
808 for index in 0..pieces.len() {
809 if alive[index] && (degree[piece_start[index]] == 1 || degree[piece_end[index]] == 1) {
810 alive[index] = false;
811 pruned = true;
812 }
813 }
814 if !pruned {
815 break;
816 }
817 }
818
819 let half_tail = |half_edge: usize| {
820 if half_edge % 2 == 0 {
821 piece_start[half_edge >> 1]
822 } else {
823 piece_end[half_edge >> 1]
824 }
825 };
826 let half_head = |half_edge: usize| {
827 if half_edge % 2 == 0 {
828 piece_end[half_edge >> 1]
829 } else {
830 piece_start[half_edge >> 1]
831 }
832 };
833 for node in &mut nodes {
834 node.outgoing.clear();
835 }
836 for index in 0..pieces.len() {
837 if !alive[index] {
838 continue;
839 }
840 nodes[piece_start[index]].outgoing.push(index * 2);
841 nodes[piece_end[index]].outgoing.push(index * 2 + 1);
842 }
843 let angles: Vec<f64> = (0..pieces.len() * 2)
844 .map(|half_edge| {
845 let tail = nodes[half_tail(half_edge)].point;
846 let head = nodes[half_head(half_edge)].point;
847 (head.y - tail.y).atan2(head.x - tail.x)
848 })
849 .collect();
850 let digon_order = digon_order_enabled();
860 for node in &mut nodes {
861 node.outgoing.sort_by(|a, b| {
862 angles[*a].total_cmp(&angles[*b]).then_with(|| {
863 if !digon_order {
864 return std::cmp::Ordering::Equal;
865 }
866 let rank = |half_edge: usize| {
867 let piece = (half_edge >> 1) as i64;
868 if half_tail(half_edge) < half_head(half_edge) {
869 piece
870 } else {
871 -piece
872 }
873 };
874 rank(*a).cmp(&rank(*b))
875 })
876 });
877 }
878
879 let mut visited = rustc_hash::FxHashSet::default();
880 let mut positive = Vec::new();
881 let mut negative = Vec::new();
882 for index in 0..pieces.len() {
883 if !alive[index] {
884 continue;
885 }
886 for start in [index * 2, index * 2 + 1] {
887 if visited.contains(&start) {
888 continue;
889 }
890 let mut uses = if defer_tags { CycleUses::Deferred(Vec::new()) } else { CycleUses::Eager(Vec::new()) };
891 let mut node_indices = Vec::new();
892 let mut area = 0.0;
893 let mut half_edge = start;
894 let mut guard = 0;
895 loop {
896 visited.insert(half_edge);
897 uses.push(half_edge, &pieces);
898 let tail_index = half_tail(half_edge);
899 let head_index = half_head(half_edge);
900 let tail = nodes[tail_index].point;
901 let head = nodes[head_index].point;
902 area += 0.5 * (tail.x * head.y - head.x * tail.y);
903 node_indices.push(tail_index);
904 let reverse = half_edge ^ 1;
905 let outgoing = &nodes[head_index].outgoing;
906 let reverse_index = outgoing
907 .iter()
908 .position(|candidate| *candidate == reverse)
909 .ok_or_else(|| "arrangeSegments: reverse half-edge missing".to_string())?;
910 half_edge = outgoing[(reverse_index + outgoing.len() - 1) % outgoing.len()];
911 guard += 1;
912 if guard > pieces.len() * 4 + 8 {
913 return Err("arrangeSegments: face walk did not terminate".into());
914 }
915 if half_edge == start {
916 break;
917 }
918 }
919 let cycle = Cycle {
920 uses,
921 area,
922 node_indices,
923 };
924 if area > tolerance * tolerance {
925 positive.push(cycle);
926 } else if area < -tolerance * tolerance {
927 negative.push(cycle);
928 }
929 }
930 }
931
932 positive.sort_by(|a, b| a.area.total_cmp(&b.area));
933 let mut regions: Vec<(ArrangementRegion, Vec<Vec2>)> = positive
934 .into_iter()
935 .map(|cycle| {
936 let polygon = cycle
937 .node_indices
938 .iter()
939 .map(|index| nodes[*index].point)
940 .collect();
941 (
942 ArrangementRegion {
943 outer: cycle.uses.into_uses(&pieces),
944 holes: Vec::new(),
945 area: cycle.area,
946 outer_area: cycle.area,
947 },
948 polygon,
949 )
950 })
951 .collect();
952 let hole_boxes = hole_box_reject_enabled();
953 let boxes: Vec<[f64; 5]> = if hole_boxes {
954 regions.iter().map(|(_, polygon)| polygon_reject_box(polygon, tolerance)).collect()
955 } else {
956 Vec::new()
957 };
958 let mut bands: Vec<Option<BoundaryBand>> = (0..regions.len()).map(|_| None).collect();
959 for cycle in negative {
960 for (region_index, (region, polygon)) in regions.iter_mut().enumerate() {
961 let mut inside = false;
962 let mut on_boundary = false;
963 for node_index in &cycle.node_indices {
964 let point = nodes[*node_index].point;
965 let class = if hole_boxes && outside_reject_box(point, &boxes[region_index]) {
966 PolygonClass::Outside
967 } else if hole_boxes {
968 let band = bands[region_index].get_or_insert_with(|| BoundaryBand::new(polygon, tolerance));
969 band.classify(point, polygon, tolerance)
970 } else {
971 point_in_polygon_class(point, polygon, tolerance)
972 };
973 match class {
974 PolygonClass::Boundary => {
975 on_boundary = true;
976 continue;
977 }
978 PolygonClass::Inside => {
979 inside = true;
980 on_boundary = false;
981 break;
982 }
983 PolygonClass::Outside => {
984 inside = false;
985 on_boundary = false;
986 break;
987 }
988 }
989 }
990 if on_boundary {
991 continue;
992 }
993 if inside {
994 region.holes.push(cycle.uses.copy_uses(&pieces));
995 region.area += cycle.area;
996 break;
997 }
998 }
999 }
1000 if crate::profile::profile_enabled() {
1001 let holes: usize = regions.iter().flat_map(|(r, _)| r.holes.iter()).map(Vec::len).sum();
1002 let returned: usize = holes + regions.iter().map(|(r, _)| r.outer.len()).sum::<usize>();
1003 let clones = if defer_tags { returned } else { pieces.len() + visited.len() + holes };
1004 crate::imprint::profile_emit(format_args!("arrange.tags segments={} pieces={} walked={} returned={returned} parent_clones={clones} deferred={defer_tags}", segments.len(), pieces.len(), visited.len()));
1005 }
1006 Ok(regions.into_iter().map(|(region, _)| region).collect())
1007}
1008
1009