Skip to content

Commit

Permalink
Merge pull request #290 from linebender/affine_reflection
Browse files Browse the repository at this point in the history
Implement reflection over a line -> affine transformation
  • Loading branch information
richard-uk1 authored Jul 5, 2023
2 parents 68a8ff6 + 80399cf commit b3a978b
Showing 1 changed file with 84 additions and 1 deletion.
85 changes: 84 additions & 1 deletion src/affine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,50 @@ impl Affine {
Affine([1.0, skew_y, skew_x, 1.0, 0.0, 0.0])
}

/// Create an affine transform that represents reflection about the line `point + direction * t, t in (-infty, infty)`
///
/// # Examples
///
/// ```
/// # use kurbo::{Point, Vec2, Affine};
/// # fn assert_near(p0: Point, p1: Point) {
/// # assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
/// # }
/// let point = Point::new(1., 0.);
/// let vec = Vec2::new(1., 1.);
/// let map = Affine::reflect(point, vec);
/// assert_near(map * Point::new(1., 0.), Point::new(1., 0.));
/// assert_near(map * Point::new(2., 1.), Point::new(2., 1.));
/// assert_near(map * Point::new(2., 2.), Point::new(3., 1.));
/// ```
#[inline]
#[must_use]
pub fn reflect(point: impl Into<Point>, direction: impl Into<Vec2>) -> Self {
let point = point.into();
let direction = direction.into();

let n = Vec2 {
x: direction.y,
y: -direction.x,
}
.normalize();

// Compute Householder reflection matrix
let x2 = n.x * n.x;
let xy = n.x * n.y;
let y2 = n.y * n.y;
// Here we also add in the post translation, because it doesn't require any further calc.
let aff = Affine::new([
1. - 2. * x2,
-2. * xy,
-2. * xy,
1. - 2. * y2,
point.x,
point.y,
]);
aff.pre_translate(-point.to_vec2())
}

/// A rotation by `th` followed by `self`.
///
/// Equivalent to `self * Affine::rotate(th)`
Expand Down Expand Up @@ -432,13 +476,19 @@ impl From<mint::ColumnMatrix2x3<f64>> for Affine {

#[cfg(test)]
mod tests {
use crate::{Affine, Point};
use crate::{Affine, Point, Vec2};
use std::f64::consts::PI;

fn assert_near(p0: Point, p1: Point) {
assert!((p1 - p0).hypot() < 1e-9, "{p0:?} != {p1:?}");
}

fn affine_assert_near(a0: Affine, a1: Affine) {
for i in 0..6 {
assert!((a0.0[i] - a1.0[i]).abs() < 1e-9, "{a0:?} != {a1:?}");
}
}

#[test]
fn affine_basic() {
let p = Point::new(3.0, 4.0);
Expand Down Expand Up @@ -480,4 +530,37 @@ mod tests {
assert_near(a_inv * (a * py), py);
assert_near(a_inv * (a * pxy), pxy);
}

#[test]
fn reflection() {
affine_assert_near(
Affine::reflect(Point::ZERO, (1., 0.)),
Affine::new([1., 0., 0., -1., 0., 0.]),
);
affine_assert_near(
Affine::reflect(Point::ZERO, (0., 1.)),
Affine::new([-1., 0., 0., 1., 0., 0.]),
);
// y = x
affine_assert_near(
Affine::reflect(Point::ZERO, (1., 1.)),
Affine::new([0., 1., 1., 0., 0., 0.]),
);

// no translate
let point = Point::new(0., 0.);
let vec = Vec2::new(1., 1.);
let map = Affine::reflect(point, vec);
assert_near(map * Point::new(0., 0.), Point::new(0., 0.));
assert_near(map * Point::new(1., 1.), Point::new(1., 1.));
assert_near(map * Point::new(1., 2.), Point::new(2., 1.));

// with translate
let point = Point::new(1., 0.);
let vec = Vec2::new(1., 1.);
let map = Affine::reflect(point, vec);
assert_near(map * Point::new(1., 0.), Point::new(1., 0.));
assert_near(map * Point::new(2., 1.), Point::new(2., 1.));
assert_near(map * Point::new(2., 2.), Point::new(3., 1.));
}
}

0 comments on commit b3a978b

Please sign in to comment.