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