Skip to main content

tess2_rust/tess/
mod.rs

1// Copyright 2025 Lars Brubaker
2// License: SGI Free Software License B (MIT-compatible)
3//
4// Port of libtess2 tess.c/h + sweep.c/h + tesselator.h
5//
6// This module is the complete tessellator: public API + full sweep line algorithm.
7// The C code is split across tess.c and sweep.c; they're merged here since both
8// share the same internal state (TESStesselator).
9
10mod geometry;
11mod output;
12#[cfg(test)]
13mod tests;
14
15use geometry::{
16    check_orientation, compute_intersect_coords, compute_normal, dot, is_valid_coord, long_axis,
17};
18
19use crate::dict::{Dict, NodeIdx, DICT_HEAD};
20use crate::geom::{edge_intersect, edge_sign, vert_eq, vert_leq, Real};
21use crate::mesh::{EdgeIdx, Mesh, VertIdx, E_HEAD, INVALID, V_HEAD};
22use crate::priorityq::INVALID_HANDLE;
23use crate::sweep::ActiveRegion;
24
25// ─────────────────────────────── Public types ──────────────────────────────────
26
27#[derive(Copy, Clone, Debug, PartialEq)]
28pub enum WindingRule {
29    Odd,
30    NonZero,
31    Positive,
32    Negative,
33    AbsGeqTwo,
34}
35
36#[derive(Copy, Clone, Debug, PartialEq)]
37pub enum ElementType {
38    Polygons,
39    ConnectedPolygons,
40    BoundaryContours,
41}
42
43#[derive(Copy, Clone, Debug, PartialEq)]
44pub enum TessOption {
45    ConstrainedDelaunayTriangulation,
46    ReverseContours,
47}
48
49#[derive(Copy, Clone, Debug, PartialEq)]
50pub enum TessStatus {
51    Ok,
52    OutOfMemory,
53    InvalidInput,
54}
55
56pub const TESS_UNDEF: u32 = u32::MAX;
57// Max magnitude that input coordinates can safely take without losing
58// precision in the sweep.  f64's 52-bit mantissa keeps integer coords
59// exact up to 2^53; we keep a conservative margin.
60const MAX_VALID_COORD: Real = (1u64 << 50) as Real;
61const MIN_VALID_COORD: Real = -MAX_VALID_COORD;
62
63type RegionIdx = u32;
64
65// ─────────────────────────── Tessellator ──────────────────────────────────────
66
67pub struct Tessellator {
68    mesh: Option<Mesh>,
69    pub status: TessStatus,
70    normal: [Real; 3],
71    s_unit: [Real; 3],
72    t_unit: [Real; 3],
73    bmin: [Real; 2],
74    bmax: [Real; 2],
75    process_cdt: bool,
76    reverse_contours: bool,
77    winding_rule: WindingRule,
78
79    // Sweep state
80    dict: Dict,
81    /// Intersection vertices inserted during sweep (heap replacement).
82    /// Each entry is a VertIdx; ordering is done by coordinate lookup.
83    intersection_verts: Vec<VertIdx>,
84    next_isect_handle: i32,
85    event: VertIdx,
86    event_s: Real,
87    event_t: Real,
88
89    // Region arena
90    regions: Vec<Option<ActiveRegion>>,
91    region_free: Vec<RegionIdx>,
92
93    // Output
94    pub out_vertices: Vec<Real>,
95    pub out_vertex_indices: Vec<u32>,
96    pub out_elements: Vec<u32>,
97    /// Per triangle-vertex edge-flag (parallel to `out_elements`).
98    ///
99    /// `1` when the polygon edge starting at this vertex (going to the next
100    /// vertex in CCW order within the same triangle) is an **original
101    /// boundary edge** of the input polygon; `0` when the edge is a new
102    /// interior edge added by the tessellation sweep.
103    ///
104    /// Parallels the C libtess2 / agg-sharp `EdgeFlagCallback` mechanism —
105    /// consumers that want analytic per-edge anti-aliasing (halo strips,
106    /// conservative outlines) look at each triangle's three flags and only
107    /// expand the sides that are actual polygon boundaries.
108    ///
109    /// Populated for `ElementType::Polygons` and `ElementType::ConnectedPolygons`;
110    /// empty for `ElementType::BoundaryContours` (no triangles are emitted in
111    /// that mode).  Length equals `poly_size × element_count`.
112    pub out_edge_flags: Vec<u8>,
113    pub out_vertex_count: usize,
114    pub out_element_count: usize,
115    vertex_index_counter: u32,
116
117    // Primary event queue: pre-sorted vertices for the initial sweep phase
118    sorted_events: Vec<VertIdx>,
119    sorted_event_pos: usize,
120    sweep_event_num: u32,
121    trace_enabled: bool,
122}
123
124impl Tessellator {
125    pub fn new() -> Self {
126        Tessellator {
127            mesh: None,
128            status: TessStatus::Ok,
129            normal: [0.0; 3],
130            s_unit: [0.0; 3],
131            t_unit: [0.0; 3],
132            bmin: [0.0; 2],
133            bmax: [0.0; 2],
134            process_cdt: false,
135            reverse_contours: false,
136            winding_rule: WindingRule::Odd,
137            dict: Dict::new(),
138            intersection_verts: Vec::new(),
139            next_isect_handle: 0,
140            event: INVALID,
141            event_s: 0.0,
142            event_t: 0.0,
143            regions: Vec::new(),
144            region_free: Vec::new(),
145            out_vertices: Vec::new(),
146            out_vertex_indices: Vec::new(),
147            out_elements: Vec::new(),
148            out_edge_flags: Vec::new(),
149            out_vertex_count: 0,
150            out_element_count: 0,
151            vertex_index_counter: 0,
152            sorted_events: Vec::new(),
153            sorted_event_pos: 0,
154            sweep_event_num: 0,
155            trace_enabled: std::env::var("TESS_TRACE").is_ok(),
156        }
157    }
158
159    pub fn set_option(&mut self, option: TessOption, value: bool) {
160        match option {
161            TessOption::ConstrainedDelaunayTriangulation => self.process_cdt = value,
162            TessOption::ReverseContours => self.reverse_contours = value,
163        }
164    }
165
166    /// Add a contour. `size` = 2 or 3 (coords per vertex). `vertices` is flat.
167    ///
168    /// Input type is `Real` — currently `f64` — to avoid losing precision on
169    /// coordinate input.  Callers holding `f32` data should cast element-wise
170    /// at the call site.
171    pub fn add_contour(&mut self, size: usize, vertices: &[Real]) {
172        if self.status != TessStatus::Ok {
173            return;
174        }
175        let size = size.min(3).max(2);
176        let count = vertices.len() / size;
177        if self.mesh.is_none() {
178            self.mesh = Some(Mesh::new());
179        }
180
181        let mut e = INVALID;
182        for i in 0..count {
183            let cx = vertices[i * size];
184            let cy = vertices[i * size + 1];
185            let cz = if size > 2 {
186                vertices[i * size + 2]
187            } else {
188                0.0
189            };
190
191            if !is_valid_coord(cx) || !is_valid_coord(cy) || (size > 2 && !is_valid_coord(cz)) {
192                self.status = TessStatus::InvalidInput;
193                return;
194            }
195
196            let mesh = self.mesh.as_mut().unwrap();
197            if e == INVALID {
198                let new_e = match mesh.make_edge() {
199                    Some(v) => v,
200                    None => {
201                        self.status = TessStatus::OutOfMemory;
202                        return;
203                    }
204                };
205                e = new_e;
206                if !mesh.splice(e, e ^ 1) {
207                    self.status = TessStatus::OutOfMemory;
208                    return;
209                }
210            } else {
211                if mesh.split_edge(e).is_none() {
212                    self.status = TessStatus::OutOfMemory;
213                    return;
214                }
215                e = mesh.edges[e as usize].lnext;
216            }
217
218            let org = mesh.edges[e as usize].org;
219            mesh.verts[org as usize].coords[0] = cx;
220            mesh.verts[org as usize].coords[1] = cy;
221            mesh.verts[org as usize].coords[2] = cz;
222            mesh.verts[org as usize].idx = self.vertex_index_counter;
223            self.vertex_index_counter += 1;
224
225            let w = if self.reverse_contours { -1 } else { 1 };
226            mesh.edges[e as usize].winding = w;
227            mesh.edges[(e ^ 1) as usize].winding = -w;
228        }
229    }
230
231    pub fn tessellate(
232        &mut self,
233        winding_rule: WindingRule,
234        element_type: ElementType,
235        poly_size: usize,
236        vertex_size: usize,
237        normal: Option<[Real; 3]>,
238    ) -> bool {
239        if self.status != TessStatus::Ok {
240            return false;
241        }
242        self.winding_rule = winding_rule;
243        self.out_vertices.clear();
244        self.out_vertex_indices.clear();
245        self.out_elements.clear();
246        self.out_edge_flags.clear();
247        self.out_vertex_count = 0;
248        self.out_element_count = 0;
249        self.normal = normal.unwrap_or([0.0, 0.0, 0.0]);
250
251        if self.mesh.is_none() {
252            self.mesh = Some(Mesh::new());
253        }
254
255        if !self.project_polygon() {
256            self.status = TessStatus::OutOfMemory;
257            return false;
258        }
259
260        if !self.compute_interior() {
261            if self.status == TessStatus::Ok {
262                self.status = TessStatus::OutOfMemory;
263            }
264            return false;
265        }
266
267        let vertex_size = vertex_size.min(3).max(2);
268        if element_type == ElementType::BoundaryContours {
269            self.output_contours(vertex_size);
270        } else {
271            self.output_polymesh(element_type, poly_size, vertex_size);
272        }
273
274        self.mesh = None;
275        self.status == TessStatus::Ok
276    }
277
278    // ─────── Accessors ────────────────────────────────────────────────────────
279
280    pub fn vertex_count(&self) -> usize {
281        self.out_vertex_count
282    }
283    pub fn element_count(&self) -> usize {
284        self.out_element_count
285    }
286    pub fn vertices(&self) -> &[Real] {
287        &self.out_vertices
288    }
289    pub fn vertex_indices(&self) -> &[u32] {
290        &self.out_vertex_indices
291    }
292    pub fn elements(&self) -> &[u32] {
293        &self.out_elements
294    }
295    /// Per triangle-vertex edge flags (see [`Tessellator::out_edge_flags`]).
296    ///
297    /// Returns an empty slice for `ElementType::BoundaryContours`.
298    pub fn edge_flags(&self) -> &[u8] {
299        &self.out_edge_flags
300    }
301    pub fn get_status(&self) -> TessStatus {
302        self.status
303    }
304
305    // ─────── Projection ───────────────────────────────────────────────────────
306
307    fn project_polygon(&mut self) -> bool {
308        let mut norm = self.normal;
309        let mut computed_normal = false;
310        if norm[0] == 0.0 && norm[1] == 0.0 && norm[2] == 0.0 {
311            if let Some(ref m) = self.mesh {
312                compute_normal(m, &mut norm);
313            }
314            computed_normal = true;
315        }
316
317        let i = long_axis(&norm);
318        self.s_unit = [0.0; 3];
319        self.t_unit = [0.0; 3];
320        self.s_unit[(i + 1) % 3] = 1.0;
321        self.t_unit[(i + 2) % 3] = if norm[i] > 0.0 { 1.0 } else { -1.0 };
322        let su = self.s_unit;
323        let tu = self.t_unit;
324
325        if let Some(ref mut mesh) = self.mesh {
326            let mut v = mesh.verts[V_HEAD as usize].next;
327            while v != V_HEAD {
328                let c = mesh.verts[v as usize].coords;
329                mesh.verts[v as usize].s = dot(&c, &su);
330                mesh.verts[v as usize].t = dot(&c, &tu);
331                v = mesh.verts[v as usize].next;
332            }
333            if computed_normal {
334                check_orientation(mesh);
335            }
336
337            let mut first = true;
338            let mut v = mesh.verts[V_HEAD as usize].next;
339            while v != V_HEAD {
340                let vs = mesh.verts[v as usize].s;
341                let vt = mesh.verts[v as usize].t;
342                if first {
343                    self.bmin = [vs, vt];
344                    self.bmax = [vs, vt];
345                    first = false;
346                } else {
347                    if vs < self.bmin[0] {
348                        self.bmin[0] = vs;
349                    }
350                    if vs > self.bmax[0] {
351                        self.bmax[0] = vs;
352                    }
353                    if vt < self.bmin[1] {
354                        self.bmin[1] = vt;
355                    }
356                    if vt > self.bmax[1] {
357                        self.bmax[1] = vt;
358                    }
359                }
360                v = mesh.verts[v as usize].next;
361            }
362        }
363        true
364    }
365
366    // ─────── Main interior computation ───────────────────────────────────────
367
368    fn compute_interior(&mut self) -> bool {
369        self.sweep_event_num = 0;
370
371        if !self.remove_degenerate_edges() {
372            return false;
373        }
374        if !self.init_priority_queue() {
375            return false;
376        }
377        if !self.init_edge_dict() {
378            return false;
379        }
380
381        loop {
382            if self.pq_is_empty() {
383                break;
384            }
385
386            let v = self.pq_extract_min();
387            if v == INVALID {
388                break;
389            }
390
391            // Coalesce coincident vertices
392            loop {
393                if self.pq_is_empty() {
394                    break;
395                }
396                let next_v = self.pq_minimum();
397                if next_v == INVALID {
398                    break;
399                }
400                let (v_s, v_t) = {
401                    let mesh = self.mesh.as_ref().unwrap();
402                    (mesh.verts[v as usize].s, mesh.verts[v as usize].t)
403                };
404                let (nv_s, nv_t) = {
405                    let mesh = self.mesh.as_ref().unwrap();
406                    (mesh.verts[next_v as usize].s, mesh.verts[next_v as usize].t)
407                };
408                if !vert_eq(v_s, v_t, nv_s, nv_t) {
409                    break;
410                }
411                let next_v = self.pq_extract_min();
412                // Merge next_v into v
413                let an1 = self.mesh.as_ref().unwrap().verts[v as usize].an_edge;
414                let an2 = self.mesh.as_ref().unwrap().verts[next_v as usize].an_edge;
415                if an1 != INVALID && an2 != INVALID {
416                    if !self.mesh.as_mut().unwrap().splice(an1, an2) {
417                        return false;
418                    }
419                }
420            }
421
422            self.event = v;
423            let (v_s, v_t) = {
424                let m = self.mesh.as_ref().unwrap();
425                (m.verts[v as usize].s, m.verts[v as usize].t)
426            };
427            self.event_s = v_s;
428            self.event_t = v_t;
429
430            if !self.sweep_event(v) {
431                return false;
432            }
433        }
434
435        self.done_edge_dict();
436
437        let trace = self.trace_enabled;
438        if let Some(ref mut mesh) = self.mesh {
439            if trace {
440                let mut inside = 0u32;
441                let mut outside = 0u32;
442                let mut f = mesh.faces[crate::mesh::F_HEAD as usize].next;
443                while f != crate::mesh::F_HEAD {
444                    let an = mesh.faces[f as usize].an_edge;
445                    let mut edge_count = 0u32;
446                    if an != INVALID {
447                        let mut e = an;
448                        loop {
449                            edge_count += 1;
450                            e = mesh.edges[e as usize].lnext;
451                            if e == an { break; }
452                            if edge_count > 10000 { break; }
453                        }
454                    }
455                    if mesh.faces[f as usize].inside {
456                        inside += 1;
457                        eprintln!("R FACE inside edges={}", edge_count);
458                    } else {
459                        outside += 1;
460                    }
461                    f = mesh.faces[f as usize].next;
462                }
463                eprintln!("R FACES inside={} outside={}", inside, outside);
464            }
465            if !mesh.tessellate_interior() {
466                return false;
467            }
468            if self.process_cdt {
469                mesh.refine_delaunay();
470            }
471        }
472        true
473    }
474
475    fn remove_degenerate_edges(&mut self) -> bool {
476        // Mirrors C RemoveDegenerateEdges exactly
477        let mesh = match self.mesh.as_mut() {
478            Some(m) => m,
479            None => return true,
480        };
481        let mut e = mesh.edges[E_HEAD as usize].next;
482        while e != E_HEAD {
483            let mut e_next = mesh.edges[e as usize].next;
484            let mut e_lnext = mesh.edges[e as usize].lnext;
485
486            let org = mesh.edges[e as usize].org;
487            let dst = mesh.dst(e);
488            let valid = org != INVALID
489                && dst != INVALID
490                && (org as usize) < mesh.verts.len()
491                && (dst as usize) < mesh.verts.len();
492
493            if valid {
494                let (os, ot) = (mesh.verts[org as usize].s, mesh.verts[org as usize].t);
495                let (ds, dt) = (mesh.verts[dst as usize].s, mesh.verts[dst as usize].t);
496
497                if vert_eq(os, ot, ds, dt) && mesh.edges[e_lnext as usize].lnext != e {
498                    // Zero-length edge, contour has at least 3 edges
499                    mesh.splice(e_lnext, e);
500                    if !mesh.delete_edge(e) {
501                        return false;
502                    }
503                    e = e_lnext;
504                    e_lnext = mesh.edges[e as usize].lnext;
505                }
506            }
507
508            // Degenerate contour (one or two edges): e_lnext->lnext == e
509            let e_lnext_lnext = mesh.edges[e_lnext as usize].lnext;
510            if e_lnext_lnext == e {
511                if e_lnext != e {
512                    // Advance e_next past e_lnext or its sym
513                    if e_lnext == e_next || e_lnext == (e_next ^ 1) {
514                        e_next = mesh.edges[e_next as usize].next;
515                    }
516                    let w1 = mesh.edges[e_lnext as usize].winding;
517                    let w2 = mesh.edges[(e_lnext ^ 1) as usize].winding;
518                    mesh.edges[e as usize].winding += w1;
519                    mesh.edges[(e ^ 1) as usize].winding += w2;
520                    if !mesh.delete_edge(e_lnext) {
521                        return false;
522                    }
523                }
524                // Advance e_next past e or its sym
525                if e == e_next || e == (e_next ^ 1) {
526                    e_next = mesh.edges[e_next as usize].next;
527                }
528                if !mesh.delete_edge(e) {
529                    return false;
530                }
531            }
532
533            e = e_next;
534        }
535        true
536    }
537
538    fn init_priority_queue(&mut self) -> bool {
539        let mesh = match self.mesh.as_ref() {
540            Some(m) => m,
541            None => return true,
542        };
543        let mut count = 0usize;
544        let mut v = mesh.verts[V_HEAD as usize].next;
545        while v != V_HEAD {
546            count += 1;
547            v = mesh.verts[v as usize].next;
548        }
549
550        // Collect (s,t,vert_idx) and sort ascending by vert_leq.
551        let mut vert_coords: Vec<(Real, Real, VertIdx)> = Vec::with_capacity(count);
552        let mut v = mesh.verts[V_HEAD as usize].next;
553        while v != V_HEAD {
554            vert_coords.push((mesh.verts[v as usize].s, mesh.verts[v as usize].t, v));
555            v = mesh.verts[v as usize].next;
556        }
557        drop(mesh);
558
559        vert_coords.sort_unstable_by(|a, b| {
560            if vert_leq(a.0, a.1, b.0, b.1) {
561                std::cmp::Ordering::Less
562            } else {
563                std::cmp::Ordering::Greater
564            }
565        });
566
567        // Build the sorted event queue. Store each vertex's position as a negative
568        // handle (convention: -(index+1)) so that pq_delete can invalidate it.
569        self.sorted_events = vert_coords.iter().map(|&(_, _, v)| v).collect();
570        self.sorted_event_pos = 0;
571        self.intersection_verts.clear();
572        self.next_isect_handle = 0;
573
574        // Assign each initial vertex a handle encoding its sorted_events index.
575        for (idx, &(_, _, v)) in vert_coords.iter().enumerate() {
576            let handle = -(idx as i32 + 1); // negative → sorted_events slot
577            self.mesh.as_mut().unwrap().verts[v as usize].pq_handle = handle;
578        }
579
580        true
581    }
582
583    fn pq_is_empty(&self) -> bool {
584        self.sorted_events_min() == INVALID && self.intersection_verts.is_empty()
585    }
586
587    fn sorted_events_min(&self) -> VertIdx {
588        let mut pos = self.sorted_event_pos;
589        while pos < self.sorted_events.len() {
590            let v = self.sorted_events[pos];
591            if v != INVALID {
592                return v;
593            }
594            pos += 1;
595        }
596        INVALID
597    }
598
599    /// Find the minimum intersection vertex by scanning with coordinate comparison.
600    fn isect_minimum(&self) -> VertIdx {
601        if self.intersection_verts.is_empty() {
602            return INVALID;
603        }
604        let mesh = self.mesh.as_ref().unwrap();
605        let mut best = INVALID;
606        for &v in &self.intersection_verts {
607            if best == INVALID {
608                best = v;
609            } else {
610                let (bs, bt) = (mesh.verts[best as usize].s, mesh.verts[best as usize].t);
611                let (vs, vt) = (mesh.verts[v as usize].s, mesh.verts[v as usize].t);
612                if vert_leq(vs, vt, bs, bt) {
613                    best = v;
614                }
615            }
616        }
617        best
618    }
619
620    fn pq_minimum(&self) -> VertIdx {
621        let sort_min = self.sorted_events_min();
622        let isect_min = self.isect_minimum();
623
624        match (sort_min, isect_min) {
625            (INVALID, INVALID) => INVALID,
626            (INVALID, h) => h,
627            (s, INVALID) => s,
628            (s, h) => {
629                let mesh = self.mesh.as_ref().unwrap();
630                let (ss, st) = (mesh.verts[s as usize].s, mesh.verts[s as usize].t);
631                let (hs, ht) = (mesh.verts[h as usize].s, mesh.verts[h as usize].t);
632                if vert_leq(ss, st, hs, ht) {
633                    s
634                } else {
635                    h
636                }
637            }
638        }
639    }
640
641    fn pq_extract_min(&mut self) -> VertIdx {
642        let v = self.pq_minimum();
643        if v == INVALID {
644            return INVALID;
645        }
646
647        if self.sorted_events_min() == v {
648            while self.sorted_event_pos < self.sorted_events.len() {
649                let s = self.sorted_events[self.sorted_event_pos];
650                self.sorted_event_pos += 1;
651                if s != INVALID {
652                    break;
653                }
654            }
655        } else {
656            // Remove from intersection_verts
657            if let Some(pos) = self.intersection_verts.iter().position(|&x| x == v) {
658                self.intersection_verts.swap_remove(pos);
659            }
660        }
661        v
662    }
663
664    fn pq_delete(&mut self, handle: i32) {
665        if handle >= 0 {
666            // Intersection vertex handle: scan and remove by handle index
667            let vert_idx = handle as u32;
668            if let Some(pos) = self.intersection_verts.iter().position(|&x| x == vert_idx) {
669                self.intersection_verts.swap_remove(pos);
670            }
671        } else {
672            // Sorted-events handle: mark the slot as INVALID
673            let idx = (-(handle + 1)) as usize;
674            if idx < self.sorted_events.len() {
675                self.sorted_events[idx] = INVALID;
676            }
677        }
678    }
679
680    fn pq_insert(&mut self, v: VertIdx) -> i32 {
681        self.intersection_verts.push(v);
682        // Return the VertIdx itself as the handle (positive, so pq_delete knows it's an intersection vertex)
683        v as i32
684    }
685
686    // ─────── Edge dictionary initialization ──────────────────────────────────
687
688    fn add_sentinel(&mut self, smin: Real, smax: Real, t: Real) -> bool {
689        // Mirror C AddSentinel: create a horizontal edge at height t,
690        // going from Org=(smax,t) to Dst=(smin,t), and insert as a sentinel region.
691        let e = match self.mesh.as_mut().unwrap().make_edge() {
692            Some(e) => e,
693            None => return false,
694        };
695        {
696            let mesh = self.mesh.as_mut().unwrap();
697            let org = mesh.edges[e as usize].org;
698            let dst = mesh.dst(e);
699            mesh.verts[org as usize].s = smax;
700            mesh.verts[org as usize].t = t;
701            mesh.verts[dst as usize].s = smin;
702            mesh.verts[dst as usize].t = t;
703        }
704        // Set the event to Dst (as C does) so edge_leq works during insertion
705        let dst = self.mesh.as_ref().unwrap().dst(e);
706        let (dst_s, dst_t) = {
707            let m = self.mesh.as_ref().unwrap();
708            (m.verts[dst as usize].s, m.verts[dst as usize].t)
709        };
710        self.event = dst;
711        self.event_s = dst_s;
712        self.event_t = dst_t;
713
714        let reg = self.alloc_region();
715        {
716            let r = self.region_mut(reg);
717            r.e_up = e;
718            r.winding_number = 0;
719            r.inside = false;
720            r.sentinel = true;
721            r.dirty = false;
722            r.fix_upper_edge = false;
723        }
724
725        // Insert the region into the dict using edge_leq ordering
726        let node = self.dict_insert_region(reg);
727        if node == INVALID {
728            return false;
729        }
730        self.region_mut(reg).node_up = node;
731
732        // Set the edge's active_region so it's recognized as a sentinel edge
733        self.mesh.as_mut().unwrap().edges[e as usize].active_region = reg;
734        true
735    }
736
737    /// Insert a region into the dict at the sorted position (using edge_leq).
738    /// Starts search from DICT_HEAD (tail). Returns the new node index.
739    fn dict_insert_region(&mut self, reg: RegionIdx) -> NodeIdx {
740        self.dict_insert_before(reg, DICT_HEAD)
741    }
742
743    /// Insert a region before `start_node` in the dict, walking backward
744    /// until the correct sorted position is found. Mirrors C's dictInsertBefore.
745    fn dict_insert_before(&mut self, reg: RegionIdx, start_node: NodeIdx) -> NodeIdx {
746        let max_dict_iters = self.dict.nodes.len() + 2;
747        let mut node = start_node;
748        let mut dict_iter = 0usize;
749        loop {
750            node = self.dict.nodes[node as usize].prev;
751            let key = self.dict.nodes[node as usize].key;
752            if key == INVALID {
753                break; // hit head sentinel
754            }
755            if self.edge_leq(key, reg) {
756                break;
757            }
758            dict_iter += 1;
759            if dict_iter > max_dict_iters {
760                break; // degenerate dict list — avoid infinite walk
761            }
762        }
763        // Insert after `node`
764        let after = node;
765        let before = self.dict.nodes[after as usize].next;
766        let new_node = self.dict.nodes.len() as NodeIdx;
767        use crate::dict::DictNode;
768        let new_dict_node = DictNode {
769            key: reg,
770            next: before,
771            prev: after,
772        };
773        self.dict.nodes.push(new_dict_node);
774        self.dict.nodes[after as usize].next = new_node;
775        self.dict.nodes[before as usize].prev = new_node;
776        new_node
777    }
778
779    fn init_edge_dict(&mut self) -> bool {
780        self.dict = Dict::new();
781
782        // Compute sentinel bounds from bounding box + margin (mirrors C InitEdgeDict)
783        let w = (self.bmax[0] - self.bmin[0]) + 0.01;
784        let h = (self.bmax[1] - self.bmin[1]) + 0.01;
785        let smin = self.bmin[0] - w;
786        let smax = self.bmax[0] + w;
787        let tmin = self.bmin[1] - h;
788        let tmax = self.bmax[1] + h;
789
790        // Add bottom sentinel first (at tmin), then top sentinel (at tmax).
791        // After insertion with EdgeLeq ordering, top ends up before bottom in the dict.
792        if !self.add_sentinel(smin, smax, tmin) {
793            return false;
794        }
795        if !self.add_sentinel(smin, smax, tmax) {
796            return false;
797        }
798
799        true
800    }
801
802    fn done_edge_dict(&mut self) {
803        // Remove all sentinel regions
804        let mut node = self.dict.min();
805        while node != DICT_HEAD {
806            let key = self.dict.key(node);
807            let next = self.dict.succ(node);
808            if key != INVALID {
809                let is_sentinel = self.region(key).sentinel;
810                if is_sentinel {
811                    self.dict.delete(node);
812                    self.free_region(key);
813                }
814            }
815            node = next;
816        }
817    }
818
819    // ─────── Region operations ────────────────────────────────────────────────
820
821    fn alloc_region(&mut self) -> RegionIdx {
822        if let Some(idx) = self.region_free.pop() {
823            self.regions[idx as usize] = Some(ActiveRegion::default());
824            idx
825        } else {
826            let idx = self.regions.len() as RegionIdx;
827            self.regions.push(Some(ActiveRegion::default()));
828            idx
829        }
830    }
831
832    fn free_region(&mut self, idx: RegionIdx) {
833        if idx != INVALID {
834            self.regions[idx as usize] = None;
835            self.region_free.push(idx);
836        }
837    }
838
839    fn region(&self, idx: RegionIdx) -> &ActiveRegion {
840        self.regions[idx as usize].as_ref().unwrap()
841    }
842
843    fn region_mut(&mut self, idx: RegionIdx) -> &mut ActiveRegion {
844        self.regions[idx as usize].as_mut().unwrap()
845    }
846
847    /// Returns the region index of the dict node's successor region.
848    fn region_above(&self, reg: RegionIdx) -> RegionIdx {
849        let node = self.region(reg).node_up;
850        self.dict.key(self.dict.succ(node))
851    }
852
853    /// Returns the region index of the dict node's predecessor region.
854    fn region_below(&self, reg: RegionIdx) -> RegionIdx {
855        let node = self.region(reg).node_up;
856        self.dict.key(self.dict.pred(node))
857    }
858
859    /// EdgeLeq: Returns reg1 <= reg2 at the current sweep position (event).
860    fn edge_leq(&self, reg1: RegionIdx, reg2: RegionIdx) -> bool {
861        let e1 = self.region(reg1).e_up;
862        let e2 = self.region(reg2).e_up;
863        if e1 == INVALID {
864            return true;
865        }
866        if e2 == INVALID {
867            return false;
868        }
869        let mesh = self.mesh.as_ref().unwrap();
870
871        let e1_dst = mesh.dst(e1);
872        let e2_dst = mesh.dst(e2);
873        let e1_org = mesh.edges[e1 as usize].org;
874        let e2_org = mesh.edges[e2 as usize].org;
875
876        let ev_s = self.event_s;
877        let ev_t = self.event_t;
878
879        let (e1ds, e1dt) = (mesh.verts[e1_dst as usize].s, mesh.verts[e1_dst as usize].t);
880        let (e2ds, e2dt) = (mesh.verts[e2_dst as usize].s, mesh.verts[e2_dst as usize].t);
881        let (e1os, e1ot) = (mesh.verts[e1_org as usize].s, mesh.verts[e1_org as usize].t);
882        let (e2os, e2ot) = (mesh.verts[e2_org as usize].s, mesh.verts[e2_org as usize].t);
883
884        if vert_eq(e1ds, e1dt, ev_s, ev_t) {
885            if vert_eq(e2ds, e2dt, ev_s, ev_t) {
886                if vert_leq(e1os, e1ot, e2os, e2ot) {
887                    return edge_sign(e2ds, e2dt, e1os, e1ot, e2os, e2ot) <= 0.0;
888                }
889                return edge_sign(e1ds, e1dt, e2os, e2ot, e1os, e1ot) >= 0.0;
890            }
891            return edge_sign(e2ds, e2dt, ev_s, ev_t, e2os, e2ot) <= 0.0;
892        }
893        if vert_eq(e2ds, e2dt, ev_s, ev_t) {
894            return edge_sign(e1ds, e1dt, ev_s, ev_t, e1os, e1ot) >= 0.0;
895        }
896        let t1 = crate::geom::edge_eval(e1ds, e1dt, ev_s, ev_t, e1os, e1ot);
897        let t2 = crate::geom::edge_eval(e2ds, e2dt, ev_s, ev_t, e2os, e2ot);
898        t1 >= t2
899    }
900
901    /// Insert a new region below `reg_above` with upper edge `e_new_up`.
902    /// Mirrors C's AddRegionBelow + ComputeWinding.
903    fn add_region_below(&mut self, _reg_above: RegionIdx, e_new_up: EdgeIdx) -> RegionIdx {
904        let reg_new = self.alloc_region();
905        {
906            let r = self.region_mut(reg_new);
907            r.e_up = e_new_up;
908            r.fix_upper_edge = false;
909            r.sentinel = false;
910            r.dirty = false;
911        }
912
913        let new_node_idx = self.dict_insert_region(reg_new);
914        if new_node_idx == INVALID {
915            self.free_region(reg_new);
916            return INVALID;
917        }
918        self.region_mut(reg_new).node_up = new_node_idx;
919
920        // Link the edge to the region
921        self.mesh.as_mut().unwrap().edges[e_new_up as usize].active_region = reg_new;
922
923        self.compute_winding(reg_new);
924
925        reg_new
926    }
927
928    fn delete_region(&mut self, reg: RegionIdx) {
929        if self.region(reg).fix_upper_edge {
930            // Was created with zero winding - must be deleted with zero winding
931        }
932        let e_up = self.region(reg).e_up;
933        if e_up != INVALID {
934            self.mesh.as_mut().unwrap().edges[e_up as usize].active_region = INVALID;
935        }
936        let node = self.region(reg).node_up;
937        self.dict.delete(node);
938        self.free_region(reg);
939    }
940
941    fn fix_upper_edge(&mut self, reg: RegionIdx, new_edge: EdgeIdx) -> bool {
942        let old_edge = self.region(reg).e_up;
943        if old_edge != INVALID {
944            if !self.mesh.as_mut().unwrap().delete_edge(old_edge) {
945                return false;
946            }
947        }
948        self.region_mut(reg).fix_upper_edge = false;
949        self.region_mut(reg).e_up = new_edge;
950        self.mesh.as_mut().unwrap().edges[new_edge as usize].active_region = reg;
951        true
952    }
953
954    fn is_winding_inside(&self, n: i32) -> bool {
955        match self.winding_rule {
956            WindingRule::Odd => n & 1 != 0,
957            WindingRule::NonZero => n != 0,
958            WindingRule::Positive => n > 0,
959            WindingRule::Negative => n < 0,
960            WindingRule::AbsGeqTwo => n >= 2 || n <= -2,
961        }
962    }
963
964    fn compute_winding(&mut self, reg: RegionIdx) {
965        let above = self.region_above(reg);
966        let above_winding = if above != INVALID {
967            self.region(above).winding_number
968        } else {
969            0
970        };
971        let e_up = self.region(reg).e_up;
972        let e_winding = if e_up != INVALID {
973            self.mesh.as_ref().unwrap().edges[e_up as usize].winding
974        } else {
975            0
976        };
977        let new_winding = above_winding + e_winding;
978        let inside = self.is_winding_inside(new_winding);
979        if self.trace_enabled {
980            eprintln!(
981                "R   COMPUTE_WINDING winding={} inside={} edge_winding={}",
982                new_winding, inside as i32, e_winding
983            );
984        }
985        self.region_mut(reg).winding_number = new_winding;
986        self.region_mut(reg).inside = inside;
987    }
988
989    fn finish_region(&mut self, reg: RegionIdx) {
990        let e = self.region(reg).e_up;
991        if e != INVALID {
992            let lface = self.mesh.as_ref().unwrap().edges[e as usize].lface;
993            if lface != INVALID {
994                let inside = self.region(reg).inside;
995                if self.trace_enabled {
996                    let mesh = self.mesh.as_ref().unwrap();
997                    let mut edge_count = 0u32;
998                    let an = mesh.faces[lface as usize].an_edge;
999                    if an != INVALID {
1000                        let mut iter = an;
1001                        loop {
1002                            edge_count += 1;
1003                            iter = mesh.edges[iter as usize].lnext;
1004                            if iter == an || edge_count > 10000 { break; }
1005                        }
1006                    }
1007                    let org = mesh.edges[e as usize].org;
1008                    let (os, ot) = if org != INVALID {
1009                        (mesh.verts[org as usize].s, mesh.verts[org as usize].t)
1010                    } else {
1011                        (0.0, 0.0)
1012                    };
1013                    eprintln!(
1014                        "R   FINISH_REGION inside={} winding={} face_edges={} eUp_org=({:.2},{:.2})",
1015                        inside as i32,
1016                        self.region(reg).winding_number,
1017                        edge_count,
1018                        os, ot
1019                    );
1020                }
1021                self.mesh.as_mut().unwrap().faces[lface as usize].inside = inside;
1022                self.mesh.as_mut().unwrap().faces[lface as usize].an_edge = e;
1023            }
1024        }
1025        self.delete_region(reg);
1026    }
1027
1028    /// Find topmost region with same Org as reg->eUp->Org.
1029    fn top_left_region(&mut self, reg: RegionIdx) -> RegionIdx {
1030        let org = {
1031            let e = self.region(reg).e_up;
1032            if e == INVALID {
1033                return INVALID;
1034            }
1035            self.mesh.as_ref().unwrap().edges[e as usize].org
1036        };
1037        let max_region_iters = self.regions.len() + 2;
1038        let mut r = reg;
1039        let mut region_iter = 0usize;
1040        loop {
1041            r = self.region_above(r);
1042            if r == INVALID {
1043                return INVALID;
1044            }
1045            let e = self.region(r).e_up;
1046            if e == INVALID {
1047                return INVALID;
1048            }
1049            let e_org = self.mesh.as_ref().unwrap().edges[e as usize].org;
1050            if e_org != org {
1051                break;
1052            }
1053            region_iter += 1;
1054            if region_iter > max_region_iters {
1055                return INVALID; // degenerate region chain
1056            }
1057        }
1058        // r is now above the topmost region with same origin
1059        // Check if we need to fix it
1060        if self.region(r).fix_upper_edge {
1061            let below = self.region_below(r);
1062            let below_e = self.region(below).e_up;
1063            let below_e_sym = below_e ^ 1;
1064            let r_e = self.region(r).e_up;
1065            let r_e_lnext = self.mesh.as_ref().unwrap().edges[r_e as usize].lnext;
1066            let new_e = match self.mesh.as_mut().unwrap().connect(below_e_sym, r_e_lnext) {
1067                Some(e) => e,
1068                None => return INVALID,
1069            };
1070            if !self.fix_upper_edge(r, new_e) {
1071                return INVALID;
1072            }
1073            r = self.region_above(r);
1074        }
1075        r
1076    }
1077
1078    fn top_right_region(&self, reg: RegionIdx) -> RegionIdx {
1079        let dst = {
1080            let e = self.region(reg).e_up;
1081            if e == INVALID {
1082                return INVALID;
1083            }
1084            self.mesh.as_ref().unwrap().dst(e)
1085        };
1086        let max_region_iters = self.regions.len() + 2;
1087        let mut r = reg;
1088        let mut region_iter = 0usize;
1089        loop {
1090            r = self.region_above(r);
1091            if r == INVALID {
1092                return INVALID;
1093            }
1094            let e = self.region(r).e_up;
1095            if e == INVALID {
1096                return INVALID;
1097            }
1098            let e_dst = self.mesh.as_ref().unwrap().dst(e);
1099            if e_dst != dst {
1100                break;
1101            }
1102            region_iter += 1;
1103            if region_iter > max_region_iters {
1104                return INVALID; // degenerate region chain
1105            }
1106        }
1107        r
1108    }
1109
1110    fn finish_left_regions(&mut self, reg_first: RegionIdx, reg_last: RegionIdx) -> EdgeIdx {
1111        let mut reg_prev = reg_first;
1112        let mut e_prev = self.region(reg_first).e_up;
1113
1114        while reg_prev != reg_last {
1115            self.region_mut(reg_prev).fix_upper_edge = false;
1116            let reg = self.region_below(reg_prev);
1117            if reg == INVALID {
1118                break;
1119            }
1120            let e = self.region(reg).e_up;
1121
1122            let e_org = if e != INVALID {
1123                self.mesh.as_ref().unwrap().edges[e as usize].org
1124            } else {
1125                INVALID
1126            };
1127            let ep_org = if e_prev != INVALID {
1128                self.mesh.as_ref().unwrap().edges[e_prev as usize].org
1129            } else {
1130                INVALID
1131            };
1132
1133            if e_org != ep_org {
1134                if !self.region(reg).fix_upper_edge {
1135                    self.finish_region(reg_prev);
1136                    break;
1137                }
1138                let ep_lprev = if e_prev != INVALID {
1139                    self.mesh.as_ref().unwrap().lprev(e_prev)
1140                } else {
1141                    INVALID
1142                };
1143                let e_sym = if e != INVALID { e ^ 1 } else { INVALID };
1144                let new_e = if ep_lprev != INVALID && e_sym != INVALID {
1145                    self.mesh.as_mut().unwrap().connect(ep_lprev, e_sym)
1146                } else {
1147                    None
1148                };
1149                if let Some(ne) = new_e {
1150                    if !self.fix_upper_edge(reg, ne) {
1151                        return INVALID;
1152                    }
1153                }
1154            }
1155
1156            if e_prev != INVALID && e != INVALID {
1157                let ep_onext = self.mesh.as_ref().unwrap().edges[e_prev as usize].onext;
1158                if ep_onext != e {
1159                    let e_oprev = self.mesh.as_ref().unwrap().oprev(e);
1160                    self.mesh.as_mut().unwrap().splice(e_oprev, e);
1161                    self.mesh.as_mut().unwrap().splice(e_prev, e);
1162                }
1163            }
1164
1165            self.finish_region(reg_prev);
1166            e_prev = self.region(reg).e_up;
1167            reg_prev = reg;
1168        }
1169        e_prev
1170    }
1171
1172    fn add_right_edges(
1173        &mut self,
1174        reg_up: RegionIdx,
1175        e_first: EdgeIdx,
1176        e_last: EdgeIdx,
1177        e_top_left: EdgeIdx,
1178        clean_up: bool,
1179    ) {
1180        // Insert right-going edges into the dictionary.
1181        // Guard: the onext ring must contain e_last; if it doesn't
1182        // (degenerate mesh), break early rather than looping forever.
1183        let max_edge_iters = self.mesh.as_ref().unwrap().edges.len() + 2;
1184        let mut e = e_first;
1185        let mut edge_iter = 0usize;
1186        loop {
1187            self.add_region_below(reg_up, e ^ 1);
1188            e = self.mesh.as_ref().unwrap().edges[e as usize].onext;
1189            if e == e_last {
1190                break;
1191            }
1192            edge_iter += 1;
1193            if edge_iter > max_edge_iters {
1194                break; // degenerate onext ring — skip remaining edges
1195            }
1196        }
1197
1198        // Determine e_top_left
1199        let e_top_left = if e_top_left == INVALID {
1200            let reg_below = self.region_below(reg_up);
1201            if reg_below == INVALID {
1202                return;
1203            }
1204            let rb_e = self.region(reg_below).e_up;
1205            if rb_e == INVALID {
1206                return;
1207            }
1208            self.mesh.as_ref().unwrap().rprev(rb_e)
1209        } else {
1210            e_top_left
1211        };
1212
1213        let mut reg_prev = reg_up;
1214        let mut e_prev = e_top_left;
1215        let mut first_time = true;
1216        let max_reg_iters = self.regions.len() + 2;
1217        let mut reg_iter2 = 0usize;
1218
1219        loop {
1220            let reg = self.region_below(reg_prev);
1221            if reg == INVALID {
1222                break;
1223            }
1224            let e = {
1225                let re = self.region(reg).e_up;
1226                if re == INVALID {
1227                    break;
1228                }
1229                re ^ 1 // e = reg->eUp->Sym
1230            };
1231            let e_org = self.mesh.as_ref().unwrap().edges[e as usize].org;
1232            let ep_org = if e_prev != INVALID {
1233                self.mesh.as_ref().unwrap().edges[e_prev as usize].org
1234            } else {
1235                INVALID
1236            };
1237            if e_org != ep_org {
1238                break;
1239            }
1240            reg_iter2 += 1;
1241            if reg_iter2 > max_reg_iters {
1242                break; // degenerate region chain
1243            }
1244
1245            if e_prev != INVALID {
1246                // C: if( e->Onext != ePrev ) { splice(e->Oprev, e); splice(ePrev->Oprev, e); }
1247                let e_onext = self.mesh.as_ref().unwrap().edges[e as usize].onext;
1248                if e_onext != e_prev {
1249                    let e_oprev = self.mesh.as_ref().unwrap().oprev(e);
1250                    self.mesh.as_mut().unwrap().splice(e_oprev, e);
1251                    let ep_oprev = self.mesh.as_ref().unwrap().oprev(e_prev);
1252                    self.mesh.as_mut().unwrap().splice(ep_oprev, e);
1253                }
1254            }
1255
1256            let above_winding = self.region(reg_prev).winding_number;
1257            let e_winding = self.mesh.as_ref().unwrap().edges[e as usize].winding;
1258            let new_winding = above_winding - e_winding;
1259            let inside = self.is_winding_inside(new_winding);
1260            self.region_mut(reg).winding_number = new_winding;
1261            self.region_mut(reg).inside = inside;
1262
1263            self.region_mut(reg_prev).dirty = true;
1264            if !first_time {
1265                if self.check_for_right_splice(reg_prev) {
1266                    // AddWinding
1267                    let re = self.region(reg).e_up;
1268                    let rep = self.region(reg_prev).e_up;
1269                    if re != INVALID && rep != INVALID {
1270                        let w1 = self.mesh.as_ref().unwrap().edges[re as usize].winding;
1271                        let w2 = self.mesh.as_ref().unwrap().edges[(re ^ 1) as usize].winding;
1272                        let wp1 = self.mesh.as_ref().unwrap().edges[rep as usize].winding;
1273                        let wp2 = self.mesh.as_ref().unwrap().edges[(rep ^ 1) as usize].winding;
1274                        self.mesh.as_mut().unwrap().edges[re as usize].winding += wp1;
1275                        self.mesh.as_mut().unwrap().edges[(re ^ 1) as usize].winding += wp2;
1276                    }
1277                    self.delete_region(reg_prev);
1278                    if e_prev != INVALID {
1279                        self.mesh.as_mut().unwrap().delete_edge(e_prev);
1280                    }
1281                }
1282            }
1283            first_time = false;
1284            reg_prev = reg;
1285            e_prev = e;
1286        }
1287
1288        self.region_mut(reg_prev).dirty = true;
1289
1290        if clean_up {
1291            self.walk_dirty_regions(reg_prev);
1292        }
1293    }
1294
1295    fn check_for_right_splice(&mut self, reg_up: RegionIdx) -> bool {
1296        let reg_lo = self.region_below(reg_up);
1297        if reg_lo == INVALID {
1298            return false;
1299        }
1300        let e_up = self.region(reg_up).e_up;
1301        let e_lo = self.region(reg_lo).e_up;
1302        if e_up == INVALID || e_lo == INVALID {
1303            return false;
1304        }
1305
1306        let mesh = self.mesh.as_ref().unwrap();
1307        let e_up_org = mesh.edges[e_up as usize].org;
1308        let e_lo_org = mesh.edges[e_lo as usize].org;
1309        let (euo_s, euo_t) = (
1310            mesh.verts[e_up_org as usize].s,
1311            mesh.verts[e_up_org as usize].t,
1312        );
1313        let (elo_s, elo_t) = (
1314            mesh.verts[e_lo_org as usize].s,
1315            mesh.verts[e_lo_org as usize].t,
1316        );
1317        let e_lo_dst = mesh.dst(e_lo);
1318        let (eld_s, eld_t) = (
1319            mesh.verts[e_lo_dst as usize].s,
1320            mesh.verts[e_lo_dst as usize].t,
1321        );
1322        let e_up_dst = mesh.dst(e_up);
1323        let (eud_s, eud_t) = (
1324            mesh.verts[e_up_dst as usize].s,
1325            mesh.verts[e_up_dst as usize].t,
1326        );
1327        drop(mesh);
1328
1329        if vert_leq(euo_s, euo_t, elo_s, elo_t) {
1330            if edge_sign(eld_s, eld_t, euo_s, euo_t, elo_s, elo_t) > 0.0 {
1331                return false;
1332            }
1333            if !vert_eq(euo_s, euo_t, elo_s, elo_t) {
1334                // Splice eUp->Org into eLo
1335                self.mesh.as_mut().unwrap().split_edge(e_lo ^ 1);
1336                let e_lo_oprev = self.mesh.as_ref().unwrap().oprev(e_lo);
1337                self.mesh.as_mut().unwrap().splice(e_up, e_lo_oprev);
1338                self.region_mut(reg_up).dirty = true;
1339                self.region_mut(reg_lo).dirty = true;
1340            } else if e_up_org != e_lo_org {
1341                // Merge: delete eUp->Org from PQ and splice
1342                let handle = self.mesh.as_ref().unwrap().verts[e_up_org as usize].pq_handle;
1343                self.pq_delete(handle);
1344                let e_lo_oprev = self.mesh.as_ref().unwrap().oprev(e_lo);
1345                self.mesh.as_mut().unwrap().splice(e_lo_oprev, e_up);
1346            }
1347        } else {
1348            if edge_sign(eud_s, eud_t, elo_s, elo_t, euo_s, euo_t) < 0.0 {
1349                return false;
1350            }
1351            let reg_above = self.region_above(reg_up);
1352            if reg_above != INVALID {
1353                self.region_mut(reg_above).dirty = true;
1354            }
1355            self.region_mut(reg_up).dirty = true;
1356            self.mesh.as_mut().unwrap().split_edge(e_up ^ 1);
1357            let e_lo_oprev = self.mesh.as_ref().unwrap().oprev(e_lo);
1358            self.mesh.as_mut().unwrap().splice(e_lo_oprev, e_up);
1359        }
1360        true
1361    }
1362
1363    fn check_for_left_splice(&mut self, reg_up: RegionIdx) -> bool {
1364        let reg_lo = self.region_below(reg_up);
1365        if reg_lo == INVALID {
1366            return false;
1367        }
1368        let e_up = self.region(reg_up).e_up;
1369        let e_lo = self.region(reg_lo).e_up;
1370        if e_up == INVALID || e_lo == INVALID {
1371            return false;
1372        }
1373
1374        let mesh = self.mesh.as_ref().unwrap();
1375        let e_up_dst = mesh.dst(e_up);
1376        let e_lo_dst = mesh.dst(e_lo);
1377        if vert_eq(
1378            mesh.verts[e_up_dst as usize].s,
1379            mesh.verts[e_up_dst as usize].t,
1380            mesh.verts[e_lo_dst as usize].s,
1381            mesh.verts[e_lo_dst as usize].t,
1382        ) {
1383            return false;
1384        } // Same destination
1385
1386        let (eud_s, eud_t) = (
1387            mesh.verts[e_up_dst as usize].s,
1388            mesh.verts[e_up_dst as usize].t,
1389        );
1390        let (eld_s, eld_t) = (
1391            mesh.verts[e_lo_dst as usize].s,
1392            mesh.verts[e_lo_dst as usize].t,
1393        );
1394        let e_up_org = mesh.edges[e_up as usize].org;
1395        let e_lo_org = mesh.edges[e_lo as usize].org;
1396        let (euo_s, euo_t) = (
1397            mesh.verts[e_up_org as usize].s,
1398            mesh.verts[e_up_org as usize].t,
1399        );
1400        let (elo_s, elo_t) = (
1401            mesh.verts[e_lo_org as usize].s,
1402            mesh.verts[e_lo_org as usize].t,
1403        );
1404        drop(mesh);
1405
1406        if vert_leq(eud_s, eud_t, eld_s, eld_t) {
1407            if edge_sign(eud_s, eud_t, eld_s, eld_t, euo_s, euo_t) < 0.0 {
1408                return false;
1409            }
1410            // eLo->Dst is above eUp: splice eLo->Dst into eUp
1411            let reg_above = self.region_above(reg_up);
1412            if reg_above != INVALID {
1413                self.region_mut(reg_above).dirty = true;
1414            }
1415            self.region_mut(reg_up).dirty = true;
1416            let new_e = match self.mesh.as_mut().unwrap().split_edge(e_up) {
1417                Some(e) => e,
1418                None => return false,
1419            };
1420            let e_lo_sym = e_lo ^ 1;
1421            self.mesh.as_mut().unwrap().splice(e_lo_sym, new_e);
1422            let new_lface = self.mesh.as_ref().unwrap().edges[new_e as usize].lface;
1423            let inside = self.region(reg_up).inside;
1424            if new_lface != INVALID {
1425                self.mesh.as_mut().unwrap().faces[new_lface as usize].inside = inside;
1426            }
1427        } else {
1428            if edge_sign(eld_s, eld_t, eud_s, eud_t, elo_s, elo_t) > 0.0 {
1429                return false;
1430            }
1431            // eUp->Dst is below eLo: splice eUp->Dst into eLo
1432            self.region_mut(reg_up).dirty = true;
1433            self.region_mut(reg_lo).dirty = true;
1434            let new_e = match self.mesh.as_mut().unwrap().split_edge(e_lo) {
1435                Some(e) => e,
1436                None => return false,
1437            };
1438            let e_up_lnext = self.mesh.as_ref().unwrap().edges[e_up as usize].lnext;
1439            let e_lo_sym = e_lo ^ 1;
1440            self.mesh.as_mut().unwrap().splice(e_up_lnext, e_lo_sym);
1441            let new_rface = self.mesh.as_ref().unwrap().rface(new_e);
1442            let inside = self.region(reg_up).inside;
1443            if new_rface != INVALID {
1444                self.mesh.as_mut().unwrap().faces[new_rface as usize].inside = inside;
1445            }
1446        }
1447        true
1448    }
1449
1450    fn check_for_intersect(&mut self, reg_up: RegionIdx) -> bool {
1451        let reg_lo = self.region_below(reg_up);
1452        if reg_lo == INVALID {
1453            return false;
1454        }
1455        let e_up = self.region(reg_up).e_up;
1456        let e_lo = self.region(reg_lo).e_up;
1457        if e_up == INVALID || e_lo == INVALID {
1458            return false;
1459        }
1460        if self.region(reg_up).fix_upper_edge || self.region(reg_lo).fix_upper_edge {
1461            return false;
1462        }
1463
1464        let mesh = self.mesh.as_ref().unwrap();
1465        let org_up = mesh.edges[e_up as usize].org;
1466        let org_lo = mesh.edges[e_lo as usize].org;
1467        let dst_up = mesh.dst(e_up);
1468        let dst_lo = mesh.dst(e_lo);
1469
1470        if vert_eq(
1471            mesh.verts[dst_up as usize].s,
1472            mesh.verts[dst_up as usize].t,
1473            mesh.verts[dst_lo as usize].s,
1474            mesh.verts[dst_lo as usize].t,
1475        ) {
1476            return false;
1477        }
1478
1479        let (ou_s, ou_t) = (mesh.verts[org_up as usize].s, mesh.verts[org_up as usize].t);
1480        let (ol_s, ol_t) = (mesh.verts[org_lo as usize].s, mesh.verts[org_lo as usize].t);
1481        let (du_s, du_t) = (mesh.verts[dst_up as usize].s, mesh.verts[dst_up as usize].t);
1482        let (dl_s, dl_t) = (mesh.verts[dst_lo as usize].s, mesh.verts[dst_lo as usize].t);
1483        // Save coords of all 4 endpoints before the mesh is mutated by split_edge.
1484        let ou_coords = mesh.verts[org_up as usize].coords;
1485        let du_coords = mesh.verts[dst_up as usize].coords;
1486        let ol_coords = mesh.verts[org_lo as usize].coords;
1487        let dl_coords = mesh.verts[dst_lo as usize].coords;
1488        let ev_s = self.event_s;
1489        let ev_t = self.event_t;
1490        drop(mesh);
1491
1492        // Quick rejection tests
1493        let t_min_up = ou_t.min(du_t);
1494        let t_max_lo = ol_t.max(dl_t);
1495        if t_min_up > t_max_lo {
1496            return false;
1497        }
1498
1499        if vert_leq(ou_s, ou_t, ol_s, ol_t) {
1500            if edge_sign(dl_s, dl_t, ou_s, ou_t, ol_s, ol_t) > 0.0 {
1501                return false;
1502            }
1503        } else {
1504            if edge_sign(du_s, du_t, ol_s, ol_t, ou_s, ou_t) < 0.0 {
1505                return false;
1506            }
1507        }
1508
1509        // Compute intersection
1510        let (isect_s, isect_t) = edge_intersect(du_s, du_t, ou_s, ou_t, dl_s, dl_t, ol_s, ol_t);
1511
1512        // Clamp intersection to sweep event position
1513        let (isect_s, isect_t) = if vert_leq(isect_s, isect_t, ev_s, ev_t) {
1514            (ev_s, ev_t)
1515        } else {
1516            (isect_s, isect_t)
1517        };
1518
1519        // Clamp to rightmost origin
1520        let (org_min_s, org_min_t) = if vert_leq(ou_s, ou_t, ol_s, ol_t) {
1521            (ou_s, ou_t)
1522        } else {
1523            (ol_s, ol_t)
1524        };
1525        let (isect_s, isect_t) = if vert_leq(org_min_s, org_min_t, isect_s, isect_t) {
1526            (org_min_s, org_min_t)
1527        } else {
1528            (isect_s, isect_t)
1529        };
1530
1531        // Check if intersection is at one of the endpoints
1532        if vert_eq(isect_s, isect_t, ou_s, ou_t) || vert_eq(isect_s, isect_t, ol_s, ol_t) {
1533            self.check_for_right_splice(reg_up);
1534            return false;
1535        }
1536
1537        if (!vert_eq(du_s, du_t, ev_s, ev_t)
1538            && edge_sign(du_s, du_t, ev_s, ev_t, isect_s, isect_t) >= 0.0)
1539            || (!vert_eq(dl_s, dl_t, ev_s, ev_t)
1540                && edge_sign(dl_s, dl_t, ev_s, ev_t, isect_s, isect_t) <= 0.0)
1541        {
1542            if vert_eq(dl_s, dl_t, ev_s, ev_t) {
1543                // Splice dstLo into eUp
1544                self.mesh.as_mut().unwrap().split_edge(e_up ^ 1);
1545                let e_lo_sym = e_lo ^ 1;
1546                let e_up2 = self.region(reg_up).e_up;
1547                self.mesh.as_mut().unwrap().splice(e_lo_sym, e_up2);
1548                let reg_up2 = self.top_left_region(reg_up);
1549                if reg_up2 == INVALID {
1550                    return false;
1551                }
1552                let rb = self.region_below(reg_up2);
1553                let rb_e = self.region(rb).e_up;
1554                let rl2 = self.region_below(rb);
1555                self.finish_left_regions(self.region_below(reg_up2), reg_lo);
1556                let e_up_new = self.region(rb).e_up;
1557                let e_oprev = self.mesh.as_ref().unwrap().oprev(e_up_new);
1558                self.add_right_edges(reg_up2, e_oprev, e_up_new, e_up_new, true);
1559                return true;
1560            }
1561            if vert_eq(du_s, du_t, ev_s, ev_t) {
1562                self.mesh.as_mut().unwrap().split_edge(e_lo ^ 1);
1563                let e_up_lnext = self.mesh.as_ref().unwrap().edges[e_up as usize].lnext;
1564                let e_lo_oprev = self.mesh.as_ref().unwrap().oprev(e_lo);
1565                self.mesh.as_mut().unwrap().splice(e_up_lnext, e_lo_oprev);
1566                let reg_lo2 = reg_up;
1567                let reg_up2 = self.top_right_region(reg_up);
1568                if reg_up2 == INVALID {
1569                    return false;
1570                }
1571                let e_finish = self
1572                    .mesh
1573                    .as_ref()
1574                    .unwrap()
1575                    .rprev(self.region(self.region_below(reg_up2)).e_up);
1576                self.region_mut(reg_lo2).e_up = self.mesh.as_ref().unwrap().oprev(e_lo);
1577                let lo_end = self.finish_left_regions(reg_lo2, INVALID);
1578                let e_lo_onext = if lo_end != INVALID {
1579                    self.mesh.as_ref().unwrap().edges[lo_end as usize].onext
1580                } else {
1581                    INVALID
1582                };
1583                let e_up_rprev = self.mesh.as_ref().unwrap().rprev(e_up);
1584                self.add_right_edges(reg_up2, e_lo_onext, e_up_rprev, e_finish, true);
1585                return true;
1586            }
1587            // Split edges
1588            if edge_sign(du_s, du_t, ev_s, ev_t, isect_s, isect_t) >= 0.0 {
1589                let reg_above = self.region_above(reg_up);
1590                if reg_above != INVALID {
1591                    self.region_mut(reg_above).dirty = true;
1592                }
1593                self.region_mut(reg_up).dirty = true;
1594                self.mesh.as_mut().unwrap().split_edge(e_up ^ 1);
1595                let e_up2 = self.region(reg_up).e_up;
1596                let e_up2_org = self.mesh.as_ref().unwrap().edges[e_up2 as usize].org;
1597                self.mesh.as_mut().unwrap().verts[e_up2_org as usize].s = ev_s;
1598                self.mesh.as_mut().unwrap().verts[e_up2_org as usize].t = ev_t;
1599            }
1600            if edge_sign(dl_s, dl_t, ev_s, ev_t, isect_s, isect_t) <= 0.0 {
1601                self.region_mut(reg_up).dirty = true;
1602                self.region_mut(reg_lo).dirty = true;
1603                self.mesh.as_mut().unwrap().split_edge(e_lo ^ 1);
1604                let e_lo2 = self.region(reg_lo).e_up;
1605                let e_lo2_org = self.mesh.as_ref().unwrap().edges[e_lo2 as usize].org;
1606                self.mesh.as_mut().unwrap().verts[e_lo2_org as usize].s = ev_s;
1607                self.mesh.as_mut().unwrap().verts[e_lo2_org as usize].t = ev_t;
1608            }
1609            return false;
1610        }
1611
1612        // General case: split both edges and splice at intersection
1613        self.mesh.as_mut().unwrap().split_edge(e_up ^ 1);
1614        self.mesh.as_mut().unwrap().split_edge(e_lo ^ 1);
1615        let e_lo2 = self.region(reg_lo).e_up;
1616        let e_lo2_oprev = self.mesh.as_ref().unwrap().oprev(e_lo2);
1617        let e_up2 = self.region(reg_up).e_up;
1618        self.mesh.as_mut().unwrap().splice(e_lo2_oprev, e_up2);
1619
1620        // Set intersection coordinates
1621        let e_up2_org = self.mesh.as_ref().unwrap().edges[e_up2 as usize].org;
1622
1623        // Compute weighted coordinates for the intersection vertex
1624        let (org_up_s, org_up_t) = (ou_s, ou_t);
1625        let (dst_up_s, dst_up_t) = (du_s, du_t);
1626        let (org_lo_s, org_lo_t) = (ol_s, ol_t);
1627        let (dst_lo_s, dst_lo_t) = (dl_s, dl_t);
1628
1629        self.mesh.as_mut().unwrap().verts[e_up2_org as usize].s = isect_s;
1630        self.mesh.as_mut().unwrap().verts[e_up2_org as usize].t = isect_t;
1631        self.mesh.as_mut().unwrap().verts[e_up2_org as usize].coords = compute_intersect_coords(
1632            isect_s, isect_t, org_up_s, org_up_t, ou_coords, dst_up_s, dst_up_t, du_coords,
1633            org_lo_s, org_lo_t, ol_coords, dst_lo_s, dst_lo_t, dl_coords,
1634        );
1635        self.mesh.as_mut().unwrap().verts[e_up2_org as usize].idx = TESS_UNDEF;
1636
1637        // Insert new vertex into priority queue
1638        let handle = self.pq_insert(e_up2_org);
1639        if handle == INVALID_HANDLE {
1640            return false;
1641        }
1642        self.mesh.as_mut().unwrap().verts[e_up2_org as usize].pq_handle = handle;
1643
1644        let reg_above = self.region_above(reg_up);
1645        if reg_above != INVALID {
1646            self.region_mut(reg_above).dirty = true;
1647        }
1648        self.region_mut(reg_up).dirty = true;
1649        self.region_mut(reg_lo).dirty = true;
1650
1651        false
1652    }
1653
1654    fn walk_dirty_regions(&mut self, reg_up: RegionIdx) {
1655        let mut reg_up = reg_up;
1656        let mut reg_lo = self.region_below(reg_up);
1657
1658        let max_dirty_iters = self.regions.len() * 4 + 100;
1659        let mut dirty_iter = 0usize;
1660        loop {
1661            dirty_iter += 1;
1662            if dirty_iter > max_dirty_iters {
1663                return; // guard against oscillating dirty-flag loops
1664            }
1665            // Find lowest dirty region
1666            while reg_lo != INVALID && self.region(reg_lo).dirty {
1667                reg_up = reg_lo;
1668                reg_lo = self.region_below(reg_lo);
1669            }
1670            if !self.region(reg_up).dirty {
1671                reg_lo = reg_up;
1672                reg_up = self.region_above(reg_up);
1673                if reg_up == INVALID || !self.region(reg_up).dirty {
1674                    return;
1675                }
1676            }
1677
1678            self.region_mut(reg_up).dirty = false;
1679            if reg_lo == INVALID {
1680                return;
1681            }
1682            let e_up = self.region(reg_up).e_up;
1683            let e_lo = self.region(reg_lo).e_up;
1684
1685            if e_up != INVALID && e_lo != INVALID {
1686                let e_up_dst = self.mesh.as_ref().unwrap().dst(e_up);
1687                let e_lo_dst = self.mesh.as_ref().unwrap().dst(e_lo);
1688                let (eud_s, eud_t) = (
1689                    self.mesh.as_ref().unwrap().verts[e_up_dst as usize].s,
1690                    self.mesh.as_ref().unwrap().verts[e_up_dst as usize].t,
1691                );
1692                let (eld_s, eld_t) = (
1693                    self.mesh.as_ref().unwrap().verts[e_lo_dst as usize].s,
1694                    self.mesh.as_ref().unwrap().verts[e_lo_dst as usize].t,
1695                );
1696
1697                if !vert_eq(eud_s, eud_t, eld_s, eld_t) {
1698                    if self.check_for_left_splice(reg_up) {
1699                        let reg_lo_fix = self.region(reg_lo).fix_upper_edge;
1700                        let reg_up_fix = self.region(reg_up).fix_upper_edge;
1701                        if reg_lo_fix {
1702                            let e_lo2 = self.region(reg_lo).e_up;
1703                            self.delete_region(reg_lo);
1704                            if e_lo2 != INVALID {
1705                                self.mesh.as_mut().unwrap().delete_edge(e_lo2);
1706                            }
1707                            reg_lo = self.region_below(reg_up);
1708                        } else if reg_up_fix {
1709                            let e_up2 = self.region(reg_up).e_up;
1710                            self.delete_region(reg_up);
1711                            if e_up2 != INVALID {
1712                                self.mesh.as_mut().unwrap().delete_edge(e_up2);
1713                            }
1714                            reg_up = self.region_above(reg_lo);
1715                        }
1716                    }
1717                }
1718
1719                let e_up2 = self.region(reg_up).e_up;
1720                let e_lo2 = self.region(reg_lo).e_up;
1721                if e_up2 != INVALID && e_lo2 != INVALID {
1722                    let e_up_org = self.mesh.as_ref().unwrap().edges[e_up2 as usize].org;
1723                    let e_lo_org = self.mesh.as_ref().unwrap().edges[e_lo2 as usize].org;
1724                    if e_up_org != e_lo_org {
1725                        let e_up_dst2 = self.mesh.as_ref().unwrap().dst(e_up2);
1726                        let e_lo_dst2 = self.mesh.as_ref().unwrap().dst(e_lo2);
1727                        let fix_up = self.region(reg_up).fix_upper_edge;
1728                        let fix_lo = self.region(reg_lo).fix_upper_edge;
1729                        if !vert_eq(
1730                            self.mesh.as_ref().unwrap().verts[e_up_dst2 as usize].s,
1731                            self.mesh.as_ref().unwrap().verts[e_up_dst2 as usize].t,
1732                            self.mesh.as_ref().unwrap().verts[e_lo_dst2 as usize].s,
1733                            self.mesh.as_ref().unwrap().verts[e_lo_dst2 as usize].t,
1734                        ) && !fix_up
1735                            && !fix_lo
1736                            && (vert_eq(
1737                                self.mesh.as_ref().unwrap().verts[e_up_dst2 as usize].s,
1738                                self.mesh.as_ref().unwrap().verts[e_up_dst2 as usize].t,
1739                                self.event_s,
1740                                self.event_t,
1741                            ) || vert_eq(
1742                                self.mesh.as_ref().unwrap().verts[e_lo_dst2 as usize].s,
1743                                self.mesh.as_ref().unwrap().verts[e_lo_dst2 as usize].t,
1744                                self.event_s,
1745                                self.event_t,
1746                            ))
1747                        {
1748                            if self.check_for_intersect(reg_up) {
1749                                return;
1750                            }
1751                        } else {
1752                            self.check_for_right_splice(reg_up);
1753                        }
1754                    }
1755                }
1756
1757                // Check for degenerate 2-edge loop
1758                let e_up3 = self.region(reg_up).e_up;
1759                let e_lo3 = self.region(reg_lo).e_up;
1760                if e_up3 != INVALID && e_lo3 != INVALID {
1761                    let e_up_org3 = self.mesh.as_ref().unwrap().edges[e_up3 as usize].org;
1762                    let e_lo_org3 = self.mesh.as_ref().unwrap().edges[e_lo3 as usize].org;
1763                    let e_up_dst3 = self.mesh.as_ref().unwrap().dst(e_up3);
1764                    let e_lo_dst3 = self.mesh.as_ref().unwrap().dst(e_lo3);
1765                    if e_up_org3 == e_lo_org3 && e_up_dst3 == e_lo_dst3 {
1766                        // Merge winding and delete one region
1767                        let eu_w = self.mesh.as_ref().unwrap().edges[e_up3 as usize].winding;
1768                        let eu_sw = self.mesh.as_ref().unwrap().edges[(e_up3 ^ 1) as usize].winding;
1769                        self.mesh.as_mut().unwrap().edges[e_lo3 as usize].winding += eu_w;
1770                        self.mesh.as_mut().unwrap().edges[(e_lo3 ^ 1) as usize].winding += eu_sw;
1771                        self.delete_region(reg_up);
1772                        self.mesh.as_mut().unwrap().delete_edge(e_up3);
1773                        reg_up = self.region_above(reg_lo);
1774                    }
1775                }
1776            }
1777        }
1778    }
1779
1780    fn connect_right_vertex(&mut self, reg_up: RegionIdx, e_bottom_left: EdgeIdx) {
1781        // Mirrors C ConnectRightVertex exactly.
1782        // eTopLeft = eBottomLeft->Onext
1783        let e_top_left = self.mesh.as_ref().unwrap().edges[e_bottom_left as usize].onext;
1784
1785        // Step 1: if eUp->Dst != eLo->Dst, check for intersection
1786        let reg_lo = self.region_below(reg_up);
1787        if reg_lo == INVALID {
1788            return;
1789        }
1790        let e_up = self.region(reg_up).e_up;
1791        let e_lo = self.region(reg_lo).e_up;
1792        if e_up == INVALID || e_lo == INVALID {
1793            return;
1794        }
1795
1796        let dst_differ = {
1797            let e_up_dst = self.mesh.as_ref().unwrap().dst(e_up);
1798            let e_lo_dst = self.mesh.as_ref().unwrap().dst(e_lo);
1799            let (s1, t1) = (
1800                self.mesh.as_ref().unwrap().verts[e_up_dst as usize].s,
1801                self.mesh.as_ref().unwrap().verts[e_up_dst as usize].t,
1802            );
1803            let (s2, t2) = (
1804                self.mesh.as_ref().unwrap().verts[e_lo_dst as usize].s,
1805                self.mesh.as_ref().unwrap().verts[e_lo_dst as usize].t,
1806            );
1807            !vert_eq(s1, t1, s2, t2)
1808        };
1809        if dst_differ {
1810            if self.check_for_intersect(reg_up) {
1811                return;
1812            }
1813        }
1814
1815        // Step 2: re-read after possible changes from CheckForIntersect
1816        let reg_lo = self.region_below(reg_up);
1817        if reg_lo == INVALID {
1818            return;
1819        }
1820        let e_up = self.region(reg_up).e_up;
1821        let e_lo = self.region(reg_lo).e_up;
1822        if e_up == INVALID || e_lo == INVALID {
1823            return;
1824        }
1825
1826        // Step 3: degenerate cases
1827        let mut degenerate = false;
1828        let mut reg_up = reg_up;
1829        let mut e_top_left = e_top_left;
1830        let mut e_bottom_left = e_bottom_left;
1831
1832        // if(VertEq(eUp->Org, event))
1833        let e_up_org = self.mesh.as_ref().unwrap().edges[e_up as usize].org;
1834        if e_up_org != INVALID {
1835            let (s, t) = (
1836                self.mesh.as_ref().unwrap().verts[e_up_org as usize].s,
1837                self.mesh.as_ref().unwrap().verts[e_up_org as usize].t,
1838            );
1839            if vert_eq(s, t, self.event_s, self.event_t) {
1840                // splice(eTopLeft->Oprev, eUp)
1841                let e_tl_oprev = self.mesh.as_ref().unwrap().oprev(e_top_left);
1842                self.mesh.as_mut().unwrap().splice(e_tl_oprev, e_up);
1843                // regUp = TopLeftRegion(regUp)
1844                let reg_up2 = self.top_left_region(reg_up);
1845                if reg_up2 == INVALID {
1846                    return;
1847                }
1848                // eTopLeft = RegionBelow(regUp)->eUp
1849                let rb = self.region_below(reg_up2);
1850                e_top_left = if rb != INVALID {
1851                    self.region(rb).e_up
1852                } else {
1853                    INVALID
1854                };
1855                // FinishLeftRegions(RegionBelow(regUp), regLo)
1856                self.finish_left_regions(rb, reg_lo);
1857                reg_up = reg_up2;
1858                degenerate = true;
1859            }
1860        }
1861
1862        // if(VertEq(eLo->Org, event))
1863        let e_lo2 = if degenerate {
1864            let rl = self.region_below(reg_up);
1865            if rl != INVALID {
1866                self.region(rl).e_up
1867            } else {
1868                INVALID
1869            }
1870        } else {
1871            e_lo
1872        };
1873        let reg_lo2 = self.region_below(reg_up);
1874
1875        let e_lo_org = if e_lo2 != INVALID {
1876            self.mesh.as_ref().unwrap().edges[e_lo2 as usize].org
1877        } else {
1878            INVALID
1879        };
1880        if e_lo_org != INVALID {
1881            let (s, t) = (
1882                self.mesh.as_ref().unwrap().verts[e_lo_org as usize].s,
1883                self.mesh.as_ref().unwrap().verts[e_lo_org as usize].t,
1884            );
1885            if vert_eq(s, t, self.event_s, self.event_t) {
1886                // splice(eBottomLeft, eLo->Oprev)
1887                let e_lo_oprev = self.mesh.as_ref().unwrap().oprev(e_lo2);
1888                self.mesh
1889                    .as_mut()
1890                    .unwrap()
1891                    .splice(e_bottom_left, e_lo_oprev);
1892                // eBottomLeft = FinishLeftRegions(regLo, NULL)
1893                e_bottom_left = self.finish_left_regions(reg_lo2, INVALID);
1894                degenerate = true;
1895            }
1896        }
1897
1898        if degenerate {
1899            if e_bottom_left != INVALID && e_top_left != INVALID {
1900                let e_bl_onext = self.mesh.as_ref().unwrap().edges[e_bottom_left as usize].onext;
1901                self.add_right_edges(reg_up, e_bl_onext, e_top_left, e_top_left, true);
1902            }
1903            return;
1904        }
1905
1906        // Step 4: non-degenerate — add temporary fixable edge
1907        let e_up2 = self.region(reg_up).e_up;
1908        let rl = self.region_below(reg_up);
1909        if rl == INVALID {
1910            return;
1911        }
1912        let e_lo3 = self.region(rl).e_up;
1913        if e_up2 == INVALID || e_lo3 == INVALID {
1914            return;
1915        }
1916
1917        let e_up2_org = self.mesh.as_ref().unwrap().edges[e_up2 as usize].org;
1918        let e_lo3_org = self.mesh.as_ref().unwrap().edges[e_lo3 as usize].org;
1919        let e_new_target = if e_up2_org != INVALID && e_lo3_org != INVALID {
1920            let (euo_s, euo_t) = (
1921                self.mesh.as_ref().unwrap().verts[e_up2_org as usize].s,
1922                self.mesh.as_ref().unwrap().verts[e_up2_org as usize].t,
1923            );
1924            let (elo_s, elot) = (
1925                self.mesh.as_ref().unwrap().verts[e_lo3_org as usize].s,
1926                self.mesh.as_ref().unwrap().verts[e_lo3_org as usize].t,
1927            );
1928            // eNew = VertLeq(eLo->Org, eUp->Org) ? eLo->Oprev : eUp
1929            if vert_leq(elo_s, elot, euo_s, euo_t) {
1930                self.mesh.as_ref().unwrap().oprev(e_lo3)
1931            } else {
1932                e_up2
1933            }
1934        } else {
1935            e_up2
1936        };
1937
1938        // eNew = connect(eBottomLeft->Lprev, eNewTarget)
1939        let e_bl_lprev = self.mesh.as_ref().unwrap().lprev(e_bottom_left);
1940        let e_new = match self
1941            .mesh
1942            .as_mut()
1943            .unwrap()
1944            .connect(e_bl_lprev, e_new_target)
1945        {
1946            Some(e) => e,
1947            None => return,
1948        };
1949
1950        // AddRightEdges(regUp, eNew, eNew->Onext, eNew->Onext, FALSE)
1951        let e_new_onext = self.mesh.as_ref().unwrap().edges[e_new as usize].onext;
1952        self.add_right_edges(reg_up, e_new, e_new_onext, e_new_onext, false);
1953
1954        // eNew->Sym->activeRegion->fixUpperEdge = TRUE
1955        let e_new_sym_ar = self.mesh.as_ref().unwrap().edges[(e_new ^ 1) as usize].active_region;
1956        if e_new_sym_ar != INVALID {
1957            self.region_mut(e_new_sym_ar).fix_upper_edge = true;
1958        }
1959        self.walk_dirty_regions(reg_up);
1960    }
1961
1962    /// Mirrors agg-sharp's `ConnectLeftDegenerate` exactly.  Called when the
1963    /// current sweep event lies on (or coincident with) an already-processed
1964    /// edge — we have to splice the event into that edge rather than adding
1965    /// it as a fresh isolated vertex.  Three sub-cases:
1966    ///
1967    ///   1. `event == e.Org` — the edge's origin was produced by an earlier
1968    ///      intersection split and is still in the PQ.  SpliceMergeVertices
1969    ///      collapses them into a single vertex.
1970    ///   2. `event` lies strictly on `e` (between Org and Dst) — split the
1971    ///      edge at `event`, splice, then recurse into `sweep_event` so the
1972    ///      new vertex is handled properly.
1973    ///   3. `event == e.Dst` — the event coincides with an already-processed
1974    ///      destination vertex.  Splice the event's right-going edges into
1975    ///      `eTopRight` so they join the mesh at the right place.
1976    ///
1977    /// The previous Rust port handled only case 1 and fell back to
1978    /// `check_for_right_splice` for cases 2 and 3, which produced the
1979    /// "eye" rendering artefacts on the lion's self-intersecting polygons
1980    /// and occasionally a panic during rotation when the mesh was left in
1981    /// an inconsistent state.
1982    fn connect_left_degenerate(&mut self, reg_up: RegionIdx, v_event: VertIdx) {
1983        let e_up = self.region(reg_up).e_up;
1984        if e_up == INVALID {
1985            return;
1986        }
1987        let e_up_org = self.mesh.as_ref().unwrap().edges[e_up as usize].org;
1988        let (euo_s, euo_t) = (
1989            self.mesh.as_ref().unwrap().verts[e_up_org as usize].s,
1990            self.mesh.as_ref().unwrap().verts[e_up_org as usize].t,
1991        );
1992        let e_up_dst = self.mesh.as_ref().unwrap().dst(e_up);
1993        let (eud_s, eud_t) = (
1994            self.mesh.as_ref().unwrap().verts[e_up_dst as usize].s,
1995            self.mesh.as_ref().unwrap().verts[e_up_dst as usize].t,
1996        );
1997        let (ev_s, ev_t) = (self.event_s, self.event_t);
1998
1999        // Case 1: e.Org == event — unprocessed vertex, merge and let the
2000        // event come out of the PQ later.
2001        if vert_eq(euo_s, euo_t, ev_s, ev_t) {
2002            let v_an = self.mesh.as_ref().unwrap().verts[v_event as usize].an_edge;
2003            if v_an != INVALID {
2004                self.splice_merge_vertices(e_up, v_an);
2005            }
2006            return;
2007        }
2008
2009        // Case 2: event lies strictly on e (not at either endpoint) —
2010        // split the edge at the event, splice in v_event's edges, recurse.
2011        if !vert_eq(eud_s, eud_t, ev_s, ev_t) {
2012            if self.mesh.as_mut().unwrap().split_edge(e_up ^ 1).is_none() {
2013                return;
2014            }
2015            // If the region had a `fix_upper_edge` flag (temporary sweep
2016            // edge), delete the unused portion.
2017            if self.region(reg_up).fix_upper_edge {
2018                let nxt = self.mesh.as_ref().unwrap().edges[e_up as usize].onext;
2019                if nxt != INVALID {
2020                    let _ = self.mesh.as_mut().unwrap().delete_edge(nxt);
2021                }
2022                self.region_mut(reg_up).fix_upper_edge = false;
2023            }
2024            let v_an = self.mesh.as_ref().unwrap().verts[v_event as usize].an_edge;
2025            if v_an != INVALID {
2026                self.mesh.as_mut().unwrap().splice(v_an, e_up);
2027            }
2028            // Re-process v_event now that the mesh has a new vertex at the
2029            // event position.  Matches C# `SweepEvent(tess, vEvent);` recurse.
2030            self.sweep_event(v_event);
2031            return;
2032        }
2033
2034        // Case 3: event == e.Dst — an already-processed destination
2035        // vertex.  Walk up to the top-right region of reg_up and splice
2036        // the event's right-going edges into the appropriate Onext ring.
2037        let reg_up2 = self.top_right_region(reg_up);
2038        if reg_up2 == INVALID {
2039            // Fallback: just splice at the current region — better than
2040            // leaving the event unattached.
2041            self.check_for_right_splice(reg_up);
2042            return;
2043        }
2044        let reg = self.region_below(reg_up2);
2045        if reg == INVALID {
2046            self.check_for_right_splice(reg_up);
2047            return;
2048        }
2049        let reg_e_up = self.region(reg).e_up;
2050        if reg_e_up == INVALID {
2051            self.check_for_right_splice(reg_up);
2052            return;
2053        }
2054        let mut e_top_right = reg_e_up ^ 1;
2055        let mut e_top_left  = self.mesh.as_ref().unwrap().edges[e_top_right as usize].onext;
2056        let mut e_last      = e_top_left;
2057        // Temp fixable-edge cleanup — matches C#.
2058        if self.region(reg).fix_upper_edge {
2059            if e_top_left != e_top_right {
2060                self.delete_region(reg);
2061                let _ = self.mesh.as_mut().unwrap().delete_edge(e_top_right);
2062                e_top_right = self.mesh.as_ref().unwrap().oprev(e_top_left);
2063            }
2064        }
2065        let v_an = self.mesh.as_ref().unwrap().verts[v_event as usize].an_edge;
2066        if v_an != INVALID {
2067            self.mesh.as_mut().unwrap().splice(v_an, e_top_right);
2068        }
2069        // C# signals "no left-going edges" by passing null for eTopLeft.
2070        // Our `add_right_edges` treats INVALID the same way.
2071        if !self.mesh.as_ref().unwrap().edge_goes_left(e_top_left) {
2072            e_top_left = INVALID;
2073        }
2074        let _ = e_last;
2075        let e_first = self.mesh.as_ref().unwrap().edges[e_top_right as usize].onext;
2076        self.add_right_edges(reg_up2, e_first, e_last, e_top_left, true);
2077    }
2078
2079    /// Port of agg-sharp's `SpliceMergeVertices` — two vertices that the
2080    /// sweep has decided are "the same" get their Onext rings spliced
2081    /// together so the mesh sees a single vertex.  We skip the user-
2082    /// callback combine (no client vertex merging) and just call
2083    /// `meshSplice`, matching the no-callback semantics of the C reference.
2084    fn splice_merge_vertices(&mut self, e1: EdgeIdx, e2: EdgeIdx) {
2085        if e1 == INVALID || e2 == INVALID { return; }
2086        // Delete one of the two originals from the PQ if it's still queued
2087        // — matches `VertexPriorityQue.Delete(eUp.originVertex.priorityQueueHandle)`
2088        // from the C# call site in `CheckForRightSplice`.  Safe to skip
2089        // if not queued.
2090        let v2_org = self.mesh.as_ref().unwrap().edges[e2 as usize].org;
2091        if v2_org != INVALID {
2092            let handle = self.mesh.as_ref().unwrap().verts[v2_org as usize].pq_handle;
2093            if handle != INVALID_HANDLE {
2094                self.pq_delete(handle);
2095            }
2096        }
2097        self.mesh.as_mut().unwrap().splice(e1, e2);
2098    }
2099
2100    /// Mirrors C's dictSearch: walks forward from head.next, returns the key of
2101    /// the FIRST node where edge_leq(tmp_reg, node.key) is true.
2102    /// This is exactly how the C code finds the containing region in ConnectLeftVertex.
2103    fn dict_search_forward(&mut self, tmp_e_up: EdgeIdx) -> RegionIdx {
2104        let tmp_reg = self.alloc_region();
2105        self.region_mut(tmp_reg).e_up = tmp_e_up;
2106
2107        // C dictSearch: walk forward from head.next until key==NULL or edge_leq(tmp, node.key)
2108        let max_fwd_iters = self.dict.nodes.len() + 2;
2109        let mut fwd_iter = 0usize;
2110        let mut node = self.dict.nodes[DICT_HEAD as usize].next;
2111        let result = loop {
2112            let key = self.dict.key(node);
2113            if key == INVALID {
2114                // hit head sentinel — not found
2115                break INVALID;
2116            }
2117            if self.edge_leq(tmp_reg, key) {
2118                break key;
2119            }
2120            node = self.dict.succ(node);
2121            fwd_iter += 1;
2122            if fwd_iter > max_fwd_iters {
2123                break INVALID; // degenerate dict — stop walking
2124            }
2125        };
2126
2127        self.free_region(tmp_reg);
2128        result
2129    }
2130
2131    fn connect_left_vertex(&mut self, v_event: VertIdx) {
2132        let an_edge = self.mesh.as_ref().unwrap().verts[v_event as usize].an_edge;
2133        if an_edge == INVALID {
2134            return;
2135        }
2136
2137        let tmp_e_up = an_edge ^ 1;
2138        let reg_up = self.dict_search_forward(tmp_e_up);
2139        if reg_up == INVALID {
2140            return;
2141        }
2142
2143        let reg_lo = self.region_below(reg_up);
2144        if reg_lo == INVALID {
2145            return;
2146        }
2147
2148        let e_up = self.region(reg_up).e_up;
2149        let e_lo = self.region(reg_lo).e_up;
2150        if e_up == INVALID || e_lo == INVALID {
2151            return;
2152        }
2153
2154        let e_up_dst = self.mesh.as_ref().unwrap().dst(e_up);
2155        let e_up_org = self.mesh.as_ref().unwrap().edges[e_up as usize].org;
2156        if e_up_dst == INVALID || e_up_org == INVALID {
2157            return;
2158        }
2159        let eud_s = self.mesh.as_ref().unwrap().verts[e_up_dst as usize].s;
2160        let eud_t = self.mesh.as_ref().unwrap().verts[e_up_dst as usize].t;
2161        let euo_s = self.mesh.as_ref().unwrap().verts[e_up_org as usize].s;
2162        let euo_t = self.mesh.as_ref().unwrap().verts[e_up_org as usize].t;
2163
2164        if crate::geom::edge_sign(eud_s, eud_t, self.event_s, self.event_t, euo_s, euo_t) == 0.0 {
2165            self.connect_left_degenerate(reg_up, v_event);
2166            return;
2167        }
2168
2169        let e_lo_dst = self.mesh.as_ref().unwrap().dst(e_lo);
2170        let eld_s = self.mesh.as_ref().unwrap().verts[e_lo_dst as usize].s;
2171        let eld_t = self.mesh.as_ref().unwrap().verts[e_lo_dst as usize].t;
2172        let reg = if vert_leq(eld_s, eld_t, eud_s, eud_t) {
2173            reg_up
2174        } else {
2175            reg_lo
2176        };
2177
2178        let reg_up_inside = self.region(reg_up).inside;
2179        let reg_fix = self.region(reg).fix_upper_edge;
2180
2181        if reg_up_inside || reg_fix {
2182            if self.trace_enabled {
2183                eprintln!(
2184                    "R   LEFT_CONNECT inside={} fixUpper={} reg={}",
2185                    reg_up_inside as i32,
2186                    reg_fix as i32,
2187                    if reg == reg_up { "up" } else { "lo" }
2188                );
2189            }
2190            let e_new = if reg == reg_up {
2191                // C: eNew = tessMeshConnect(mesh, vEvent->anEdge->Sym, eUp->Lnext)
2192                let e_up_lnext = self.mesh.as_ref().unwrap().edges[e_up as usize].lnext;
2193                self.mesh.as_mut().unwrap().connect(an_edge ^ 1, e_up_lnext)
2194            } else {
2195                let e_lo_dnext = self.mesh.as_ref().unwrap().dnext(e_lo);
2196                self.mesh
2197                    .as_mut()
2198                    .unwrap()
2199                    .connect(e_lo_dnext, an_edge)
2200                    .map(|e| e ^ 1)
2201            };
2202            let e_new = match e_new {
2203                Some(e) => e,
2204                None => return,
2205            };
2206
2207            if reg_fix {
2208                if !self.fix_upper_edge(reg, e_new) {
2209                    return;
2210                }
2211            } else {
2212                self.add_region_below(reg_up, e_new);
2213            }
2214            self.sweep_event(v_event);
2215        } else {
2216            if self.trace_enabled {
2217                eprintln!("R   LEFT_OUTSIDE");
2218            }
2219            self.add_right_edges(reg_up, an_edge, an_edge, INVALID, true);
2220        }
2221    }
2222
2223    /// Dict search: finds the first region where edge_leq(tmp_reg, region) == true.
2224    /// `tmp_e_up` is the e_up of a temporary region used for comparison.
2225    /// Returns the matching region index.
2226    fn dict_search_by_edge(&mut self, tmp_e_up: EdgeIdx) -> RegionIdx {
2227        // Temporarily allocate a region with tmp_e_up for comparison
2228        let tmp_reg = self.alloc_region();
2229        self.region_mut(tmp_reg).e_up = tmp_e_up;
2230
2231        // Walk forward from head looking for the first node where edge_leq(tmp_reg, node_key)
2232        let mut node = self.dict.succ(DICT_HEAD);
2233        let result = loop {
2234            let key = self.dict.key(node);
2235            if key == INVALID {
2236                // Hit head (wrapped around) - not found
2237                break INVALID;
2238            }
2239            if self.edge_leq(tmp_reg, key) {
2240                break key;
2241            }
2242            node = self.dict.succ(node);
2243        };
2244
2245        self.free_region(tmp_reg);
2246        result
2247    }
2248
2249    fn sweep_event(&mut self, v_event: VertIdx) -> bool {
2250        let an_edge = self.mesh.as_ref().unwrap().verts[v_event as usize].an_edge;
2251        if an_edge == INVALID {
2252            return true;
2253        }
2254
2255        if self.trace_enabled {
2256            let (vs, vt) = (
2257                self.mesh.as_ref().unwrap().verts[v_event as usize].s,
2258                self.mesh.as_ref().unwrap().verts[v_event as usize].t,
2259            );
2260            eprintln!(
2261                "R SWEEP #{} s={:.6} t={:.6}",
2262                self.sweep_event_num, vs, vt
2263            );
2264            self.sweep_event_num += 1;
2265        }
2266
2267        // Walk through all edges at v_event (the onext ring).
2268        // If ANY has active_region != INVALID, it's already in the dict -> "right vertex" case.
2269        // If NONE has active_region set -> call connect_left_vertex (C: ConnectLeftVertex).
2270        let e_start = an_edge;
2271        let mut e = e_start;
2272        let found_e = loop {
2273            let ar = self.mesh.as_ref().unwrap().edges[e as usize].active_region;
2274            if ar != INVALID {
2275                break Some(e);
2276            }
2277            let next = self.mesh.as_ref().unwrap().edges[e as usize].onext;
2278            e = next;
2279            if e == e_start {
2280                break None;
2281            }
2282        };
2283
2284        if found_e.is_none() {
2285            if self.trace_enabled {
2286                eprintln!("R   PATH left");
2287            }
2288            self.connect_left_vertex(v_event);
2289            return true;
2290        }
2291
2292        // At least one edge is already in the dict.
2293        let e = found_e.unwrap();
2294        if self.trace_enabled {
2295            eprintln!("R   PATH right");
2296        }
2297        let reg_up = {
2298            let ar = self.mesh.as_ref().unwrap().edges[e as usize].active_region;
2299            self.top_left_region(ar)
2300        };
2301        if reg_up == INVALID {
2302            return false;
2303        }
2304
2305        let reg_lo = self.region_below(reg_up);
2306        if reg_lo == INVALID {
2307            return true;
2308        }
2309        let e_top_left = self.region(reg_lo).e_up;
2310        let e_bottom_left = self.finish_left_regions(reg_lo, INVALID);
2311
2312        if e_bottom_left == INVALID {
2313            return true;
2314        }
2315        let e_bottom_left_onext = self.mesh.as_ref().unwrap().edges[e_bottom_left as usize].onext;
2316        if e_bottom_left_onext == e_top_left {
2317            if self.trace_enabled {
2318                eprintln!("R   CONNECT_RIGHT");
2319            }
2320            self.connect_right_vertex(reg_up, e_bottom_left);
2321        } else {
2322            if self.trace_enabled {
2323                eprintln!("R   ADD_RIGHT_EDGES");
2324            }
2325            self.add_right_edges(reg_up, e_bottom_left_onext, e_top_left, e_top_left, true);
2326        }
2327        true
2328    }
2329
2330}
2331
2332// ────────────────────────── Public wrapper ────────────────────────────────────
2333
2334/// High-level tessellator (public interface).
2335pub struct TessellatorApi {
2336    inner: Tessellator,
2337}
2338
2339impl TessellatorApi {
2340    pub fn new() -> Self {
2341        TessellatorApi {
2342            inner: Tessellator::new(),
2343        }
2344    }
2345    pub fn set_option(&mut self, option: TessOption, value: bool) {
2346        self.inner.set_option(option, value);
2347    }
2348    pub fn add_contour(&mut self, size: usize, vertices: &[Real]) {
2349        self.inner.add_contour(size, vertices);
2350    }
2351    pub fn tessellate(
2352        &mut self,
2353        winding_rule: WindingRule,
2354        element_type: ElementType,
2355        poly_size: usize,
2356        vertex_size: usize,
2357        normal: Option<[Real; 3]>,
2358    ) -> bool {
2359        self.inner
2360            .tessellate(winding_rule, element_type, poly_size, vertex_size, normal)
2361    }
2362    pub fn vertex_count(&self) -> usize {
2363        self.inner.vertex_count()
2364    }
2365    pub fn element_count(&self) -> usize {
2366        self.inner.element_count()
2367    }
2368    pub fn vertices(&self) -> &[Real] {
2369        self.inner.vertices()
2370    }
2371    pub fn vertex_indices(&self) -> &[u32] {
2372        self.inner.vertex_indices()
2373    }
2374    pub fn elements(&self) -> &[u32] {
2375        self.inner.elements()
2376    }
2377    /// Per triangle-vertex edge flags — see [`Tessellator::out_edge_flags`].
2378    pub fn edge_flags(&self) -> &[u8] {
2379        self.inner.edge_flags()
2380    }
2381    pub fn status(&self) -> TessStatus {
2382        self.inner.get_status()
2383    }
2384}
2385
2386impl Default for TessellatorApi {
2387    fn default() -> Self {
2388        Self::new()
2389    }
2390}