Skip to content

Commit

Permalink
feat!: support scaling around a point
Browse files Browse the repository at this point in the history
* Change .scale method on Path and Paths to take both scale_x and scale_y as the original Clipper2 library does
* Add .scale_around_point that also takes a point argument for the point to scale around

BREAKING CHANGE: scale now takes two arguments, allowing separate x and
y scaling factors

---------

Co-authored-by: Fredrik Söderström <tirithen@gmail.com>
  • Loading branch information
msvbg and tirithen authored Jun 1, 2024
1 parent b9800b7 commit ba6dec3
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 16 deletions.
14 changes: 7 additions & 7 deletions src/bounds.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
use crate::Point;
use crate::{Centi, Point, PointScaler};

/// Represents an area from one min and one max [Point](struct.Point.html).
#[derive(Default, Debug, Copy, Clone, PartialEq)]
pub struct Bounds {
pub struct Bounds<P: PointScaler = Centi> {
/// Minimum point of the boundary.
pub min: Point,
pub min: Point<P>,
/// Maximum point of the boundary.
pub max: Point,
pub max: Point<P>,
}

impl Bounds {
impl<P: PointScaler> Bounds<P> {
/// Create a `Bounds` struct starting at xy 0.0 and ending at the given xy
/// coordinates.
#[must_use]
Expand All @@ -32,13 +32,13 @@ impl Bounds {

/// Return the size of the bounds area as a [Point](struct.Point.html).
#[must_use]
pub fn size(&self) -> Point {
pub fn size(&self) -> Point<P> {
Point::new(self.max.x() - self.min.x(), self.max.y() - self.min.y())
}

/// Return the center of the bounds area as a [Point](struct.Point.html).
#[must_use]
pub fn center(&self) -> Point {
pub fn center(&self) -> Point<P> {
let size = self.size();
Point::new(self.min.x() + size.x() / 2.0, self.min.y() + size.y() / 2.0)
}
Expand Down
33 changes: 27 additions & 6 deletions src/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,38 @@ impl<P: PointScaler> Path<P> {
}

/// Construct a scaled clone of the path with the origin at the path center
pub fn scale(&self, scale: f64) -> Self {
let bounds = self.bounds();
let center = bounds.center();
///
/// # Examples
///
/// ```rust
/// use clipper2::Path;
/// let path: Path = vec![(-1.0, -1.0), (1.0, 1.0)].into();
/// let scaled = path.scale(2.0, 2.0);
/// assert_eq!(scaled.iter().map(|p| (p.x(), p.y())).collect::<Vec<_>>(), vec![(-2.0, -2.0), (2.0, 2.0)]);
/// ```
pub fn scale(&self, scale_x: f64, scale_y: f64) -> Self {
let center = self.bounds().center();
self.scale_around_point(scale_x, scale_y, center)
}

/// Construct a scaled clone of the path with the origin at a given point
///
/// # Examples
///
/// ```rust
/// use clipper2::Path;
/// let path: Path = vec![(0.0, 0.0), (1.0, 1.0)].into();
/// let scaled = path.scale_around_point(2.0, 2.0, (0.0, 0.0).into());
/// assert_eq!(scaled.iter().map(|p| (p.x(), p.y())).collect::<Vec<_>>(), vec![(0.0, 0.0), (2.0, 2.0)]);
/// ```
pub fn scale_around_point(&self, scale_x: f64, scale_y: f64, point: Point<P>) -> Self {
Self::new(
self.0
.iter()
.map(|p| {
Point::<P>::new(
(center.x() - p.x()) * scale + center.x(),
(center.y() - p.y()) * scale + center.y(),
(p.x() - point.x()) * scale_x + point.x(),
(p.y() - point.y()) * scale_y + point.y(),
)
})
.collect(),
Expand Down Expand Up @@ -148,7 +169,7 @@ impl<P: PointScaler> Path<P> {
}

/// Returns the bounds for this path
pub fn bounds(&self) -> Bounds {
pub fn bounds(&self) -> Bounds<P> {
let mut bounds = Bounds::minmax();

for p in &self.0 {
Expand Down
67 changes: 64 additions & 3 deletions src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,19 @@ impl<P: PointScaler> Paths<P> {
}

/// Construct a scaled clone of the path with the origin at the path center.
pub fn scale(&self, scale: f64) -> Self {
Self::new(self.0.iter().map(|p| p.scale(scale)).collect())
pub fn scale(&self, scale_x: f64, scale_y: f64) -> Self {
let center = self.bounds().center();
self.scale_around_point(scale_x, scale_y, center)
}

/// Construct a scaled clone of the path with the origin at a given point.
pub fn scale_around_point(&self, scale_x: f64, scale_y: f64, point: Point<P>) -> Self {
Self::new(
self.0
.iter()
.map(|p| p.scale_around_point(scale_x, scale_y, point))
.collect(),
)
}

/// Construct a rotated clone of the path with the origin at the path
Expand All @@ -107,7 +118,7 @@ impl<P: PointScaler> Paths<P> {
}

/// Returns the bounds for this path.
pub fn bounds(&self) -> Bounds {
pub fn bounds(&self) -> Bounds<P> {
let mut bounds = Bounds::minmax();

for p in &self.0 {
Expand Down Expand Up @@ -459,6 +470,56 @@ mod test {
assert_eq!(area, 6000.0);
}

#[test]
fn test_scale_two_separate_triangles() {
let paths = Paths::<Centi>::from(vec![
vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)],
vec![(10.0, 10.0), (11.0, 10.0), (10.0, 11.0)],
]);

let scaled = paths.scale(4.0, 2.0);

let expected_output = Paths::<Centi>::from(vec![
vec![(-16.5, -5.5), (-12.5, -5.5), (-16.5, -3.5)],
vec![(23.5, 14.5), (27.5, 14.5), (23.5, 16.5)],
]);

assert_eq!(scaled, expected_output);
}

#[test]
fn test_scale_overlapping_rectangles() {
let paths = Paths::<Centi>::from(vec![
Path::rectangle(-10.0, -20.0, 20.0, 40.0),
Path::rectangle(-20.0, -10.0, 40.0, 20.0),
]);
let scaled = paths.scale(4.0, 2.0);

let expected_output = Paths::<Centi>::from(vec![
vec![(-40.0, -40.0), (40.0, -40.0), (40.0, 40.0), (-40.0, 40.0)],
vec![(-80.0, -20.0), (80.0, -20.0), (80.0, 20.0), (-80.0, 20.0)],
]);

assert_eq!(scaled, expected_output);
}

#[test]
fn test_scale_around_point() {
let paths = Paths::<Centi>::from(vec![
Path::rectangle(-10.0, -20.0, 20.0, 40.0),
Path::rectangle(-20.0, -10.0, 40.0, 20.0),
]);

let scaled = paths.scale_around_point(4.0, 2.0, Point::new(-10.0, -20.0));

let expected_output = Paths::<Centi>::from(vec![
vec![(-10.0, -20.0), (70.0, -20.0), (70.0, 60.0), (-10.0, 60.0)],
vec![(-50.0, 0.0), (110.0, 0.0), (110.0, 40.0), (-50.0, 40.0)],
]);

assert_eq!(scaled, expected_output);
}

#[cfg(feature = "serde")]
#[test]
fn test_serde() {
Expand Down

0 comments on commit ba6dec3

Please sign in to comment.