use super::{Vec2, Angle};
#[derive(Clone, Debug)]
pub struct Transform {
pub position: Vec2,
pub rotation: Angle,
pub scale: Vec2,
pub parent: Option<usize>,
world_matrix: glam::Mat4,
dirty: bool,
}
impl Default for Transform {
fn default() -> Self {
Self {
position: Vec2::ZERO,
rotation: Angle::zero(),
scale: Vec2::ONE,
parent: None,
world_matrix: glam::Mat4::IDENTITY,
dirty: false,
}
}
}
impl Transform {
pub fn at(x: f32, y: f32) -> Self {
Self {
position: Vec2::new(x, y),
..Default::default()
}
}
pub fn new(x: f32, y: f32, rotation_degrees: f32, scale: f32) -> Self {
Self {
position: Vec2::new(x, y),
rotation: Angle::from_degrees(rotation_degrees),
scale: Vec2::splat(scale),
..Default::default()
}
}
pub fn set_position(&mut self, pos: Vec2) {
self.position = pos;
self.dirty = true;
}
pub fn set_rotation_degrees(&mut self, degrees: f32) {
self.rotation = Angle::from_degrees(degrees);
self.dirty = true;
}
pub fn set_scale(&mut self, s: f32) {
self.scale = Vec2::splat(s);
self.dirty = true;
}
pub fn set_scale_vec(&mut self, s: Vec2) {
self.scale = s;
self.dirty = true;
}
pub fn translate(&mut self, offset: Vec2) {
self.position += offset;
self.dirty = true;
}
pub fn rotate(&mut self, angle: Angle) {
self.rotation += angle;
self.dirty = true;
}
pub fn local_matrix(&self) -> glam::Mat4 {
let translation = glam::Mat4::from_translation(glam::Vec3::new(
self.position.x,
self.position.y,
0.0,
));
let rotation = glam::Mat4::from_rotation_z(self.rotation.as_radians());
let scale = glam::Mat4::from_scale(glam::Vec3::new(self.scale.x, self.scale.y, 1.0));
translation * rotation * scale
}
pub fn world_matrix(&mut self, parent_world: Option<glam::Mat4>) -> glam::Mat4 {
if self.dirty || parent_world.is_some() {
let local = self.local_matrix();
self.world_matrix = match parent_world {
Some(pw) => pw * local,
None => local,
};
self.dirty = false;
}
self.world_matrix
}
pub fn mark_dirty(&mut self) {
self.dirty = true;
}
pub fn world_position(&self) -> Vec2 {
Vec2::new(self.world_matrix.w_axis.x, self.world_matrix.w_axis.y)
}
pub fn transform_point(&self, local_point: Vec2) -> Vec2 {
let transformed = self.world_matrix
* glam::Vec4::new(local_point.x, local_point.y, 0.0, 1.0);
Vec2::new(transformed.x, transformed.y)
}
pub fn inverse_transform_point(&self, world_point: Vec2) -> Vec2 {
let det = self.world_matrix.determinant();
if det.abs() < 1e-9 {
return world_point;
}
let inv = self.world_matrix.inverse();
let transformed = inv * glam::Vec4::new(world_point.x, world_point.y, 0.0, 1.0);
Vec2::new(transformed.x, transformed.y)
}
pub fn lerp_to(&self, other: &Transform, t: f32) -> Transform {
use super::FloatExt;
Transform {
position: self.position.lerp(other.position, t),
rotation: Angle::from_radians(
self.rotation.as_radians().lerp(other.rotation.as_radians(), t),
),
scale: Vec2::new(
self.scale.x.lerp(other.scale.x, t),
self.scale.y.lerp(other.scale.y, t),
),
parent: self.parent,
world_matrix: glam::Mat4::IDENTITY,
dirty: true,
}
}
pub fn look_at(&mut self, target: Vec2) {
let diff = target - self.position;
self.rotation = Angle::from_radians(diff.y.atan2(diff.x));
self.dirty = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_transform() {
let t = Transform::default();
assert_eq!(t.position, Vec2::ZERO);
assert_eq!(t.rotation.as_radians(), 0.0);
assert_eq!(t.scale, Vec2::ONE);
}
#[test]
fn test_local_matrix_identity() {
let t = Transform::default();
let m = t.local_matrix();
assert!(m.abs_diff_eq(glam::Mat4::IDENTITY, 1e-6));
}
#[test]
fn test_translate() {
let mut t = Transform::default();
t.translate(Vec2::new(10.0, 20.0));
assert_eq!(t.position, Vec2::new(10.0, 20.0));
}
}