1use glam::{Quat, Vec3};
2
3pub trait QuatExt: Sized {
5 fn rotate_negative_z_towards(forward: Vec3, up: Vec3) -> Option<Quat>;
12
13 fn rotate_positive_z_towards(forward: Vec3, up: Vec3) -> Option<Quat>;
20}
21
22impl QuatExt for Quat {
23 fn rotate_negative_z_towards(forward: Vec3, up: Vec3) -> Option<Quat> {
24 let forward = forward.normalize_or_zero();
25 let side = forward.cross(up).normalize_or_zero(); let up = side.cross(forward);
27
28 if forward != Vec3::ZERO && side != Vec3::ZERO && up != Vec3::ZERO {
29 Some(Self::from_mat3(&glam::Mat3::from_cols(side, up, -forward)))
30 } else {
31 None
32 }
33 }
34
35 fn rotate_positive_z_towards(forward: Vec3, up: Vec3) -> Option<Quat> {
36 let forward = forward.normalize_or_zero();
37 let side = up.cross(forward).normalize_or_zero(); let up = forward.cross(side);
39
40 if forward != Vec3::ZERO && side != Vec3::ZERO && up != Vec3::ZERO {
41 Some(Self::from_mat3(&glam::Mat3::from_cols(side, up, forward)))
42 } else {
43 None
44 }
45 }
46}
47
48#[cfg(test)]
49mod test {
50 use super::*;
51
52 #[test]
53 fn test_rotate_negative_z_towards() {
54 #![allow(clippy::disallowed_methods)] let desired_fwd = Vec3::new(1.0, 2.0, 3.0).normalize();
57 let desired_up = Vec3::new(4.0, 5.0, 6.0).normalize();
58
59 let rot = Quat::rotate_negative_z_towards(desired_fwd, desired_up).unwrap();
60
61 let rotated_z = rot * -Vec3::Z;
62 assert!(
63 (rotated_z - desired_fwd).length() < 1e-5,
64 "Expected to rotate -Z to {desired_fwd}, but got {rotated_z}"
65 );
66
67 let rotated_y = rot * Vec3::Y;
68 assert!(
69 rotated_y.dot(desired_up) >= 0.0,
70 "Expected to rotate +Y to point approximately towards {desired_up}, but got {rotated_y}",
71 );
72 }
73
74 #[test]
75 fn test_rotate_positive_z_towards() {
76 #![allow(clippy::disallowed_methods)] let desired_fwd = Vec3::new(1.0, 2.0, 3.0).normalize();
79 let desired_up = Vec3::new(4.0, 5.0, 6.0).normalize();
80
81 let rot = Quat::rotate_positive_z_towards(desired_fwd, desired_up).unwrap();
82
83 let rotated_z = rot * Vec3::Z;
84 assert!(
85 (rotated_z - desired_fwd).length() < 1e-5,
86 "Expected to rotate +Z to {desired_fwd}, but got {rotated_z}",
87 );
88
89 let rotated_y = rot * Vec3::Y;
90 assert!(
91 rotated_y.dot(desired_up) >= 0.0,
92 "Expected to rotate +Y to point approximately towards {desired_up}, but got {rotated_y}",
93 );
94 }
95}