Skip to main content

tess2_rust/tess/
mod.rs

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