Skip to main content

clipper2_rust/
engine_public.rs

1//! Public polygon clipper API types
2//!
3//! Contains PolyPath/PolyTree types, Clipper64, and ClipperD.
4//! Direct port from clipper.engine.h
5//! Copyright (c) Angus Johnson 2010-2025
6
7use crate::core::*;
8use crate::engine::*;
9use crate::engine_fns::*;
10
11// ============================================================================
12// PolyPath - Tree structure for polygon results
13// Direct port from clipper.engine.h line 298
14// ============================================================================
15
16/// PolyPath64 - Integer coordinate polytree node
17/// Direct port from clipper.engine.h line 334
18#[derive(Debug, Clone)]
19pub struct PolyPath64 {
20    parent: Option<usize>, // index into polytree arena
21    children: Vec<usize>,  // indices into polytree arena
22    pub(crate) polygon: Path64,
23}
24
25impl PolyPath64 {
26    pub fn new() -> Self {
27        Self {
28            parent: None,
29            children: Vec::new(),
30            polygon: Path64::new(),
31        }
32    }
33
34    pub fn with_parent(parent_idx: usize) -> Self {
35        Self {
36            parent: Some(parent_idx),
37            children: Vec::new(),
38            polygon: Path64::new(),
39        }
40    }
41
42    pub fn with_path(parent_idx: usize, path: Path64) -> Self {
43        Self {
44            parent: Some(parent_idx),
45            children: Vec::new(),
46            polygon: path,
47        }
48    }
49
50    pub fn polygon(&self) -> &Path64 {
51        &self.polygon
52    }
53
54    pub fn count(&self) -> usize {
55        self.children.len()
56    }
57
58    pub fn parent(&self) -> Option<usize> {
59        self.parent
60    }
61
62    pub fn children(&self) -> &[usize] {
63        &self.children
64    }
65}
66
67impl Default for PolyPath64 {
68    fn default() -> Self {
69        Self::new()
70    }
71}
72
73/// PolyTree64 arena - stores all PolyPath64 nodes
74/// Direct port from clipper.engine.h PolyTree64
75pub struct PolyTree64 {
76    pub nodes: Vec<PolyPath64>,
77}
78
79impl PolyTree64 {
80    pub fn new() -> Self {
81        // Root node is always at index 0
82        let root = PolyPath64::new();
83        Self { nodes: vec![root] }
84    }
85
86    /// Add a child path to a parent node
87    pub fn add_child(&mut self, parent_idx: usize, path: Path64) -> usize {
88        let child_idx = self.nodes.len();
89        self.nodes.push(PolyPath64::with_path(parent_idx, path));
90        self.nodes[parent_idx].children.push(child_idx);
91        child_idx
92    }
93
94    /// Get the root node
95    pub fn root(&self) -> &PolyPath64 {
96        &self.nodes[0]
97    }
98
99    /// Clear the tree
100    pub fn clear(&mut self) {
101        self.nodes.clear();
102        self.nodes.push(PolyPath64::new());
103    }
104
105    /// Get level of a node
106    pub fn level(&self, node_idx: usize) -> u32 {
107        let mut result = 0u32;
108        let mut p = self.nodes[node_idx].parent;
109        while let Some(parent_idx) = p {
110            result += 1;
111            p = self.nodes[parent_idx].parent;
112        }
113        result
114    }
115
116    /// Check if a node is a hole
117    pub fn is_hole(&self, node_idx: usize) -> bool {
118        let lvl = self.level(node_idx);
119        lvl > 0 && (lvl & 1) == 0
120    }
121
122    /// Get total area including children
123    pub fn area_of(&self, node_idx: usize) -> f64 {
124        let mut result = area(&self.nodes[node_idx].polygon);
125        for &child_idx in &self.nodes[node_idx].children {
126            result += self.area_of(child_idx);
127        }
128        result
129    }
130}
131
132impl Default for PolyTree64 {
133    fn default() -> Self {
134        Self::new()
135    }
136}
137
138/// PolyPathD - Double coordinate polytree node
139/// Direct port from clipper.engine.h line 385
140#[derive(Debug, Clone)]
141pub struct PolyPathD {
142    parent: Option<usize>,
143    children: Vec<usize>,
144    polygon: PathD,
145    pub(crate) scale: f64,
146}
147
148impl PolyPathD {
149    pub fn new() -> Self {
150        Self {
151            parent: None,
152            children: Vec::new(),
153            polygon: PathD::new(),
154            scale: 1.0,
155        }
156    }
157
158    pub fn with_parent(parent_idx: usize, scale: f64) -> Self {
159        Self {
160            parent: Some(parent_idx),
161            children: Vec::new(),
162            polygon: PathD::new(),
163            scale,
164        }
165    }
166
167    pub fn polygon(&self) -> &PathD {
168        &self.polygon
169    }
170
171    pub fn set_polygon(&mut self, polygon: PathD) {
172        self.polygon = polygon;
173    }
174
175    pub fn count(&self) -> usize {
176        self.children.len()
177    }
178
179    pub fn scale(&self) -> f64 {
180        self.scale
181    }
182
183    pub fn set_scale(&mut self, scale: f64) {
184        self.scale = scale;
185    }
186
187    pub fn parent(&self) -> Option<usize> {
188        self.parent
189    }
190
191    pub fn children(&self) -> &[usize] {
192        &self.children
193    }
194}
195
196impl Default for PolyPathD {
197    fn default() -> Self {
198        Self::new()
199    }
200}
201
202/// PolyTreeD arena
203pub struct PolyTreeD {
204    pub nodes: Vec<PolyPathD>,
205}
206
207impl PolyTreeD {
208    pub fn new() -> Self {
209        Self {
210            nodes: vec![PolyPathD::new()],
211        }
212    }
213
214    pub fn add_child(&mut self, parent_idx: usize, path: PathD) -> usize {
215        let scale = self.nodes[parent_idx].scale;
216        let child_idx = self.nodes.len();
217        let mut node = PolyPathD::with_parent(parent_idx, scale);
218        node.polygon = path;
219        self.nodes.push(node);
220        self.nodes[parent_idx].children.push(child_idx);
221        child_idx
222    }
223
224    pub fn add_child_from_path64(&mut self, parent_idx: usize, path: &Path64) -> usize {
225        let scale = self.nodes[parent_idx].scale;
226        let child_idx = self.nodes.len();
227        let mut node = PolyPathD::with_parent(parent_idx, scale);
228        let mut error_code = 0;
229        node.polygon = scale_path(path, scale, scale, &mut error_code);
230        self.nodes.push(node);
231        self.nodes[parent_idx].children.push(child_idx);
232        child_idx
233    }
234
235    pub fn root(&self) -> &PolyPathD {
236        &self.nodes[0]
237    }
238
239    pub fn clear(&mut self) {
240        self.nodes.clear();
241        let mut root = PolyPathD::new();
242        root.scale = 1.0;
243        self.nodes.push(root);
244    }
245
246    pub fn set_scale(&mut self, scale: f64) {
247        self.nodes[0].scale = scale;
248    }
249
250    pub fn level(&self, node_idx: usize) -> u32 {
251        let mut result = 0u32;
252        let mut p = self.nodes[node_idx].parent;
253        while let Some(parent_idx) = p {
254            result += 1;
255            p = self.nodes[parent_idx].parent;
256        }
257        result
258    }
259
260    pub fn is_hole(&self, node_idx: usize) -> bool {
261        let lvl = self.level(node_idx);
262        lvl > 0 && (lvl & 1) == 0
263    }
264}
265
266impl Default for PolyTreeD {
267    fn default() -> Self {
268        Self::new()
269    }
270}
271
272// ============================================================================
273// Clipper64 - Public int64 clipper
274// Direct port from clipper.engine.h line 459
275// ============================================================================
276
277/// Main integer-coordinate polygon clipper
278/// Direct port from clipper.engine.h line 459
279pub struct Clipper64 {
280    pub base: ClipperBase,
281}
282
283impl Clipper64 {
284    pub fn new() -> Self {
285        Self {
286            base: ClipperBase::new(),
287        }
288    }
289
290    /// Add subject paths
291    pub fn add_subject(&mut self, subjects: &Paths64) {
292        self.base.add_paths(subjects, PathType::Subject, false);
293    }
294
295    /// Add open subject paths
296    pub fn add_open_subject(&mut self, open_subjects: &Paths64) {
297        self.base.add_paths(open_subjects, PathType::Subject, true);
298    }
299
300    /// Add clip paths
301    pub fn add_clip(&mut self, clips: &Paths64) {
302        self.base.add_paths(clips, PathType::Clip, false);
303    }
304
305    /// Get error code
306    pub fn error_code(&self) -> i32 {
307        self.base.error_code
308    }
309
310    /// Set/get preserve collinear
311    pub fn set_preserve_collinear(&mut self, val: bool) {
312        self.base.preserve_collinear = val;
313    }
314
315    pub fn preserve_collinear(&self) -> bool {
316        self.base.preserve_collinear
317    }
318
319    /// Set/get reverse solution
320    pub fn set_reverse_solution(&mut self, val: bool) {
321        self.base.reverse_solution = val;
322    }
323
324    pub fn reverse_solution(&self) -> bool {
325        self.base.reverse_solution
326    }
327
328    /// Clear the clipper
329    pub fn clear(&mut self) {
330        self.base.clear();
331    }
332
333    /// Execute a clipping operation, returning closed and open paths
334    /// Direct port from clipper.engine.h Clipper64::Execute
335    pub fn execute(
336        &mut self,
337        clip_type: ClipType,
338        fill_rule: FillRule,
339        solution_closed: &mut Paths64,
340        mut solution_open: Option<&mut Paths64>,
341    ) -> bool {
342        solution_closed.clear();
343        if let Some(ref mut open) = solution_open {
344            open.clear();
345        }
346
347        if self.base.execute_internal(clip_type, fill_rule, false) {
348            self.build_paths64(solution_closed, solution_open);
349        }
350        self.base.clean_up();
351        self.base.succeeded
352    }
353
354    /// Execute a clipping operation, returning a polytree and open paths
355    /// Direct port from clipper.engine.h Clipper64::Execute (polytree version)
356    pub fn execute_tree(
357        &mut self,
358        clip_type: ClipType,
359        fill_rule: FillRule,
360        polytree: &mut PolyTree64,
361        open_paths: &mut Paths64,
362    ) -> bool {
363        polytree.clear();
364        open_paths.clear();
365
366        if self.base.execute_internal(clip_type, fill_rule, true) {
367            self.build_tree64(polytree, open_paths);
368        }
369        self.base.clean_up();
370        self.base.succeeded
371    }
372
373    /// Build output paths from outrec list
374    /// Direct port from clipper.engine.cpp Clipper64::BuildPaths64 (line 2992)
375    fn build_paths64(
376        &mut self,
377        solution_closed: &mut Paths64,
378        solution_open: Option<&mut Paths64>,
379    ) {
380        solution_closed.clear();
381        solution_closed.reserve(self.base.outrec_list.len());
382
383        let mut open_paths = Vec::new();
384
385        let mut i = 0;
386        while i < self.base.outrec_list.len() {
387            if self.base.outrec_list[i].pts.is_none() {
388                i += 1;
389                continue;
390            }
391
392            if self.base.outrec_list[i].is_open {
393                let op = self.base.outrec_list[i].pts.unwrap();
394                if let Some(path) = build_path64_from_outpt(
395                    op,
396                    self.base.reverse_solution,
397                    true,
398                    &self.base.outpt_arena,
399                ) {
400                    open_paths.push(path);
401                }
402            } else {
403                self.base.clean_collinear(i);
404                if self.base.outrec_list[i].pts.is_some() {
405                    let op = self.base.outrec_list[i].pts.unwrap();
406                    if let Some(path) = build_path64_from_outpt(
407                        op,
408                        self.base.reverse_solution,
409                        false,
410                        &self.base.outpt_arena,
411                    ) {
412                        solution_closed.push(path);
413                    }
414                }
415            }
416            i += 1;
417        }
418
419        if let Some(open) = solution_open {
420            *open = open_paths;
421        }
422    }
423
424    /// Build polytree output
425    /// Direct port from clipper.engine.cpp Clipper64::BuildTree64 (line 3027)
426    fn build_tree64(&mut self, polytree: &mut PolyTree64, open_paths: &mut Paths64) {
427        polytree.clear();
428        open_paths.clear();
429        if self.base.has_open_paths {
430            open_paths.reserve(self.base.outrec_list.len());
431        }
432
433        let mut i = 0;
434        while i < self.base.outrec_list.len() {
435            if self.base.outrec_list[i].pts.is_none() {
436                i += 1;
437                continue;
438            }
439
440            if self.base.outrec_list[i].is_open {
441                let op = self.base.outrec_list[i].pts.unwrap();
442                if let Some(path) = build_path64_from_outpt(
443                    op,
444                    self.base.reverse_solution,
445                    true,
446                    &self.base.outpt_arena,
447                ) {
448                    open_paths.push(path);
449                }
450                i += 1;
451                continue;
452            }
453
454            if self.base.check_bounds(i) {
455                self.base.recursive_check_owners(i, polytree);
456            }
457            i += 1;
458        }
459    }
460}
461
462impl Default for Clipper64 {
463    fn default() -> Self {
464        Self::new()
465    }
466}
467
468/// Double-precision polygon clipper that scales to int64 internally
469/// Direct port from clipper.engine.h line 520
470pub struct ClipperD {
471    pub base: ClipperBase,
472    scale: f64,
473    inv_scale: f64,
474}
475
476impl ClipperD {
477    pub fn new(precision: i32) -> Self {
478        let mut prec = precision;
479        let mut error_code = 0;
480        check_precision_range(&mut prec, &mut error_code);
481
482        // Set the scale to a power of double's radix (2)
483        let scale = 2.0f64.powi(((10.0f64.powi(prec)).log2().floor() as i32) + 1);
484        let inv_scale = 1.0 / scale;
485
486        let mut base = ClipperBase::new();
487        base.error_code = error_code;
488
489        Self {
490            base,
491            scale,
492            inv_scale,
493        }
494    }
495
496    pub fn scale(&self) -> f64 {
497        self.scale
498    }
499
500    pub fn inv_scale(&self) -> f64 {
501        self.inv_scale
502    }
503
504    /// Add subject paths (double precision)
505    pub fn add_subject(&mut self, subjects: &PathsD) {
506        let scaled: Paths64 =
507            scale_paths(subjects, self.scale, self.scale, &mut self.base.error_code);
508        self.base.add_paths(&scaled, PathType::Subject, false);
509    }
510
511    /// Add open subject paths (double precision)
512    pub fn add_open_subject(&mut self, open_subjects: &PathsD) {
513        let scaled: Paths64 = scale_paths(
514            open_subjects,
515            self.scale,
516            self.scale,
517            &mut self.base.error_code,
518        );
519        self.base.add_paths(&scaled, PathType::Subject, true);
520    }
521
522    /// Add clip paths (double precision)
523    pub fn add_clip(&mut self, clips: &PathsD) {
524        let scaled: Paths64 = scale_paths(clips, self.scale, self.scale, &mut self.base.error_code);
525        self.base.add_paths(&scaled, PathType::Clip, false);
526    }
527
528    pub fn error_code(&self) -> i32 {
529        self.base.error_code
530    }
531
532    pub fn set_preserve_collinear(&mut self, val: bool) {
533        self.base.preserve_collinear = val;
534    }
535
536    pub fn preserve_collinear(&self) -> bool {
537        self.base.preserve_collinear
538    }
539
540    pub fn set_reverse_solution(&mut self, val: bool) {
541        self.base.reverse_solution = val;
542    }
543
544    pub fn reverse_solution(&self) -> bool {
545        self.base.reverse_solution
546    }
547
548    pub fn clear(&mut self) {
549        self.base.clear();
550    }
551
552    /// Execute a clipping operation with double-precision paths
553    /// Direct port from clipper.engine.h ClipperD::Execute
554    pub fn execute(
555        &mut self,
556        clip_type: ClipType,
557        fill_rule: FillRule,
558        solution_closed: &mut PathsD,
559        mut solution_open: Option<&mut PathsD>,
560    ) -> bool {
561        solution_closed.clear();
562        if let Some(ref mut open) = solution_open {
563            open.clear();
564        }
565
566        if self.base.execute_internal(clip_type, fill_rule, false) {
567            self.build_paths_d(solution_closed, solution_open);
568        }
569        self.base.clean_up();
570        self.base.succeeded
571    }
572
573    /// Execute returning polytree with double-precision
574    pub fn execute_tree(
575        &mut self,
576        clip_type: ClipType,
577        fill_rule: FillRule,
578        polytree: &mut PolyTreeD,
579        open_paths: &mut PathsD,
580    ) -> bool {
581        polytree.clear();
582        open_paths.clear();
583
584        if self.base.execute_internal(clip_type, fill_rule, true) {
585            self.build_tree_d(polytree, open_paths);
586        }
587        self.base.clean_up();
588        self.base.succeeded
589    }
590
591    /// Build output paths for double-precision
592    /// Direct port from clipper.engine.cpp ClipperD::BuildPathsD (line 3101)
593    fn build_paths_d(&mut self, solution_closed: &mut PathsD, solution_open: Option<&mut PathsD>) {
594        solution_closed.clear();
595        solution_closed.reserve(self.base.outrec_list.len());
596
597        let mut open_paths = Vec::new();
598
599        let mut i = 0;
600        while i < self.base.outrec_list.len() {
601            if self.base.outrec_list[i].pts.is_none() {
602                i += 1;
603                continue;
604            }
605
606            if self.base.outrec_list[i].is_open {
607                let op = self.base.outrec_list[i].pts.unwrap();
608                if let Some(path) = build_path_d_from_outpt(
609                    op,
610                    self.base.reverse_solution,
611                    true,
612                    &self.base.outpt_arena,
613                    self.inv_scale,
614                ) {
615                    open_paths.push(path);
616                }
617            } else {
618                self.base.clean_collinear(i);
619                if self.base.outrec_list[i].pts.is_some() {
620                    let op = self.base.outrec_list[i].pts.unwrap();
621                    if let Some(path) = build_path_d_from_outpt(
622                        op,
623                        self.base.reverse_solution,
624                        false,
625                        &self.base.outpt_arena,
626                        self.inv_scale,
627                    ) {
628                        solution_closed.push(path);
629                    }
630                }
631            }
632            i += 1;
633        }
634
635        if let Some(open) = solution_open {
636            *open = open_paths;
637        }
638    }
639
640    /// Build polytree for double-precision
641    /// Direct port from clipper.engine.cpp ClipperD::BuildTreeD (line 3135)
642    fn build_tree_d(&mut self, polytree: &mut PolyTreeD, open_paths: &mut PathsD) {
643        polytree.clear();
644        polytree.set_scale(self.inv_scale);
645        open_paths.clear();
646        if self.base.has_open_paths {
647            open_paths.reserve(self.base.outrec_list.len());
648        }
649
650        // Build a PolyTree64 internally, then convert to PolyTreeD
651        // This matches the C++ approach where RecursiveCheckOwners works with Path64
652        let mut polytree64 = PolyTree64::new();
653
654        let mut i = 0;
655        while i < self.base.outrec_list.len() {
656            if self.base.outrec_list[i].pts.is_none() {
657                i += 1;
658                continue;
659            }
660
661            if self.base.outrec_list[i].is_open {
662                let op = self.base.outrec_list[i].pts.unwrap();
663                if let Some(path) = build_path_d_from_outpt(
664                    op,
665                    self.base.reverse_solution,
666                    true,
667                    &self.base.outpt_arena,
668                    self.inv_scale,
669                ) {
670                    open_paths.push(path);
671                }
672                i += 1;
673                continue;
674            }
675
676            if self.base.check_bounds(i) {
677                self.base.recursive_check_owners(i, &mut polytree64);
678            }
679            i += 1;
680        }
681
682        // Convert PolyTree64 to PolyTreeD by scaling all paths
683        self.convert_polytree64_to_d(&polytree64, polytree);
684    }
685
686    /// Convert a PolyTree64 to PolyTreeD by scaling all paths
687    fn convert_polytree64_to_d(&self, src: &PolyTree64, dst: &mut PolyTreeD) {
688        dst.clear();
689        dst.set_scale(self.inv_scale);
690        // Recursively convert nodes, skipping the root (index 0)
691        let root_children = src.nodes[0].children.clone();
692        for &child_idx in &root_children {
693            Self::convert_polypath64_node(src, child_idx, 0, dst);
694        }
695    }
696
697    fn convert_polypath64_node(
698        src: &PolyTree64,
699        src_idx: usize,
700        dst_parent: usize,
701        dst: &mut PolyTreeD,
702    ) {
703        let src_node = &src.nodes[src_idx];
704        let dst_idx = dst.add_child_from_path64(dst_parent, &src_node.polygon);
705        let children = src_node.children.clone();
706        for &child_idx in &children {
707            Self::convert_polypath64_node(src, child_idx, dst_idx, dst);
708        }
709    }
710}