emath/ts_transform.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
use crate::{Pos2, Rect, Vec2};
/// Linearly transforms positions via a translation, then a scaling.
///
/// [`TSTransform`] first scales points with the scaling origin at `0, 0`
/// (the top left corner), then translates them.
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct TSTransform {
/// Scaling applied first, scaled around (0, 0).
pub scaling: f32,
/// Translation amount, applied after scaling.
pub translation: Vec2,
}
impl Eq for TSTransform {}
impl Default for TSTransform {
#[inline]
fn default() -> Self {
Self::IDENTITY
}
}
impl TSTransform {
pub const IDENTITY: Self = Self {
translation: Vec2::ZERO,
scaling: 1.0,
};
#[inline]
/// Creates a new translation that first scales points around
/// `(0, 0)`, then translates them.
pub fn new(translation: Vec2, scaling: f32) -> Self {
Self {
translation,
scaling,
}
}
#[inline]
pub fn from_translation(translation: Vec2) -> Self {
Self::new(translation, 1.0)
}
#[inline]
pub fn from_scaling(scaling: f32) -> Self {
Self::new(Vec2::ZERO, scaling)
}
/// Inverts the transform.
///
/// ```
/// # use emath::{pos2, vec2, TSTransform};
/// let p1 = pos2(2.0, 3.0);
/// let p2 = pos2(12.0, 5.0);
/// let ts = TSTransform::new(vec2(2.0, 3.0), 2.0);
/// let inv = ts.inverse();
/// assert_eq!(inv.mul_pos(p1), pos2(0.0, 0.0));
/// assert_eq!(inv.mul_pos(p2), pos2(5.0, 1.0));
///
/// assert_eq!(ts.inverse().inverse(), ts);
/// ```
#[inline]
pub fn inverse(&self) -> Self {
Self::new(-self.translation / self.scaling, 1.0 / self.scaling)
}
/// Transforms the given coordinate.
///
/// ```
/// # use emath::{pos2, vec2, TSTransform};
/// let p1 = pos2(0.0, 0.0);
/// let p2 = pos2(5.0, 1.0);
/// let ts = TSTransform::new(vec2(2.0, 3.0), 2.0);
/// assert_eq!(ts.mul_pos(p1), pos2(2.0, 3.0));
/// assert_eq!(ts.mul_pos(p2), pos2(12.0, 5.0));
/// ```
#[inline]
pub fn mul_pos(&self, pos: Pos2) -> Pos2 {
self.scaling * pos + self.translation
}
/// Transforms the given rectangle.
///
/// ```
/// # use emath::{pos2, vec2, Rect, TSTransform};
/// let rect = Rect::from_min_max(pos2(5.0, 5.0), pos2(15.0, 10.0));
/// let ts = TSTransform::new(vec2(1.0, 0.0), 3.0);
/// let transformed = ts.mul_rect(rect);
/// assert_eq!(transformed.min, pos2(16.0, 15.0));
/// assert_eq!(transformed.max, pos2(46.0, 30.0));
/// ```
#[inline]
pub fn mul_rect(&self, rect: Rect) -> Rect {
Rect {
min: self.mul_pos(rect.min),
max: self.mul_pos(rect.max),
}
}
}
/// Transforms the position.
impl std::ops::Mul<Pos2> for TSTransform {
type Output = Pos2;
#[inline]
fn mul(self, pos: Pos2) -> Pos2 {
self.mul_pos(pos)
}
}
/// Transforms the rectangle.
impl std::ops::Mul<Rect> for TSTransform {
type Output = Rect;
#[inline]
fn mul(self, rect: Rect) -> Rect {
self.mul_rect(rect)
}
}
impl std::ops::Mul<Self> for TSTransform {
type Output = Self;
#[inline]
/// Applies the right hand side transform, then the left hand side.
///
/// ```
/// # use emath::{TSTransform, vec2};
/// let ts1 = TSTransform::new(vec2(1.0, 0.0), 2.0);
/// let ts2 = TSTransform::new(vec2(-1.0, -1.0), 3.0);
/// let ts_combined = TSTransform::new(vec2(2.0, -1.0), 6.0);
/// assert_eq!(ts_combined, ts2 * ts1);
/// ```
fn mul(self, rhs: Self) -> Self::Output {
// Apply rhs first.
Self {
scaling: self.scaling * rhs.scaling,
translation: self.translation + self.scaling * rhs.translation,
}
}
}