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;