Files
figcraft/crates/geom/src/transform.rs
T

62 lines
2.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Decomposing and composing the 2×3 node transforms the design panel shows as X, Y and rotation.
use kurbo::{Affine, Point, Vec2};
/// Rotation in degrees (counter-clockwise on screen, as Figma's rotation field shows it) of `xf`.
pub fn rotation_degrees(xf: Affine) -> f64 {
let [a, b, ..] = xf.as_coeffs();
// y-down: positive Figma rotation turns counter-clockwise on screen, i.e. a negative math angle.
let deg = -b.atan2(a).to_degrees();
let deg = if deg.abs() < 1e-9 { 0.0 } else { deg };
if deg <= -180.0 { deg + 360.0 } else { deg }
}
/// The translation part of `xf`.
pub fn translation(xf: Affine) -> Point {
let c = xf.as_coeffs();
Point::new(c[4], c[5])
}
/// Replace the translation of `xf`.
pub fn with_translation(xf: Affine, p: Point) -> Affine {
let c = xf.as_coeffs();
Affine::new([c[0], c[1], c[2], c[3], p.x, p.y])
}
/// Whether the linear part is a mirror (negative determinant).
pub fn is_flipped(xf: Affine) -> bool {
xf.determinant() < 0.0
}
/// A transform with the given translation and rotation (degrees, Figma convention).
pub fn from_position_rotation(x: f64, y: f64, degrees: f64) -> Affine {
Affine::translate(Vec2::new(x, y)) * Affine::rotate(-degrees.to_radians())
}
/// Change the rotation of `xf` to `degrees` about the centre of a `w`×`h` box in its local space,
/// keeping that centre in place and keeping any flip.
pub fn set_rotation_about_center(xf: Affine, w: f64, h: f64, degrees: f64) -> Affine {
let center_local = Point::new(w / 2.0, h / 2.0);
let center = xf * center_local;
let flip = if is_flipped(xf) { Affine::FLIP_X } else { Affine::IDENTITY };
let rot = Affine::rotate(-degrees.to_radians());
let lin = rot * flip;
// Solve translation so that lin * center_local + t = center.
let mapped = lin * center_local;
let t = center - mapped;
let c = lin.as_coeffs();
Affine::new([c[0], c[1], c[2], c[3], t.x, t.y])
}
/// Rotate `xf` by `degrees` (Figma convention) about the point `pivot` (parent space).
pub fn rotate_about(xf: Affine, pivot: Point, degrees: f64) -> Affine {
Affine::translate(pivot.to_vec2()) * Affine::rotate(-degrees.to_radians()) * Affine::translate(-pivot.to_vec2()) * xf
}
/// Round a value to `digits` decimals for display (avoids `-0` and float noise).
pub fn round_to(v: f64, digits: i32) -> f64 {
let f = 10f64.powi(digits);
let r = (v * f).round() / f;
if r == 0.0 { 0.0 } else { r }
}