game_gem/math/
transform.rs1use super::{Vec2, Angle};
4
5#[derive(Clone, Debug)]
10pub struct Transform {
11 pub position: Vec2,
13 pub rotation: Angle,
15 pub scale: Vec2,
17 pub parent: Option<usize>,
19 world_matrix: glam::Mat4,
21 dirty: bool,
23}
24
25impl Default for Transform {
26 fn default() -> Self {
27 Self {
28 position: Vec2::ZERO,
29 rotation: Angle::zero(),
30 scale: Vec2::ONE,
31 parent: None,
32 world_matrix: glam::Mat4::IDENTITY,
33 dirty: false,
34 }
35 }
36}
37
38impl Transform {
39 pub fn at(x: f32, y: f32) -> Self {
41 Self {
42 position: Vec2::new(x, y),
43 ..Default::default()
44 }
45 }
46
47 pub fn new(x: f32, y: f32, rotation_degrees: f32, scale: f32) -> Self {
49 Self {
50 position: Vec2::new(x, y),
51 rotation: Angle::from_degrees(rotation_degrees),
52 scale: Vec2::splat(scale),
53 ..Default::default()
54 }
55 }
56
57 pub fn set_position(&mut self, pos: Vec2) {
59 self.position = pos;
60 self.dirty = true;
61 }
62
63 pub fn set_rotation_degrees(&mut self, degrees: f32) {
65 self.rotation = Angle::from_degrees(degrees);
66 self.dirty = true;
67 }
68
69 pub fn set_scale(&mut self, s: f32) {
71 self.scale = Vec2::splat(s);
72 self.dirty = true;
73 }
74
75 pub fn set_scale_vec(&mut self, s: Vec2) {
77 self.scale = s;
78 self.dirty = true;
79 }
80
81 pub fn translate(&mut self, offset: Vec2) {
83 self.position += offset;
84 self.dirty = true;
85 }
86
87 pub fn rotate(&mut self, angle: Angle) {
89 self.rotation += angle;
90 self.dirty = true;
91 }
92
93 pub fn local_matrix(&self) -> glam::Mat4 {
95 let translation = glam::Mat4::from_translation(glam::Vec3::new(
96 self.position.x,
97 self.position.y,
98 0.0,
99 ));
100 let rotation = glam::Mat4::from_rotation_z(self.rotation.as_radians());
101 let scale = glam::Mat4::from_scale(glam::Vec3::new(self.scale.x, self.scale.y, 1.0));
102 translation * rotation * scale
103 }
104
105 pub fn world_matrix(&mut self, parent_world: Option<glam::Mat4>) -> glam::Mat4 {
109 if self.dirty || parent_world.is_some() {
110 let local = self.local_matrix();
111 self.world_matrix = match parent_world {
112 Some(pw) => pw * local,
113 None => local,
114 };
115 self.dirty = false;
116 }
117 self.world_matrix
118 }
119
120 pub fn mark_dirty(&mut self) {
122 self.dirty = true;
123 }
124
125 pub fn world_position(&self) -> Vec2 {
127 Vec2::new(self.world_matrix.w_axis.x, self.world_matrix.w_axis.y)
128 }
129
130 pub fn transform_point(&self, local_point: Vec2) -> Vec2 {
132 let transformed = self.world_matrix
133 * glam::Vec4::new(local_point.x, local_point.y, 0.0, 1.0);
134 Vec2::new(transformed.x, transformed.y)
135 }
136
137 pub fn inverse_transform_point(&self, world_point: Vec2) -> Vec2 {
139 let det = self.world_matrix.determinant();
143 if det.abs() < 1e-9 {
144 return world_point;
145 }
146 let inv = self.world_matrix.inverse();
147 let transformed = inv * glam::Vec4::new(world_point.x, world_point.y, 0.0, 1.0);
148 Vec2::new(transformed.x, transformed.y)
149 }
150
151 pub fn lerp_to(&self, other: &Transform, t: f32) -> Transform {
153 use super::FloatExt;
154 Transform {
155 position: self.position.lerp(other.position, t),
156 rotation: Angle::from_radians(
157 self.rotation.as_radians().lerp(other.rotation.as_radians(), t),
158 ),
159 scale: Vec2::new(
160 self.scale.x.lerp(other.scale.x, t),
161 self.scale.y.lerp(other.scale.y, t),
162 ),
163 parent: self.parent,
164 world_matrix: glam::Mat4::IDENTITY,
165 dirty: true,
166 }
167 }
168
169 pub fn look_at(&mut self, target: Vec2) {
171 let diff = target - self.position;
172 self.rotation = Angle::from_radians(diff.y.atan2(diff.x));
173 self.dirty = true;
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180
181 #[test]
182 fn test_default_transform() {
183 let t = Transform::default();
184 assert_eq!(t.position, Vec2::ZERO);
185 assert_eq!(t.rotation.as_radians(), 0.0);
186 assert_eq!(t.scale, Vec2::ONE);
187 }
188
189 #[test]
190 fn test_local_matrix_identity() {
191 let t = Transform::default();
192 let m = t.local_matrix();
193 assert!(m.abs_diff_eq(glam::Mat4::IDENTITY, 1e-6));
195 }
196
197 #[test]
198 fn test_translate() {
199 let mut t = Transform::default();
200 t.translate(Vec2::new(10.0, 20.0));
201 assert_eq!(t.position, Vec2::new(10.0, 20.0));
202 }
203}