Skip to main content

clipper2_rust/
minkowski.rs

1// Copyright 2025 - Clipper2 Rust port
2// Ported from clipper.minkowski.h by Angus Johnson
3// Original Copyright: Angus Johnson 2010-2023
4// License: https://www.boost.org/LICENSE_1_0.txt
5//
6// Purpose: Minkowski Sum and Difference operations
7
8use crate::core::{is_positive, scale_path, scale_paths, Path64, PathD, Paths64, PathsD};
9use crate::engine::ClipType;
10use crate::engine_public::Clipper64;
11use crate::FillRule;
12
13// ============================================================================
14// Internal helper functions (equivalent to C++ detail namespace)
15// ============================================================================
16
17/// Core Minkowski operation that generates quads from pattern translated along path.
18///
19/// Direct port from C++ detail::Minkowski (clipper.minkowski.h lines 20-72).
20///
21/// For each point on `path`, translates the `pattern` (adding or subtracting
22/// depending on `is_sum`), then builds quadrilateral polygons connecting
23/// adjacent translated copies. The resulting quads are ensured to have
24/// positive orientation.
25///
26/// # Arguments
27/// * `pattern` - The pattern path to convolve
28/// * `path` - The path along which the pattern is translated
29/// * `is_sum` - If true, computes sum (p + pattern); if false, computes difference (p - pattern)
30/// * `is_closed` - If true, the path is treated as closed (last point connects to first)
31fn minkowski_internal(pattern: &Path64, path: &Path64, is_sum: bool, is_closed: bool) -> Paths64 {
32    let delta: usize = if is_closed { 0 } else { 1 };
33    let pat_len = pattern.len();
34    let path_len = path.len();
35
36    if pat_len == 0 || path_len == 0 {
37        return Paths64::new();
38    }
39
40    // Build translated copies of pattern at each path point
41    let mut tmp: Vec<Path64> = Vec::with_capacity(path_len);
42
43    if is_sum {
44        for p in path.iter() {
45            let path2: Path64 = pattern.iter().map(|pt2| *p + *pt2).collect();
46            tmp.push(path2);
47        }
48    } else {
49        for p in path.iter() {
50            let path2: Path64 = pattern.iter().map(|pt2| *p - *pt2).collect();
51            tmp.push(path2);
52        }
53    }
54
55    // Build quad polygons connecting adjacent translated copies
56    // Each quad connects: tmp[g][h], tmp[i][h], tmp[i][j], tmp[g][j]
57    let result_capacity = (path_len - delta) * pat_len;
58    let mut result: Paths64 = Vec::with_capacity(result_capacity);
59
60    let mut g: usize = if is_closed { path_len - 1 } else { 0 };
61
62    let mut i = delta;
63    while i < path_len {
64        let mut h: usize = pat_len - 1;
65        for j in 0..pat_len {
66            let mut quad: Path64 = vec![tmp[g][h], tmp[i][h], tmp[i][j], tmp[g][j]];
67
68            if !is_positive(&quad) {
69                quad.reverse();
70            }
71            result.push(quad);
72            h = j;
73        }
74        g = i;
75        i += 1;
76    }
77
78    result
79}
80
81/// Union a set of paths using the clipping engine.
82///
83/// Direct port from C++ detail::Union (clipper.minkowski.h lines 74-81).
84///
85/// # Arguments
86/// * `subjects` - The paths to union
87/// * `fill_rule` - The fill rule to use for the union operation
88fn union_paths(subjects: &Paths64, fill_rule: FillRule) -> Paths64 {
89    let mut result = Paths64::new();
90    let mut clipper = Clipper64::new();
91    clipper.add_subject(subjects);
92    clipper.execute(ClipType::Union, fill_rule, &mut result, None);
93    result
94}
95
96// ============================================================================
97// Public API functions
98// ============================================================================
99
100/// Compute the Minkowski Sum of a pattern and path using integer coordinates.
101///
102/// Direct port from C++ MinkowskiSum (Path64 overload, clipper.minkowski.h lines 85-88).
103///
104/// The Minkowski Sum is the set of all points that are the sum of any point
105/// in the pattern and any point in the path. Geometrically, it can be thought
106/// of as sweeping the pattern along the path.
107///
108/// # Arguments
109/// * `pattern` - The pattern path (typically a small convex polygon)
110/// * `path` - The path along which to sweep
111/// * `is_closed` - Whether the path should be treated as closed
112///
113/// # Returns
114/// The Minkowski sum as a set of paths (unioned into a clean result)
115pub fn minkowski_sum(pattern: &Path64, path: &Path64, is_closed: bool) -> Paths64 {
116    union_paths(
117        &minkowski_internal(pattern, path, true, is_closed),
118        FillRule::NonZero,
119    )
120}
121
122/// Compute the Minkowski Sum of a pattern and path using floating-point coordinates.
123///
124/// Direct port from C++ MinkowskiSum (PathD overload, clipper.minkowski.h lines 90-98).
125///
126/// Internally scales to integer coordinates, performs the operation, then scales back.
127///
128/// # Arguments
129/// * `pattern` - The pattern path in floating-point coordinates
130/// * `path` - The path along which to sweep
131/// * `is_closed` - Whether the path should be treated as closed
132/// * `decimal_places` - Number of decimal places of precision (default 2 in C++)
133///
134/// # Returns
135/// The Minkowski sum as a set of paths in floating-point coordinates
136pub fn minkowski_sum_d(
137    pattern: &PathD,
138    path: &PathD,
139    is_closed: bool,
140    decimal_places: i32,
141) -> PathsD {
142    let mut error_code: i32 = 0;
143    let scale = 10f64.powi(decimal_places);
144
145    let pat64: Path64 = scale_path(pattern, scale, scale, &mut error_code);
146    let path64: Path64 = scale_path(path, scale, scale, &mut error_code);
147
148    let tmp = union_paths(
149        &minkowski_internal(&pat64, &path64, true, is_closed),
150        FillRule::NonZero,
151    );
152
153    let inv_scale = 1.0 / scale;
154    scale_paths(&tmp, inv_scale, inv_scale, &mut error_code)
155}
156
157/// Compute the Minkowski Difference of a pattern and path using integer coordinates.
158///
159/// Direct port from C++ MinkowskiDiff (Path64 overload, clipper.minkowski.h lines 100-103).
160///
161/// The Minkowski Difference is similar to the Minkowski Sum but subtracts
162/// pattern points from path points instead of adding them.
163///
164/// # Arguments
165/// * `pattern` - The pattern path
166/// * `path` - The path from which to subtract
167/// * `is_closed` - Whether the path should be treated as closed
168///
169/// # Returns
170/// The Minkowski difference as a set of paths (unioned into a clean result)
171pub fn minkowski_diff(pattern: &Path64, path: &Path64, is_closed: bool) -> Paths64 {
172    union_paths(
173        &minkowski_internal(pattern, path, false, is_closed),
174        FillRule::NonZero,
175    )
176}
177
178/// Compute the Minkowski Difference of a pattern and path using floating-point coordinates.
179///
180/// Direct port from C++ MinkowskiDiff (PathD overload, clipper.minkowski.h lines 105-113).
181///
182/// Internally scales to integer coordinates, performs the operation, then scales back.
183///
184/// # Arguments
185/// * `pattern` - The pattern path in floating-point coordinates
186/// * `path` - The path from which to subtract
187/// * `is_closed` - Whether the path should be treated as closed
188/// * `decimal_places` - Number of decimal places of precision (default 2 in C++)
189///
190/// # Returns
191/// The Minkowski difference as a set of paths in floating-point coordinates
192pub fn minkowski_diff_d(
193    pattern: &PathD,
194    path: &PathD,
195    is_closed: bool,
196    decimal_places: i32,
197) -> PathsD {
198    let mut error_code: i32 = 0;
199    let scale = 10f64.powi(decimal_places);
200
201    let pat64: Path64 = scale_path(pattern, scale, scale, &mut error_code);
202    let path64: Path64 = scale_path(path, scale, scale, &mut error_code);
203
204    let tmp = union_paths(
205        &minkowski_internal(&pat64, &path64, false, is_closed),
206        FillRule::NonZero,
207    );
208
209    let inv_scale = 1.0 / scale;
210    scale_paths(&tmp, inv_scale, inv_scale, &mut error_code)
211}
212
213#[cfg(test)]
214#[path = "minkowski_tests.rs"]
215mod tests;