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
231pub use crate::polygon::PolygonClass;
232
233fn point_segment_distance(point: Vec2, a: Vec2, b: Vec2) -> f64 {
234 point.distance_to_segment(a, b, 1e-300)
235}
236
237fn polygon_reject_box(polygon: &[Vec2], tolerance: f64) -> [f64; 5] {
251 let mut bounds = [f64::INFINITY, f64::INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY];
252 let mut magnitude = 1.0f64;
253 for point in polygon {
254 bounds[0] = bounds[0].min(point.x);
255 bounds[1] = bounds[1].min(point.y);
256 bounds[2] = bounds[2].max(point.x);
257 bounds[3] = bounds[3].max(point.y);
258 magnitude = magnitude.max(point.x.abs()).max(point.y.abs());
259 }
260 let finite = !polygon.is_empty() && bounds.iter().all(|value| value.is_finite()) && tolerance.is_finite() && tolerance >= 0.0;
261 let reach = if finite { tolerance * (1.0 + 1e-9) + 64.0 * f64::EPSILON * magnitude } else { f64::NAN };
262 [bounds[0], bounds[1], bounds[2], bounds[3], reach]
263}
264
265struct BoundaryBand {
277 origin: [f64; 2],
278 cell: [f64; 2],
279 side: usize,
280 reach: f64,
281 starts: Vec<u32>,
282 items: Vec<u32>,
283 usable: bool,
284}
285
286impl BoundaryBand {
287 fn new(polygon: &[Vec2], tolerance: f64) -> Self {
288 let reject = polygon_reject_box(polygon, tolerance);
289 let reach = reject[4];
290 let count = polygon.len();
291 let usable = reach.is_finite() && count >= 16 && count <= (1 << 24);
292 let side = if usable { ((count as f64).sqrt() as usize).clamp(4, 512) } else { 0 };
293 let origin = [reject[0] - reach, reject[1] - reach];
294 let extent = [(reject[2] + reach) - origin[0], (reject[3] + reach) - origin[1]];
295 let cell = [(extent[0] / side.max(1) as f64).max(f64::MIN_POSITIVE), (extent[1] / side.max(1) as f64).max(f64::MIN_POSITIVE)];
296 let mut band = Self { origin, cell, side, reach, starts: Vec::new(), items: Vec::new(), usable };
297 if !band.usable {
298 return band;
299 }
300 let mut counts = vec![0u32; side * side + 1];
301 let ranges: Vec<[usize; 4]> = (0..count)
302 .map(|index| {
303 let (a, b) = (polygon[index], polygon[(index + 1) % count]);
304 let pad = reach + 64.0 * f64::EPSILON * a.x.abs().max(a.y.abs()).max(b.x.abs()).max(b.y.abs());
305 [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)]
306 })
307 .collect();
308 for range in &ranges {
309 for y in range[2]..=range[3] {
310 for x in range[0]..=range[1] {
311 counts[y * side + x + 1] += 1;
312 }
313 }
314 }
315 for index in 1..counts.len() {
316 counts[index] += counts[index - 1];
317 }
318 if counts[side * side] as usize > 64 * count + 4096 {
320 band.usable = false;
321 return band;
322 }
323 let mut cursor = counts.clone();
324 band.items = vec![0u32; counts[side * side] as usize];
325 for (edge, range) in ranges.iter().enumerate() {
326 for y in range[2]..=range[3] {
327 for x in range[0]..=range[1] {
328 let slot = &mut cursor[y * side + x];
329 band.items[*slot as usize] = edge as u32;
330 *slot += 1;
331 }
332 }
333 }
334 band.starts = counts;
335 band
336 }
337 fn column(&self, x: f64) -> usize {
338 (((x - self.origin[0]) / self.cell[0]).floor().max(0.0) as usize).min(self.side - 1)
339 }
340 fn row(&self, y: f64) -> usize {
341 (((y - self.origin[1]) / self.cell[1]).floor().max(0.0) as usize).min(self.side - 1)
342 }
343 fn classify(&self, point: Vec2, polygon: &[Vec2], tolerance: f64) -> PolygonClass {
344 if !self.usable || !point.x.is_finite() || !point.y.is_finite() {
345 return point_in_polygon_class(point, polygon, tolerance);
346 }
347 let cell = self.row(point.y) * self.side + self.column(point.x);
348 let count = polygon.len();
349 for &edge in &self.items[self.starts[cell] as usize..self.starts[cell + 1] as usize] {
350 let edge = edge as usize;
351 if point.distance_to_segment(polygon[edge], polygon[(edge + 1) % count], 1e-300) <= tolerance {
352 return PolygonClass::Boundary;
353 }
354 }
355 if crate::polygon::parity_inside([point.x, point.y], count, |i| [polygon[i].x, polygon[i].y]) {
356 PolygonClass::Inside
357 } else {
358 PolygonClass::Outside
359 }
360 }
361}
362
363fn outside_reject_box(point: Vec2, reject: &[f64; 5]) -> bool {
364 let reach = reject[4] + 64.0 * f64::EPSILON * point.x.abs().max(point.y.abs());
365 point.x < reject[0] - reach || point.y < reject[1] - reach || point.x > reject[2] + reach || point.y > reject[3] + reach
367}
368
369fn digon_order_enabled() -> bool {
372 switch!("BREP_ARRANGE_DIGON_ORDER", true)
373}
374
375fn hole_box_reject_enabled() -> bool {
376 switch!("BREP_ARRANGE_HOLE_BOX", true)
377}
378
379
380fn point_in_polygon_class(point: Vec2, polygon: &[Vec2], tolerance: f64) -> PolygonClass {
381 crate::polygon::classify(point, polygon, tolerance, 1e-300)
382}
383
384pub fn point_in_polygon(point: Vec2, polygon: &[Vec2], tolerance: f64) -> &'static str {
385 match point_in_polygon_class(point, polygon, tolerance) {
386 PolygonClass::Inside => "in",
387 PolygonClass::Outside => "out",
388 PolygonClass::Boundary => "boundary",
389 }
390}
391
392struct PieceInput<'a> {
396 a: Vec2,
397 b: Vec2,
398 parent: std::borrow::Cow<'a, Segment2>,
399}
400impl PieceInput<'_> {
401 fn materialize(&self, forward: bool) -> CycleUse {
402 CycleUse { piece: ArrangementPiece { a: self.a, b: self.b, parent: self.parent.as_ref().clone() }, forward }
403 }
404}
405
406#[derive(Clone)]
407enum CycleUses {
408 Eager(Vec<CycleUse>),
409 Deferred(Vec<usize>),
410}
411impl CycleUses {
412 fn push(&mut self, half: usize, pieces: &[PieceInput<'_>]) {
413 match self {
414 Self::Eager(uses) => uses.push(pieces[half >> 1].materialize(half % 2 == 0)),
415 Self::Deferred(halves) => halves.push(half),
416 }
417 }
418 fn into_uses(self, pieces: &[PieceInput<'_>]) -> Vec<CycleUse> {
419 match self {
420 Self::Eager(uses) => uses,
421 Self::Deferred(halves) => halves.into_iter().map(|h| pieces[h >> 1].materialize(h % 2 == 0)).collect(),
422 }
423 }
424 fn copy_uses(&self, pieces: &[PieceInput<'_>]) -> Vec<CycleUse> {
425 match self {
426 Self::Eager(uses) => uses.clone(),
427 Self::Deferred(halves) => halves.iter().map(|&h| pieces[h >> 1].materialize(h % 2 == 0)).collect(),
428 }
429 }
430}
431
432#[derive(Clone)]
433struct Cycle {
434 uses: CycleUses,
435 area: f64,
436 node_indices: Vec<usize>,
437}
438
439fn visit_segment_pairs(segments: &[Segment2], tolerance: f64, mut visit: impl FnMut(usize, usize)) {
442 let all_pairs = |visit: &mut dyn FnMut(usize, usize)| {
443 for first in 0..segments.len() {
444 for second in first + 1..segments.len() {
445 visit(first, second);
446 }
447 }
448 };
449 if segments.len() <= 32
450 || segments.len() > u32::MAX as usize
451 || !tolerance.is_finite()
452 || tolerance < 0.0
453 || tolerance > 1e100
454 {
455 all_pairs(&mut visit);
456 return;
457 }
458 let mut scale = 0.0f64;
459 let mut boxes = Vec::with_capacity(segments.len());
460 let mut extent = Aabb::empty();
461 for segment in segments {
462 let coordinates = [segment.a.x, segment.a.y, segment.b.x, segment.b.y];
463 let length = segment.a.distance(segment.b);
464 if coordinates
467 .iter()
468 .any(|v| !v.is_finite() || v.abs() > 1e100)
469 || !(1e-100..=1e100).contains(&length)
470 {
471 all_pairs(&mut visit);
472 return;
473 }
474 for coordinate in coordinates {
475 scale = scale.max(coordinate.abs());
476 }
477 let bounds = Aabb {
478 minimum: Vec3::new(
479 segment.a.x.min(segment.b.x),
480 segment.a.y.min(segment.b.y),
481 0.0,
482 ),
483 maximum: Vec3::new(
484 segment.a.x.max(segment.b.x),
485 segment.a.y.max(segment.b.y),
486 0.0,
487 ),
488 };
489 extent.include(bounds);
490 boxes.push(bounds);
491 }
492 let padding = 2.0 * tolerance + 128.0 * f64::EPSILON / PARALLEL_THRESHOLD * scale;
500 if padding >= extent.diagonal() {
503 all_pairs(&mut visit);
504 return;
505 }
506 let direction_tiers = direction_index_on();
507 let small_padding = 2.0 * tolerance + 128.0 * f64::EPSILON / DIRECTION_SINE * scale;
511 let swept = if direction_tiers && arrange_sweep_on() { swept_pairs(&boxes, small_padding) } else { None };
512 let tree = swept.is_none().then(|| Bvh::build(&boxes));
513 let mut candidates = Vec::new();
514 if direction_tiers {
515 let width = 2.0 * DIRECTION_SINE;
530 let bucket_count = ((std::f64::consts::PI / width).ceil() as usize).max(1);
531 let mut buckets: FxHashMap<usize, Vec<usize>> = FxHashMap::default();
532 let bucket_of: Vec<usize> = segments
533 .iter()
534 .map(|segment| {
535 let angle = (segment.b.y - segment.a.y).atan2(segment.b.x - segment.a.x).rem_euclid(std::f64::consts::PI);
536 ((angle / width) as usize).min(bucket_count - 1)
537 })
538 .collect();
539 for (index, bucket) in bucket_of.iter().enumerate() {
540 buckets.entry(*bucket).or_default().push(index);
541 }
542 let near = |a: &Aabb, b: &Aabb| {
543 a.minimum.x - padding <= b.maximum.x
544 && b.minimum.x - padding <= a.maximum.x
545 && a.minimum.y - padding <= b.maximum.y
546 && b.minimum.y - padding <= a.maximum.y
547 };
548 for (first, &bounds) in boxes.iter().enumerate() {
549 candidates.clear();
550 match (&swept, &tree) {
551 (Some(lists), _) => candidates.extend_from_slice(&lists[first]),
552 (None, Some(tree)) => tree.overlapping(bounds, small_padding, &mut candidates),
553 (None, None) => unreachable!("the tree is built whenever the sweep is not"),
554 }
555 let bucket = bucket_of[first];
556 for neighbour in [(bucket + bucket_count - 1) % bucket_count, bucket, (bucket + 1) % bucket_count] {
557 if let Some(members) = buckets.get(&neighbour) {
558 candidates.extend(members.iter().copied().filter(|&second| second > first && near(&bounds, &boxes[second])));
559 }
560 if bucket_count < 3 {
561 break;
562 }
563 }
564 candidates.retain(|&second| second > first);
565 candidates.sort_unstable();
566 candidates.dedup();
567 for &second in &candidates {
568 visit(first, second);
569 }
570 }
571 return;
572 }
573 let Some(tree) = tree else { unreachable!("the sweep runs only under the direction tiers") };
574 for (first, &bounds) in boxes.iter().enumerate() {
575 candidates.clear();
576 tree.overlapping(bounds, padding, &mut candidates);
577 candidates.retain(|&second| second > first);
578 candidates.sort_unstable();
579 for &second in &candidates {
580 visit(first, second);
581 }
582 }
583}
584
585const DIRECTION_SINE: f64 = 1e-3;
588
589fn swept_pairs(boxes: &[Aabb], padding: f64) -> Option<Vec<Vec<usize>>> {
609 let reach = 2.0 * padding;
610 let low = |index: usize, axis: usize| if axis == 0 { boxes[index].minimum.x } else { boxes[index].minimum.y };
611 let high = |index: usize, axis: usize| if axis == 0 { boxes[index].maximum.x } else { boxes[index].maximum.y };
612 let mut best: Option<(usize, Vec<usize>, usize)> = None;
613 for axis in [0, 1] {
614 let mut order: Vec<usize> = (0..boxes.len()).collect();
615 order.sort_by(|&a, &b| low(a, axis).total_cmp(&low(b, axis)));
616 let lows: Vec<f64> = order.iter().map(|&index| low(index, axis)).collect();
617 let windows: usize = order
618 .iter()
619 .enumerate()
620 .map(|(position, &a)| {
621 let limit = high(a, axis) + reach;
622 lows.partition_point(|&value| value <= limit).saturating_sub(position + 1)
623 })
624 .sum();
625 if best.as_ref().is_none_or(|(least, _, _)| windows < *least) {
626 best = Some((windows, order, axis));
627 }
628 }
629 let (windows, order, axis) = best?;
630 if windows > 32 * boxes.len() {
631 return None;
632 }
633 let mut lists = vec![Vec::new(); boxes.len()];
634 for (position, &a) in order.iter().enumerate() {
635 let limit = high(a, axis) + reach;
636 for &b in &order[position + 1..] {
637 if low(b, axis) > limit {
638 break;
639 }
640 let (first, second) = if a < b { (a, b) } else { (b, a) };
641 if boxes[second].intersects(boxes[first], padding) {
642 lists[first].push(second);
643 }
644 }
645 }
646 for list in &mut lists {
647 list.sort_unstable();
648 }
649 Some(lists)
650}
651
652fn arrange_sweep_on() -> bool {
654 switch!("BREP_ARRANGE_SWEEP", true)
655}
656
657
658fn direction_index_on() -> bool {
661 switch!("BREP_ARRANGE_DIRECTION_INDEX", true)
662}
663
664pub fn arrange_segments(
665 segments: &[Segment2],
666 tolerance: f64,
667) -> Result<Vec<ArrangementRegion>, String> {
668 arrange_segments_impl(segments, tolerance, true, true)
669}
670
671fn arrange_segments_impl(
672 segments: &[Segment2],
673 tolerance: f64,
674 indexed: bool,
675 indexed_nodes: bool,
676) -> Result<Vec<ArrangementRegion>, String> {
677 let defer_tags = switch!("BREP_ARRANGE_DEFER_TAGS", true);
678 let mut cuts = vec![Vec::<f64>::new(); segments.len()];
679 let mut intersect_pair = |first: usize, second: usize| {
680 let Some([first_parameter, second_parameter]) = segment_intersection(
681 segments[first].a,
682 segments[first].b,
683 segments[second].a,
684 segments[second].b,
685 tolerance,
686 ) else {
687 return;
688 };
689 let first_tolerance = tolerance_parameter(&segments[first], tolerance);
690 let second_tolerance = tolerance_parameter(&segments[second], tolerance);
691 if first_parameter > first_tolerance && first_parameter < 1.0 - first_tolerance {
692 cuts[first].push(first_parameter);
693 }
694 if second_parameter > second_tolerance && second_parameter < 1.0 - second_tolerance {
695 cuts[second].push(second_parameter);
696 }
697 };
698 if indexed {
699 visit_segment_pairs(segments, tolerance, &mut intersect_pair);
700 } else {
701 for first in 0..segments.len() {
702 for second in first + 1..segments.len() {
703 intersect_pair(first, second);
704 }
705 }
706 }
707
708 let mut pieces = Vec::new();
709 for (index, segment) in segments.iter().enumerate() {
710 let mut parameters = vec![0.0];
711 cuts[index].sort_by(f64::total_cmp);
712 parameters.extend(cuts[index].iter().copied());
713 parameters.push(1.0);
714 let parameter_tolerance = tolerance_parameter(segment, tolerance);
715 let mut deduplicated: Vec<f64> = Vec::new();
716 for parameter in parameters {
717 if deduplicated
718 .last()
719 .is_none_or(|previous| parameter - *previous > parameter_tolerance)
720 {
721 deduplicated.push(parameter);
722 } else if parameter == 1.0 {
723 *deduplicated.last_mut().unwrap() = 1.0;
724 }
725 }
726 for pair in deduplicated.windows(2) {
727 let a = interpolate(segment.a, segment.b, pair[0]);
728 let b = interpolate(segment.a, segment.b, pair[1]);
729 if a.distance(b) <= tolerance {
730 continue;
731 }
732 pieces.push(PieceInput {
733 a,
734 b,
735 parent: if defer_tags { std::borrow::Cow::Borrowed(segment) } else { std::borrow::Cow::Owned(segment.clone()) },
736 });
737 }
738 }
739
740 let mut nodes: Vec<Node> = Vec::new();
741 let mut lookup = NodeLookup::new(tolerance, indexed_nodes && pieces.len() > 64);
742 let mut piece_start = Vec::with_capacity(pieces.len());
743 let mut piece_end = Vec::with_capacity(pieces.len());
744 for piece in &pieces {
745 piece_start.push(lookup.find_or_insert(piece.a, &mut nodes));
746 piece_end.push(lookup.find_or_insert(piece.b, &mut nodes));
747 }
748
749 let mut alive = vec![true; pieces.len()];
750 loop {
751 let mut pruned = false;
752 let mut degree = vec![0usize; nodes.len()];
753 for index in 0..pieces.len() {
754 if !alive[index] {
755 continue;
756 }
757 if piece_start[index] == piece_end[index] {
758 alive[index] = false;
759 pruned = true;
760 continue;
761 }
762 degree[piece_start[index]] += 1;
763 degree[piece_end[index]] += 1;
764 }
765 for index in 0..pieces.len() {
766 if alive[index] && (degree[piece_start[index]] == 1 || degree[piece_end[index]] == 1) {
767 alive[index] = false;
768 pruned = true;
769 }
770 }
771 if !pruned {
772 break;
773 }
774 }
775
776 let half_tail = |half_edge: usize| {
777 if half_edge % 2 == 0 {
778 piece_start[half_edge >> 1]
779 } else {
780 piece_end[half_edge >> 1]
781 }
782 };
783 let half_head = |half_edge: usize| {
784 if half_edge % 2 == 0 {
785 piece_end[half_edge >> 1]
786 } else {
787 piece_start[half_edge >> 1]
788 }
789 };
790 for node in &mut nodes {
791 node.outgoing.clear();
792 }
793 for index in 0..pieces.len() {
794 if !alive[index] {
795 continue;
796 }
797 nodes[piece_start[index]].outgoing.push(index * 2);
798 nodes[piece_end[index]].outgoing.push(index * 2 + 1);
799 }
800 let angles: Vec<f64> = (0..pieces.len() * 2)
801 .map(|half_edge| {
802 let tail = nodes[half_tail(half_edge)].point;
803 let head = nodes[half_head(half_edge)].point;
804 (head.y - tail.y).atan2(head.x - tail.x)
805 })
806 .collect();
807 let digon_order = digon_order_enabled();
817 for node in &mut nodes {
818 node.outgoing.sort_by(|a, b| {
819 angles[*a].total_cmp(&angles[*b]).then_with(|| {
820 if !digon_order {
821 return std::cmp::Ordering::Equal;
822 }
823 let rank = |half_edge: usize| {
824 let piece = (half_edge >> 1) as i64;
825 if half_tail(half_edge) < half_head(half_edge) {
826 piece
827 } else {
828 -piece
829 }
830 };
831 rank(*a).cmp(&rank(*b))
832 })
833 });
834 }
835
836 let mut visited = rustc_hash::FxHashSet::default();
837 let mut positive = Vec::new();
838 let mut negative = Vec::new();
839 for index in 0..pieces.len() {
840 if !alive[index] {
841 continue;
842 }
843 for start in [index * 2, index * 2 + 1] {
844 if visited.contains(&start) {
845 continue;
846 }
847 let mut uses = if defer_tags { CycleUses::Deferred(Vec::new()) } else { CycleUses::Eager(Vec::new()) };
848 let mut node_indices = Vec::new();
849 let mut area = 0.0;
850 let mut half_edge = start;
851 let mut guard = 0;
852 loop {
853 visited.insert(half_edge);
854 uses.push(half_edge, &pieces);
855 let tail_index = half_tail(half_edge);
856 let head_index = half_head(half_edge);
857 let tail = nodes[tail_index].point;
858 let head = nodes[head_index].point;
859 area += 0.5 * (tail.x * head.y - head.x * tail.y);
860 node_indices.push(tail_index);
861 let reverse = half_edge ^ 1;
862 let outgoing = &nodes[head_index].outgoing;
863 let reverse_index = outgoing
864 .iter()
865 .position(|candidate| *candidate == reverse)
866 .ok_or_else(|| "arrangeSegments: reverse half-edge missing".to_string())?;
867 half_edge = outgoing[(reverse_index + outgoing.len() - 1) % outgoing.len()];
868 guard += 1;
869 if guard > pieces.len() * 4 + 8 {
870 return Err("arrangeSegments: face walk did not terminate".into());
871 }
872 if half_edge == start {
873 break;
874 }
875 }
876 let cycle = Cycle {
877 uses,
878 area,
879 node_indices,
880 };
881 if area > tolerance * tolerance {
882 positive.push(cycle);
883 } else if area < -tolerance * tolerance {
884 negative.push(cycle);
885 }
886 }
887 }
888
889 positive.sort_by(|a, b| a.area.total_cmp(&b.area));
890 let mut regions: Vec<(ArrangementRegion, Vec<Vec2>)> = positive
891 .into_iter()
892 .map(|cycle| {
893 let polygon = cycle
894 .node_indices
895 .iter()
896 .map(|index| nodes[*index].point)
897 .collect();
898 (
899 ArrangementRegion {
900 outer: cycle.uses.into_uses(&pieces),
901 holes: Vec::new(),
902 area: cycle.area,
903 outer_area: cycle.area,
904 },
905 polygon,
906 )
907 })
908 .collect();
909 let hole_boxes = hole_box_reject_enabled();
910 let boxes: Vec<[f64; 5]> = if hole_boxes {
911 regions.iter().map(|(_, polygon)| polygon_reject_box(polygon, tolerance)).collect()
912 } else {
913 Vec::new()
914 };
915 let mut bands: Vec<Option<BoundaryBand>> = (0..regions.len()).map(|_| None).collect();
916 for cycle in negative {
917 for (region_index, (region, polygon)) in regions.iter_mut().enumerate() {
918 let mut inside = false;
919 let mut on_boundary = false;
920 for node_index in &cycle.node_indices {
921 let point = nodes[*node_index].point;
922 let class = if hole_boxes && outside_reject_box(point, &boxes[region_index]) {
923 PolygonClass::Outside
924 } else if hole_boxes {
925 let band = bands[region_index].get_or_insert_with(|| BoundaryBand::new(polygon, tolerance));
926 band.classify(point, polygon, tolerance)
927 } else {
928 point_in_polygon_class(point, polygon, tolerance)
929 };
930 match class {
931 PolygonClass::Boundary => {
932 on_boundary = true;
933 continue;
934 }
935 PolygonClass::Inside => {
936 inside = true;
937 on_boundary = false;
938 break;
939 }
940 PolygonClass::Outside => {
941 inside = false;
942 on_boundary = false;
943 break;
944 }
945 }
946 }
947 if on_boundary {
948 continue;
949 }
950 if inside {
951 region.holes.push(cycle.uses.copy_uses(&pieces));
952 region.area += cycle.area;
953 break;
954 }
955 }
956 }
957 if crate::profile::profile_enabled() {
958 let holes: usize = regions.iter().flat_map(|(r, _)| r.holes.iter()).map(Vec::len).sum();
959 let returned: usize = holes + regions.iter().map(|(r, _)| r.outer.len()).sum::<usize>();
960 let clones = if defer_tags { returned } else { pieces.len() + visited.len() + holes };
961 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()));
962 }
963 Ok(regions.into_iter().map(|(region, _)| region).collect())
964}
965
966