Skip to content

Commit

Permalink
add TSTransform doctests, switch transform order
Browse files Browse the repository at this point in the history
  • Loading branch information
Tweoss committed Feb 1, 2024
1 parent b0d73d0 commit 18347d3
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 14 deletions.
8 changes: 7 additions & 1 deletion crates/egui_demo_lib/src/demo/pan_zoom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,14 @@ impl super::View for PanZoom {
if response.hovered() {
let original_zoom = self.transform.scaling;
let new_zoom = original_zoom * ui.ctx().input(|i| i.zoom_delta());

let layer_pos = self.transform.invert_pos(pointer);
let new_transform = TSTransform::new(self.transform.translation, new_zoom);
let new_pos = new_transform * layer_pos;

// Keep mouse centered.
let pan_delta = pointer / new_zoom - pointer / original_zoom;
let pan_delta = pointer - new_pos;

// Handle scrolling.
let pan_delta =
pan_delta + ui.ctx().input(|i| i.smooth_scroll_delta) / original_zoom;
Expand Down
71 changes: 58 additions & 13 deletions crates/emath/src/ts_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,61 +9,104 @@ use crate::{Pos2, Rect, Vec2};
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "bytemuck", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct TSTransform {
/// Translation amount.
pub translation: Vec2,

/// Scaling amount after translation, scaled around (0, 0).
/// Scaling applied first, scaled around (0, 0).
pub scaling: f32,

/// Translation amount, applied after translation.
pub translation: Vec2,
}

impl Eq for TSTransform {}

impl Default for TSTransform {
#[inline]
fn default() -> Self {
Self {
translation: Vec2::ZERO,
scaling: 1.0,
}
Self::IDENTITY
}
}

impl TSTransform {
pub const IDENTITY: Self = Self {
translation: Vec2::ZERO,
scaling: 1.0,
};

/// The translation is applied first, then scaling around 0, 0.
#[inline]
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)
}

/// Reverses the transformation from screen space to layer space.
///
/// ```
/// # 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);
/// assert_eq!(ts.invert_pos(p1), pos2(0.0, 0.0));
/// assert_eq!(ts.invert_pos(p2), pos2(5.0, 1.0));
/// ```
#[inline]
pub fn invert_pos(&self, pos: Pos2) -> Pos2 {
// First, reverse scale around 0, 0, then reverse transform.
let pos = pos / self.scaling;
pos - self.translation
// First, reverse translation, then reverse scaling.
(pos - self.translation) / self.scaling
}

/// Reverses the transformation from screen space to layer space.
///
/// ```
/// # use emath::{pos2, vec2, Rect, TSTransform};
/// let rect = Rect::from_min_max(pos2(16.0, 15.0), pos2(46.0, 30.0));
/// let ts = TSTransform::new(vec2(1.0, 0.0), 3.0);
/// let inverted = ts.invert_rect(rect);
/// assert_eq!(inverted.min, pos2(5.0, 5.0));
/// assert_eq!(inverted.max, pos2(15.0, 10.0));
/// ```
#[inline]
pub fn invert_rect(&self, rect: Rect) -> Rect {
Rect::from_min_max(self.invert_pos(rect.min), self.invert_pos(rect.max))
}

/// Transforms the given coordinate by translation then scaling.
///
/// ```
/// # 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 {
let pos = pos + self.translation;
pos * self.scaling
self.scaling * pos + self.translation
}

/// Transforms the given rectangle by translation then scaling.
///
/// ```
/// # 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),
Expand All @@ -76,6 +119,7 @@ impl TSTransform {
impl std::ops::Mul<Pos2> for TSTransform {
type Output = Pos2;

#[inline]
fn mul(self, pos: Pos2) -> Pos2 {
self.mul_pos(pos)
}
Expand All @@ -85,6 +129,7 @@ impl std::ops::Mul<Pos2> for TSTransform {
impl std::ops::Mul<Rect> for TSTransform {
type Output = Rect;

#[inline]
fn mul(self, rect: Rect) -> Rect {
self.mul_rect(rect)
}
Expand Down

0 comments on commit 18347d3

Please sign in to comment.