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 connect;
12mod dirty_regions;
13mod geometry;
14mod output;
15mod priority_queue;
16mod region;
17mod sweep;
18#[cfg(test)]
19mod tests;
20
21pub use api::TessellatorApi;
22
23use geometry::{check_orientation, compute_normal, dot, is_valid_coord, long_axis};
24
25use crate::dict::Dict;
26use crate::geom::{vert_eq, Real};
27use crate::mesh::{Mesh, VertIdx, E_HEAD, INVALID, V_HEAD};
28use crate::sweep::ActiveRegion;
29
30// ─────────────────────────────── Public types ──────────────────────────────────
31
32#[derive(Copy, Clone, Debug, PartialEq)]
33pub enum WindingRule {
34    Odd,
35    NonZero,
36    Positive,
37    Negative,
38    AbsGeqTwo,
39}
40
41#[derive(Copy, Clone, Debug, PartialEq)]
42pub enum ElementType {
43    Polygons,
44    ConnectedPolygons,
45    BoundaryContours,
46}
47
48#[derive(Copy, Clone, Debug, PartialEq)]
49pub enum TessOption {
50    ConstrainedDelaunayTriangulation,
51    ReverseContours,
52}
53
54#[derive(Copy, Clone, Debug, PartialEq)]
55pub enum TessStatus {
56    Ok,
57    OutOfMemory,
58    InvalidInput,
59}
60
61pub const TESS_UNDEF: u32 = u32::MAX;
62// Max magnitude that input coordinates can safely take without losing
63// precision in the sweep.  f64's 52-bit mantissa keeps integer coords
64// exact up to 2^53; we keep a conservative margin.
65const MAX_VALID_COORD: Real = (1u64 << 50) as Real;
66const MIN_VALID_COORD: Real = -MAX_VALID_COORD;
67
68type RegionIdx = u32;
69
70// ─────────────────────────── Tessellator ──────────────────────────────────────
71
72pub struct Tessellator {
73    mesh: Option<Mesh>,
74    pub status: TessStatus,
75    normal: [Real; 3],
76    s_unit: [Real; 3],
77    t_unit: [Real; 3],
78    bmin: [Real; 2],
79    bmax: [Real; 2],
80    process_cdt: bool,
81    reverse_contours: bool,
82    winding_rule: WindingRule,
83
84    // Sweep state
85    dict: Dict,
86    /// Intersection vertices inserted during sweep (heap replacement).
87    /// Each entry is a VertIdx; ordering is done by coordinate lookup.
88    intersection_verts: Vec<VertIdx>,
89    next_isect_handle: i32,
90    event: VertIdx,
91    event_s: Real,
92    event_t: Real,
93
94    // Region arena
95    regions: Vec<Option<ActiveRegion>>,
96    region_free: Vec<RegionIdx>,
97
98    // Output
99    pub out_vertices: Vec<Real>,
100    pub out_vertex_indices: Vec<u32>,
101    pub out_elements: Vec<u32>,
102    /// Per triangle-vertex edge-flag (parallel to `out_elements`).
103    ///
104    /// `1` when the polygon edge starting at this vertex (going to the next
105    /// vertex in CCW order within the same triangle) is an **original
106    /// boundary edge** of the input polygon; `0` when the edge is a new
107    /// interior edge added by the tessellation sweep.
108    ///
109    /// Parallels the C libtess2 / agg-sharp `EdgeFlagCallback` mechanism —
110    /// consumers that want analytic per-edge anti-aliasing (halo strips,
111    /// conservative outlines) look at each triangle's three flags and only
112    /// expand the sides that are actual polygon boundaries.
113    ///
114    /// Populated for `ElementType::Polygons` and `ElementType::ConnectedPolygons`;
115    /// empty for `ElementType::BoundaryContours` (no triangles are emitted in
116    /// that mode).  Length equals `poly_size × element_count`.
117    pub out_edge_flags: Vec<u8>,
118    pub out_vertex_count: usize,
119    pub out_element_count: usize,
120    vertex_index_counter: u32,
121
122    // Primary event queue: pre-sorted vertices for the initial sweep phase
123    sorted_events: Vec<VertIdx>,
124    sorted_event_pos: usize,
125    sweep_event_num: u32,
126    trace_enabled: bool,
127}
128
129impl Tessellator {
130    pub fn new() -> Self {
131        Tessellator {
132            mesh: None,
133            status: TessStatus::Ok,
134            normal: [0.0; 3],
135            s_unit: [0.0; 3],
136            t_unit: [0.0; 3],
137            bmin: [0.0; 2],
138            bmax: [0.0; 2],
139            process_cdt: false,
140            reverse_contours: false,
141            winding_rule: WindingRule::Odd,
142            dict: Dict::new(),
143            intersection_verts: Vec::new(),
144            next_isect_handle: 0,
145            event: INVALID,
146            event_s: 0.0,
147            event_t: 0.0,
148            regions: Vec::new(),
149            region_free: Vec::new(),
150            out_vertices: Vec::new(),
151            out_vertex_indices: Vec::new(),
152            out_elements: Vec::new(),
153            out_edge_flags: Vec::new(),
154            out_vertex_count: 0,
155            out_element_count: 0,
156            vertex_index_counter: 0,
157            sorted_events: Vec::new(),
158            sorted_event_pos: 0,
159            sweep_event_num: 0,
160            trace_enabled: std::env::var("TESS_TRACE").is_ok(),
161        }
162    }
163
164    pub fn set_option(&mut self, option: TessOption, value: bool) {
165        match option {
166            TessOption::ConstrainedDelaunayTriangulation => self.process_cdt = value,
167            TessOption::ReverseContours => self.reverse_contours = value,
168        }
169    }
170
171    /// Add a contour. `size` = 2 or 3 (coords per vertex). `vertices` is flat.
172    ///
173    /// Input type is `Real` — currently `f64` — to avoid losing precision on
174    /// coordinate input.  Callers holding `f32` data should cast element-wise
175    /// at the call site.
176    pub fn add_contour(&mut self, size: usize, vertices: &[Real]) {
177        if self.status != TessStatus::Ok {
178            return;
179        }
180        let size = size.min(3).max(2);
181        let count = vertices.len() / size;
182        if self.mesh.is_none() {
183            self.mesh = Some(Mesh::new());
184        }
185
186        let mut e = INVALID;
187        for i in 0..count {
188            let cx = vertices[i * size];
189            let cy = vertices[i * size + 1];
190            let cz = if size > 2 {
191                vertices[i * size + 2]
192            } else {
193                0.0
194            };
195
196            if !is_valid_coord(cx) || !is_valid_coord(cy) || (size > 2 && !is_valid_coord(cz)) {
197                self.status = TessStatus::InvalidInput;
198                return;
199            }
200
201            let mesh = self.mesh.as_mut().unwrap();
202            if e == INVALID {
203                let new_e = match mesh.make_edge() {
204                    Some(v) => v,
205                    None => {
206                        self.status = TessStatus::OutOfMemory;
207                        return;
208                    }
209                };
210                e = new_e;
211                if !mesh.splice(e, e ^ 1) {
212                    self.status = TessStatus::OutOfMemory;
213                    return;
214                }
215            } else {
216                if mesh.split_edge(e).is_none() {
217                    self.status = TessStatus::OutOfMemory;
218                    return;
219                }
220                e = mesh.edges[e as usize].lnext;
221            }
222
223            let org = mesh.edges[e as usize].org;
224            mesh.verts[org as usize].coords[0] = cx;
225            mesh.verts[org as usize].coords[1] = cy;
226            mesh.verts[org as usize].coords[2] = cz;
227            mesh.verts[org as usize].idx = self.vertex_index_counter;
228            self.vertex_index_counter += 1;
229
230            let w = if self.reverse_contours { -1 } else { 1 };
231            mesh.edges[e as usize].winding = w;
232            mesh.edges[(e ^ 1) as usize].winding = -w;
233        }
234    }
235
236    pub fn tessellate(
237        &mut self,
238        winding_rule: WindingRule,
239        element_type: ElementType,
240        poly_size: usize,
241        vertex_size: usize,
242        normal: Option<[Real; 3]>,
243    ) -> bool {
244        if self.status != TessStatus::Ok {
245            return false;
246        }
247        self.winding_rule = winding_rule;
248        self.out_vertices.clear();
249        self.out_vertex_indices.clear();
250        self.out_elements.clear();
251        self.out_edge_flags.clear();
252        self.out_vertex_count = 0;
253        self.out_element_count = 0;
254        self.normal = normal.unwrap_or([0.0, 0.0, 0.0]);
255
256        if self.mesh.is_none() {
257            self.mesh = Some(Mesh::new());
258        }
259
260        if !self.project_polygon() {
261            self.status = TessStatus::OutOfMemory;
262            return false;
263        }
264
265        if !self.compute_interior() {
266            if self.status == TessStatus::Ok {
267                self.status = TessStatus::OutOfMemory;
268            }
269            return false;
270        }
271
272        let vertex_size = vertex_size.min(3).max(2);
273        if element_type == ElementType::BoundaryContours {
274            self.output_contours(vertex_size);
275        } else {
276            self.output_polymesh(element_type, poly_size, vertex_size);
277        }
278
279        self.mesh = None;
280        self.status == TessStatus::Ok
281    }
282
283    // ─────── Accessors ────────────────────────────────────────────────────────
284
285    pub fn vertex_count(&self) -> usize {
286        self.out_vertex_count
287    }
288    pub fn element_count(&self) -> usize {
289        self.out_element_count
290    }
291    pub fn vertices(&self) -> &[Real] {
292        &self.out_vertices
293    }
294    pub fn vertex_indices(&self) -> &[u32] {
295        &self.out_vertex_indices
296    }
297    pub fn elements(&self) -> &[u32] {
298        &self.out_elements
299    }
300    /// Per triangle-vertex edge flags (see [`Tessellator::out_edge_flags`]).
301    ///
302    /// Returns an empty slice for `ElementType::BoundaryContours`.
303    pub fn edge_flags(&self) -> &[u8] {
304        &self.out_edge_flags
305    }
306    pub fn get_status(&self) -> TessStatus {
307        self.status
308    }
309
310    // ─────── Projection ───────────────────────────────────────────────────────
311
312    fn project_polygon(&mut self) -> bool {
313        let mut norm = self.normal;
314        let mut computed_normal = false;
315        if norm[0] == 0.0 && norm[1] == 0.0 && norm[2] == 0.0 {
316            if let Some(ref m) = self.mesh {
317                compute_normal(m, &mut norm);
318            }
319            computed_normal = true;
320        }
321
322        let i = long_axis(&norm);
323        self.s_unit = [0.0; 3];
324        self.t_unit = [0.0; 3];
325        self.s_unit[(i + 1) % 3] = 1.0;
326        self.t_unit[(i + 2) % 3] = if norm[i] > 0.0 { 1.0 } else { -1.0 };
327        let su = self.s_unit;
328        let tu = self.t_unit;
329
330        if let Some(ref mut mesh) = self.mesh {
331            let mut v = mesh.verts[V_HEAD as usize].next;
332            while v != V_HEAD {
333                let c = mesh.verts[v as usize].coords;
334                mesh.verts[v as usize].s = dot(&c, &su);
335                mesh.verts[v as usize].t = dot(&c, &tu);
336                v = mesh.verts[v as usize].next;
337            }
338            if computed_normal {
339                check_orientation(mesh);
340            }
341
342            let mut first = true;
343            let mut v = mesh.verts[V_HEAD as usize].next;
344            while v != V_HEAD {
345                let vs = mesh.verts[v as usize].s;
346                let vt = mesh.verts[v as usize].t;
347                if first {
348                    self.bmin = [vs, vt];
349                    self.bmax = [vs, vt];
350                    first = false;
351                } else {
352                    if vs < self.bmin[0] {
353                        self.bmin[0] = vs;
354                    }
355                    if vs > self.bmax[0] {
356                        self.bmax[0] = vs;
357                    }
358                    if vt < self.bmin[1] {
359                        self.bmin[1] = vt;
360                    }
361                    if vt > self.bmax[1] {
362                        self.bmax[1] = vt;
363                    }
364                }
365                v = mesh.verts[v as usize].next;
366            }
367        }
368        true
369    }
370
371    // ─────── Main interior computation ───────────────────────────────────────
372
373    fn compute_interior(&mut self) -> bool {
374        self.sweep_event_num = 0;
375
376        if !self.remove_degenerate_edges() {
377            return false;
378        }
379        if !self.init_priority_queue() {
380            return false;
381        }
382        if !self.init_edge_dict() {
383            return false;
384        }
385
386        loop {
387            if self.pq_is_empty() {
388                break;
389            }
390
391            let v = self.pq_extract_min();
392            if v == INVALID {
393                break;
394            }
395
396            // Coalesce coincident vertices
397            loop {
398                if self.pq_is_empty() {
399                    break;
400                }
401                let next_v = self.pq_minimum();
402                if next_v == INVALID {
403                    break;
404                }
405                let (v_s, v_t) = {
406                    let mesh = self.mesh.as_ref().unwrap();
407                    (mesh.verts[v as usize].s, mesh.verts[v as usize].t)
408                };
409                let (nv_s, nv_t) = {
410                    let mesh = self.mesh.as_ref().unwrap();
411                    (mesh.verts[next_v as usize].s, mesh.verts[next_v as usize].t)
412                };
413                if !vert_eq(v_s, v_t, nv_s, nv_t) {
414                    break;
415                }
416                let next_v = self.pq_extract_min();
417                // Merge next_v into v
418                let an1 = self.mesh.as_ref().unwrap().verts[v as usize].an_edge;
419                let an2 = self.mesh.as_ref().unwrap().verts[next_v as usize].an_edge;
420                if an1 != INVALID && an2 != INVALID {
421                    if !self.mesh.as_mut().unwrap().splice(an1, an2) {
422                        return false;
423                    }
424                }
425            }
426
427            self.event = v;
428            let (v_s, v_t) = {
429                let m = self.mesh.as_ref().unwrap();
430                (m.verts[v as usize].s, m.verts[v as usize].t)
431            };
432            self.event_s = v_s;
433            self.event_t = v_t;
434
435            if !self.sweep_event(v) {
436                return false;
437            }
438        }
439
440        self.done_edge_dict();
441
442        let trace = self.trace_enabled;
443        if let Some(ref mut mesh) = self.mesh {
444            if trace {
445                let mut inside = 0u32;
446                let mut outside = 0u32;
447                let mut f = mesh.faces[crate::mesh::F_HEAD as usize].next;
448                while f != crate::mesh::F_HEAD {
449                    let an = mesh.faces[f as usize].an_edge;
450                    let mut edge_count = 0u32;
451                    if an != INVALID {
452                        let mut e = an;
453                        loop {
454                            edge_count += 1;
455                            e = mesh.edges[e as usize].lnext;
456                            if e == an { break; }
457                            if edge_count > 10000 { break; }
458                        }
459                    }
460                    if mesh.faces[f as usize].inside {
461                        inside += 1;
462                        eprintln!("R FACE inside edges={}", edge_count);
463                    } else {
464                        outside += 1;
465                    }
466                    f = mesh.faces[f as usize].next;
467                }
468                eprintln!("R FACES inside={} outside={}", inside, outside);
469            }
470            if !mesh.tessellate_interior() {
471                return false;
472            }
473            if self.process_cdt {
474                mesh.refine_delaunay();
475            }
476        }
477        true
478    }
479
480    fn remove_degenerate_edges(&mut self) -> bool {
481        // Mirrors C RemoveDegenerateEdges exactly
482        let mesh = match self.mesh.as_mut() {
483            Some(m) => m,
484            None => return true,
485        };
486        let mut e = mesh.edges[E_HEAD as usize].next;
487        while e != E_HEAD {
488            let mut e_next = mesh.edges[e as usize].next;
489            let mut e_lnext = mesh.edges[e as usize].lnext;
490
491            let org = mesh.edges[e as usize].org;
492            let dst = mesh.dst(e);
493            let valid = org != INVALID
494                && dst != INVALID
495                && (org as usize) < mesh.verts.len()
496                && (dst as usize) < mesh.verts.len();
497
498            if valid {
499                let (os, ot) = (mesh.verts[org as usize].s, mesh.verts[org as usize].t);
500                let (ds, dt) = (mesh.verts[dst as usize].s, mesh.verts[dst as usize].t);
501
502                if vert_eq(os, ot, ds, dt) && mesh.edges[e_lnext as usize].lnext != e {
503                    // Zero-length edge, contour has at least 3 edges
504                    mesh.splice(e_lnext, e);
505                    if !mesh.delete_edge(e) {
506                        return false;
507                    }
508                    e = e_lnext;
509                    e_lnext = mesh.edges[e as usize].lnext;
510                }
511            }
512
513            // Degenerate contour (one or two edges): e_lnext->lnext == e
514            let e_lnext_lnext = mesh.edges[e_lnext as usize].lnext;
515            if e_lnext_lnext == e {
516                if e_lnext != e {
517                    // Advance e_next past e_lnext or its sym
518                    if e_lnext == e_next || e_lnext == (e_next ^ 1) {
519                        e_next = mesh.edges[e_next as usize].next;
520                    }
521                    let w1 = mesh.edges[e_lnext as usize].winding;
522                    let w2 = mesh.edges[(e_lnext ^ 1) as usize].winding;
523                    mesh.edges[e as usize].winding += w1;
524                    mesh.edges[(e ^ 1) as usize].winding += w2;
525                    if !mesh.delete_edge(e_lnext) {
526                        return false;
527                    }
528                }
529                // Advance e_next past e or its sym
530                if e == e_next || e == (e_next ^ 1) {
531                    e_next = mesh.edges[e_next as usize].next;
532                }
533                if !mesh.delete_edge(e) {
534                    return false;
535                }
536            }
537
538            e = e_next;
539        }
540        true
541    }
542}