1use glam::{Mat4, Quat, Vec3};
4
5#[derive(Clone, Copy)]
7pub struct NodeTransform {
8 pub position: Vec3,
10 pub rotation: Quat,
12 pub scale: Vec3,
14 pub matrix: Mat4,
16 world_transform: WorldTransform,
18}
19
20#[derive(Clone, Copy)]
22pub struct WorldTransform {
23 pub position: Vec3,
25 pub rotation: Quat,
27 pub scale: Vec3,
29 pub matrix: Mat4,
31}
32
33impl Default for WorldTransform {
34 fn default() -> Self {
35 let mut out = Self {
36 position: Vec3::ZERO,
37 rotation: Quat::IDENTITY,
38 scale: Vec3::ONE,
39 matrix: Mat4::IDENTITY,
40 };
41
42 out.update_matrix();
43 out
44 }
45}
46
47impl std::ops::Add for NodeTransform {
48 type Output = NodeTransform;
49
50 fn add(self, rhs: Self) -> Self::Output {
51 let rotated_position = self.rotation * rhs.position; let position = self.position + rotated_position * self.scale; let rotation = (self.rotation * rhs.rotation).normalize();
54 let scale = self.scale * rhs.scale;
55
56 Self::new(position, rotation, scale)
57 }
58}
59
60impl std::ops::Add for WorldTransform {
62 type Output = WorldTransform;
63
64 fn add(self, rhs: Self) -> Self::Output {
65 let rotated_position = self.rotation * rhs.position; let position = self.position + rotated_position * self.scale; let rotation = (self.rotation * rhs.rotation).normalize();
68 let scale = self.scale * rhs.scale;
69
70 let mut result = Self {
71 position,
72 rotation,
73 scale,
74 matrix: Mat4::IDENTITY,
75 };
76
77 result.update_matrix();
78 result
79 }
80}
81
82impl WorldTransform {
83 fn update_matrix(&mut self) {
85 self.matrix =
86 Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position);
87 }
88}
89
90impl Default for NodeTransform {
91 fn default() -> Self {
93 let mut transform = Self {
94 position: Vec3::ZERO,
95 rotation: Quat::IDENTITY,
96 scale: Vec3::ONE,
97 matrix: Mat4::IDENTITY,
98 world_transform: WorldTransform::default(),
99 };
100 transform.update_matrix();
101 transform
102 }
103}
104
105impl PartialEq for NodeTransform {
106 fn eq(&self, other: &Self) -> bool {
108 self.position == other.position
109 && self.rotation == other.rotation
110 && self.scale == other.scale
111 && self.matrix == other.matrix
112 }
113}
114
115impl std::fmt::Debug for NodeTransform {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 write!(
118 f,
119 "Local => Position: {:?}, Rotation: {:?}, Scale: {:?}, Matrix: {:?}\n\
120 World => Position: {:?}, Rotation: {:?}, Scale: {:?}",
121 self.position,
122 self.rotation,
123 self.scale,
124 self.matrix,
125 self.world_transform.position,
126 self.world_transform.rotation,
127 self.world_transform.scale
128 )
129 }
130}
131
132impl NodeTransform {
133 pub fn new(position: impl Into<Vec3>, rotation: Quat, scale: impl Into<Vec3>) -> Self {
143 let mut transform = Self {
144 position: position.into(),
145 rotation,
146 scale: scale.into(),
147 matrix: Mat4::IDENTITY,
148 world_transform: WorldTransform::default(),
149 };
150 transform.update_matrix();
151 transform
152 }
153
154 fn update_matrix(&mut self) {
156 self.matrix =
157 Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.position);
158 }
159
160 pub fn world_space(&self) -> &WorldTransform {
164 &self.world_transform
165 }
166
167 pub fn get_world_space(&mut self, parent_space: WorldTransform) {
171 let local_world_space = WorldTransform {
175 position: self.position,
176 rotation: self.rotation,
177 scale: self.scale,
178 matrix: self.matrix,
179 };
180
181 self.world_transform = parent_space + local_world_space;
182 self.world_transform.update_matrix();
183 }
184
185 pub fn get_position(&self) -> &Vec3 {
190 &self.position
191 }
192
193 pub fn get_position_mut(&mut self) -> &mut Vec3 {
195 &mut self.position
196 }
197
198 pub fn lerp(a: &Self, b: &Self, t: f32) -> Self {
200 let position = a.position.lerp(b.position, t);
201 let rotation = a.rotation.slerp(b.rotation, t);
202 let scale = a.scale.lerp(b.scale, t);
203
204 Self::new(position, rotation, scale)
205 }
206
207 pub fn set_position(&mut self, position: impl Into<Vec3>) -> &mut Self {
215 self.position = position.into();
216 self.update_matrix();
217 self
218 }
219
220 pub fn get_rotation(&self) -> &Quat {
225 &self.rotation
226 }
227
228 pub fn get_rotation_mut(&mut self) -> &mut Quat {
230 &mut self.rotation
231 }
232
233 pub fn get_rotation_euler_xyz(&self) -> Vec3 {
238 let (x, y, z) = self.rotation.to_euler(glam::EulerRot::XYZ);
239 Vec3::new(x.to_degrees(), y.to_degrees(), z.to_degrees())
240 }
241
242 pub fn set_rotation(&mut self, rotation: Quat) -> &mut Self {
250 self.rotation = rotation;
251 self.update_matrix();
252 self
253 }
254
255 pub fn set_euler_xyz(&mut self, degrees: impl Into<Vec3>) -> &mut Self {
263 let degrees = degrees.into();
264
265 self.rotation = Quat::from_euler(
266 glam::EulerRot::XYZ,
267 degrees.x.to_radians(),
268 degrees.y.to_radians(),
269 degrees.z.to_radians(),
270 );
271 self.update_matrix();
272 self
273 }
274
275 pub fn get_scale(&self) -> &Vec3 {
280 &self.scale
281 }
282
283 pub fn get_scale_mut(&mut self) -> &mut Vec3 {
285 &mut self.scale
286 }
287
288 pub fn set_scale(&mut self, scale: impl Into<Vec3>) -> &mut Self {
295 self.scale = scale.into();
296 self.update_matrix();
297 self
298 }
299
300 pub fn get_forward_vector(&self) -> Vec3 {
305 self.rotation * Vec3::Z
306 }
307
308 pub fn get_right_vector(&self) -> Vec3 {
313 self.rotation * Vec3::X
314 }
315
316 pub fn get_up_vector(&self) -> Vec3 {
321 self.rotation * Vec3::Y
322 }
323
324 pub fn scale(&mut self, scale: impl Into<Vec3>) -> &mut Self {
332 self.scale *= scale.into();
333 self.update_matrix();
334 self
335 }
336
337 pub fn translate(&mut self, translation: impl Into<Vec3>) -> &mut Self {
345 self.position += translation.into();
346 self.update_matrix();
347 self
348 }
349
350 pub fn translate_world_space(&mut self, translation: impl Into<Vec3>) -> &mut Self {
356 self.position += self.rotation * translation.into();
357 self.update_matrix();
358 self
359 }
360
361 pub fn rotate(&mut self, axis: impl Into<Vec3>, degrees: f32) -> &mut Self {
370 let angle_quat = Quat::from_axis_angle(axis.into(), degrees.to_radians());
371 self.rotation = angle_quat * self.rotation;
372 self.update_matrix();
373 self
374 }
375
376 pub fn rotate_euler_xyz(&mut self, degrees: impl Into<Vec3>) -> &mut Self {
384 let degrees = degrees.into();
385
386 let euler_quat = Quat::from_euler(
387 glam::EulerRot::XYZ,
388 degrees.x.to_radians(),
389 degrees.y.to_radians(),
390 degrees.z.to_radians(),
391 );
392 self.rotation = euler_quat * self.rotation;
393 self.update_matrix();
394 self
395 }
396}
397
398impl From<NodeTransform> for WorldTransform {
399 fn from(value: NodeTransform) -> Self {
400 let mut out = Self {
401 position: value.position,
402 rotation: value.rotation,
403 scale: value.scale,
404 matrix: Mat4::IDENTITY,
405 };
406 out.update_matrix();
407 out
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414 use glam::{Mat4, Quat, Vec3};
415
416 #[test]
417 fn test_default_transform() {
418 let transform = NodeTransform::default();
419 assert_eq!(transform.position, Vec3::ZERO);
420 assert_eq!(transform.rotation, Quat::IDENTITY);
421 assert_eq!(transform.scale, Vec3::ONE);
422 assert_eq!(transform.matrix, Mat4::IDENTITY);
423 }
424
425 #[test]
426 fn test_translation() {
427 let mut transform = NodeTransform::default();
428 transform.translate(Vec3::new(1.0, 2.0, 3.0));
429 assert_eq!(transform.position, Vec3::new(1.0, 2.0, 3.0));
430 }
431
432 #[test]
433 fn test_rotation() {
434 let mut transform = NodeTransform::default();
435 transform.rotate(Vec3::Y, 90.0);
436 let expected_rotation = Quat::from_axis_angle(Vec3::Y, 90.0_f32.to_radians());
437 assert_eq!(transform.rotation, expected_rotation);
438 }
439
440 #[test]
441 fn test_scaling() {
442 let mut transform = NodeTransform::default();
443 transform.scale(Vec3::new(2.0, 3.0, 4.0));
444 assert_eq!(transform.scale, Vec3::new(2.0, 3.0, 4.0));
445 }
446
447 #[test]
448 fn test_model_matrix_update() {
449 let mut transform = NodeTransform::default();
450 transform.set_position(Vec3::new(1.0, 2.0, 3.0));
451 transform.set_scale(Vec3::new(2.0, 2.0, 2.0));
452 transform.set_rotation(Quat::from_axis_angle(Vec3::Y, 45.0_f32.to_radians()));
453
454 let expected_matrix = Mat4::from_scale_rotation_translation(
455 transform.scale,
456 transform.rotation,
457 transform.position,
458 );
459 assert_eq!(transform.matrix, expected_matrix);
460 }
461
462 #[test]
463 fn test_add_transform() {
464 const EPSILON: f32 = 1e-5;
465
466 fn approx_eq(v1: &Vec3, v2: &Vec3) -> bool {
467 (*v1 - *v2).length() < EPSILON
468 }
469
470 fn approx_eq_quat(q1: &Quat, q2: &Quat) -> bool {
471 q1.dot(*q2).abs() > 1.0 - EPSILON
472 }
473
474 let transform1 = NodeTransform::new(
475 Vec3::new(1.0, 0.0, 0.0),
476 Quat::from_axis_angle(Vec3::Y, 90.0_f32.to_radians()),
477 Vec3::new(2.0, 2.0, 2.0),
478 );
479
480 let transform2 = NodeTransform::new(
481 Vec3::new(0.0, 1.0, 0.0),
482 Quat::from_axis_angle(Vec3::X, 90.0_f32.to_radians()),
483 Vec3::new(0.5, 0.5, 0.5),
484 );
485
486 let result = transform1 + transform2;
487
488 let expected_position = Vec3::new(1.0, 2.0, 0.0);
489 let expected_rotation = (transform1.rotation * transform2.rotation).normalize();
490 let expected_scale = Vec3::new(1.0, 1.0, 1.0);
491
492 assert!(
493 approx_eq(&result.position, &expected_position),
494 "position: {:?} != {:?}",
495 result.position,
496 expected_position
497 );
498 assert!(
499 approx_eq_quat(&result.rotation, &expected_rotation),
500 "rotation: {:?} != {:?}",
501 result.rotation,
502 expected_rotation
503 );
504 assert!(
505 approx_eq(&result.scale, &expected_scale),
506 "scale: {:?} != {:?}",
507 result.scale,
508 expected_scale
509 );
510 }
511
512 #[test]
513 fn test_euler_rotation() {
514 let mut transform = NodeTransform::default();
515 transform.set_euler_xyz(Vec3::new(90.0, 0.0, 0.0));
516
517 let expected_rotation = Quat::from_axis_angle(Vec3::X, 90.0_f32.to_radians());
518 assert_eq!(transform.rotation, expected_rotation);
519 }
520
521 #[test]
522 fn test_get_euler() {
523 let mut transform = NodeTransform::default();
524 transform.set_euler_xyz(Vec3::new(90.0, 0.0, 0.0));
525
526 let result = transform.get_rotation_euler_xyz();
527 let expected = Vec3::new(90.0, 0.0, 0.0);
528
529 const EPSILON: f32 = 0.001; assert!(
532 (result.x - expected.x).abs() < EPSILON
533 && (result.y - expected.y).abs() < EPSILON
534 && (result.z - expected.z).abs() < EPSILON,
535 "Expected approximately {:?}, got {:?}",
536 expected,
537 result
538 );
539 }
540}