use clipper2c_sys::{
clipper_delete_path64, clipper_delete_paths64, clipper_paths64_minkowski_diff,
clipper_paths64_minkowski_sum, clipper_paths64_size,
};
use crate::{FillRule, Path, Paths, PointScaler, malloc};
pub fn minkowski_sum<P: PointScaler>(
pattern: impl Into<Path<P>>,
paths: impl Into<Paths<P>>,
is_closed: bool,
) -> Paths<P> {
minkowski(pattern, paths, is_closed, MinkowskiOp::Sum)
}
pub fn minkowski_diff<P: PointScaler>(
pattern: impl Into<Path<P>>,
paths: impl Into<Paths<P>>,
is_closed: bool,
) -> Paths<P> {
minkowski(pattern, paths, is_closed, MinkowskiOp::Diff)
}
#[derive(Clone, Copy)]
enum MinkowskiOp {
Sum,
Diff,
}
fn minkowski<P: PointScaler>(
pattern: impl Into<Path<P>>,
paths: impl Into<Paths<P>>,
is_closed: bool,
op: MinkowskiOp,
) -> Paths<P> {
let pattern: Path<P> = pattern.into();
let paths: Paths<P> = paths.into();
unsafe {
let mem = malloc(clipper_paths64_size());
let pattern_ptr = pattern.to_clipperpath64();
let paths_ptr = paths.to_clipperpaths64();
let result_ptr = match op {
MinkowskiOp::Sum => clipper_paths64_minkowski_sum(
mem,
pattern_ptr,
paths_ptr,
is_closed.into(),
FillRule::NonZero.into(),
),
MinkowskiOp::Diff => clipper_paths64_minkowski_diff(
mem,
pattern_ptr,
paths_ptr,
is_closed.into(),
FillRule::NonZero.into(),
),
};
clipper_delete_path64(pattern_ptr);
clipper_delete_paths64(paths_ptr);
let result = Paths::from_clipperpaths64(result_ptr);
clipper_delete_paths64(result_ptr);
result
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::Centi;
#[test]
fn minkowski_sum_unit_square_along_segment() {
let pattern: Path<Centi> = vec![(-0.5, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)].into();
let path: Path<Centi> = vec![(0.0, 0.0), (4.0, 0.0)].into();
let result = minkowski_sum(pattern, path, false);
assert!(!result.is_empty());
let bounds = result.bounds();
assert!((bounds.min.x() - -0.5).abs() < 1e-9);
assert!((bounds.max.x() - 4.5).abs() < 1e-9);
assert!((bounds.min.y() - -0.5).abs() < 1e-9);
assert!((bounds.max.y() - 0.5).abs() < 1e-9);
}
#[test]
fn minkowski_sum_and_diff_agree_for_symmetric_pattern() {
let pattern: Path<Centi> = vec![(-1.0, -1.0), (1.0, -1.0), (1.0, 1.0), (-1.0, 1.0)].into();
let path: Path<Centi> = vec![(0.0, 0.0), (3.0, 0.0), (3.0, 3.0), (0.0, 3.0)].into();
let sum = minkowski_sum(pattern.clone(), path.clone(), true);
let diff = minkowski_diff(pattern, path, true);
assert_eq!(sum.bounds().min, diff.bounds().min);
assert_eq!(sum.bounds().max, diff.bounds().max);
}
#[test]
fn minkowski_sum_covers_all_input_paths() {
let pattern: Path<Centi> = vec![(-0.2, -0.2), (0.2, -0.2), (0.2, 0.2), (-0.2, 0.2)].into();
let paths: Paths<Centi> = vec![
vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0)],
vec![(3.0, 0.0), (4.0, 0.0), (4.0, 1.0), (3.0, 1.0)],
]
.into();
let result = minkowski_sum(pattern, paths, true);
assert!(!result.is_empty());
let bounds = result.bounds();
assert!((bounds.min.x() - -0.2).abs() < 1e-9);
assert!((bounds.max.x() - 4.2).abs() < 1e-9);
assert!((bounds.min.y() - -0.2).abs() < 1e-9);
assert!((bounds.max.y() - 1.2).abs() < 1e-9);
}
}