Skip to main content

alpha2_geometry/
affine.rs

1//! Affine transforms built from `libm`, and lengths from `sqrt` alone, so
2//! they are the same bits on every target.
3
4use kurbo::{Affine, Point, Vec2};
5
6/// A clockwise rotation by `degrees` in pixel space (y down), about the
7/// origin.
8#[must_use]
9pub fn rotation(degrees: f64) -> Affine {
10    let radians = degrees.to_radians();
11    let (sin, cos) = (libm::sin(radians), libm::cos(radians));
12    Affine::new([cos, sin, -sin, cos, 0.0, 0.0])
13}
14
15/// A clockwise rotation by `degrees` about `center`.
16#[must_use]
17pub fn rotation_about(degrees: f64, center: Point) -> Affine {
18    Affine::translate(center.to_vec2()) * rotation(degrees) * Affine::translate(-center.to_vec2())
19}
20
21/// A group's transform in pixel space: translate by minus `anchor`, scale,
22/// rotate clockwise, then translate to `position`.
23#[must_use]
24pub fn placement(position: Point, anchor: Point, scale: Vec2, degrees: f64) -> Affine {
25    Affine::translate(position.to_vec2())
26        * rotation(degrees)
27        * Affine::scale_non_uniform(scale.x, scale.y)
28        * Affine::translate(-anchor.to_vec2())
29}
30
31/// The length of `vector`, from `sqrt` alone.
32#[must_use]
33pub fn length(vector: Vec2) -> f64 {
34    (vector.x * vector.x + vector.y * vector.y).sqrt()
35}
36
37/// The distance between two points, from `sqrt` alone.
38#[must_use]
39pub fn distance(a: Point, b: Point) -> f64 {
40    length(b - a)
41}
42
43/// The largest factor `affine` stretches a length by: its larger singular
44/// value. Zero for a transform that collapses everything.
45#[must_use]
46pub fn max_scale(affine: Affine) -> f64 {
47    let [xx, yx, xy, yy, _, _] = affine.as_coeffs();
48    // The singular values are the square roots of the eigenvalues of M^T M.
49    let first = xx * xx + yx * yx;
50    let second = xy * xy + yy * yy;
51    let cross = xx * xy + yx * yy;
52    let half_sum = f64::midpoint(first, second);
53    let half_difference = 0.5 * (first - second);
54    let root = (half_difference * half_difference + cross * cross).sqrt();
55    (half_sum + root).max(0.0).sqrt()
56}
57
58/// Whether `affine` can be inverted: a finite, non-zero determinant.
59#[must_use]
60pub fn is_invertible(affine: Affine) -> bool {
61    let determinant = affine.determinant();
62    determinant.is_finite() && determinant.abs() > 1e-12
63}