1use crate::vector::*;
4use crate::BinarySerializable;
5use glam::Quat;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
16pub struct Rotator {
17 pub pitch: f32,
19 pub yaw: f32,
21 pub roll: f32,
23}
24
25impl fmt::Display for Rotator {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 write!(f, "P={:.2}° Y={:.2}° R={:.2}°", self.pitch, self.yaw, self.roll)
28 }
29}
30
31impl BinarySerializable for Rotator {}
32
33impl Rotator {
34 pub const ZERO: Self = Self {
36 pitch: 0.0,
37 yaw: 0.0,
38 roll: 0.0
39 };
40
41 pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self {
43 Self { pitch, yaw, roll }
44 }
45
46 pub fn from_yaw(yaw: f32) -> Self {
48 Self {
49 pitch: 0.0,
50 yaw,
51 roll: 0.0
52 }
53 }
54
55 pub fn from_pitch(pitch: f32) -> Self {
57 Self {
58 pitch,
59 yaw: 0.0,
60 roll: 0.0
61 }
62 }
63
64 pub fn from_roll(roll: f32) -> Self {
66 Self {
67 pitch: 0.0,
68 yaw: 0.0,
69 roll
70 }
71 }
72
73 pub fn to_quaternion(self) -> Quaternion {
75 let pitch_rad = self.pitch.to_radians();
76 let yaw_rad = self.yaw.to_radians();
77 let roll_rad = self.roll.to_radians();
78
79 Quat::from_euler(glam::EulerRot::YXZ, yaw_rad, pitch_rad, roll_rad)
81 }
82
83 pub fn from_quaternion(quat: Quaternion) -> Self {
85 let (yaw, pitch, roll) = quat.to_euler(glam::EulerRot::YXZ);
86 Self {
87 pitch: pitch.to_degrees(),
88 yaw: yaw.to_degrees(),
89 roll: roll.to_degrees(),
90 }
91 }
92
93 pub fn normalize(mut self) -> Self {
95 self.pitch = normalize_angle(self.pitch);
96 self.yaw = normalize_angle(self.yaw);
97 self.roll = normalize_angle(self.roll);
98 self
99 }
100
101 pub fn get_forward_vector(self) -> Vector {
103 self.to_quaternion() * VectorConstants::FORWARD
104 }
105
106 pub fn get_right_vector(self) -> Vector {
108 self.to_quaternion() * VectorConstants::RIGHT
109 }
110
111 pub fn get_up_vector(self) -> Vector {
113 self.to_quaternion() * VectorConstants::UP
114 }
115
116 pub fn is_nearly_zero(self, tolerance: f32) -> bool {
118 self.pitch.abs() <= tolerance
119 && self.yaw.abs() <= tolerance
120 && self.roll.abs() <= tolerance
121 }
122
123 pub fn is_nearly_equal(self, other: Rotator, tolerance: f32) -> bool {
125 (self.pitch - other.pitch).abs() <= tolerance
126 && (self.yaw - other.yaw).abs() <= tolerance
127 && (self.roll - other.roll).abs() <= tolerance
128 }
129
130 pub fn add(self, other: Rotator) -> Self {
132 Self {
133 pitch: self.pitch + other.pitch,
134 yaw: self.yaw + other.yaw,
135 roll: self.roll + other.roll,
136 }
137 }
138
139 pub fn sub(self, other: Rotator) -> Self {
141 Self {
142 pitch: self.pitch - other.pitch,
143 yaw: self.yaw - other.yaw,
144 roll: self.roll - other.roll,
145 }
146 }
147
148 pub fn scale(self, factor: f32) -> Self {
150 Self {
151 pitch: self.pitch * factor,
152 yaw: self.yaw * factor,
153 roll: self.roll * factor,
154 }
155 }
156}
157
158impl Default for Rotator {
159 fn default() -> Self {
160 Self::ZERO
161 }
162}
163
164pub fn normalize_angle(angle: f32) -> f32 {
166 let mut result = angle % 360.0;
167 if result > 180.0 {
168 result -= 360.0;
169 } else if result < -180.0 {
170 result += 360.0;
171 }
172 result
173}
174
175pub fn angle_difference(angle1: f32, angle2: f32) -> f32 {
178 normalize_angle(angle2 - angle1)
179}
180
181pub fn lerp_rotator(a: Rotator, b: Rotator, alpha: f32) -> Rotator {
183 Rotator {
184 pitch: a.pitch + alpha * angle_difference(a.pitch, b.pitch),
185 yaw: a.yaw + alpha * angle_difference(a.yaw, b.yaw),
186 roll: a.roll + alpha * angle_difference(a.roll, b.roll),
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
195 fn test_rotator_creation() {
196 let rot = Rotator::new(45.0, 90.0, 0.0);
197 assert_eq!(rot.pitch, 45.0);
198 assert_eq!(rot.yaw, 90.0);
199 assert_eq!(rot.roll, 0.0);
200 }
201
202 #[test]
203 fn test_normalize_angle() {
204 assert_eq!(normalize_angle(270.0), -90.0);
205 assert_eq!(normalize_angle(-270.0), 90.0);
206 assert_eq!(normalize_angle(180.0), 180.0);
207 assert_eq!(normalize_angle(-180.0), -180.0);
208 }
209
210 #[test]
211 fn test_quaternion_conversion() {
212 let rot = Rotator::new(0.0, 90.0, 0.0);
213 let quat = rot.to_quaternion();
214 let back_to_rot = Rotator::from_quaternion(quat);
215
216 assert!((rot.yaw - back_to_rot.yaw).abs() < 0.001);
218 }
219
220 #[test]
221 fn test_forward_vector() {
222 let rot = Rotator::from_yaw(90.0);
223 let forward = rot.get_forward_vector();
224
225 assert!((forward.y - 1.0).abs() < 0.001);
227 assert!(forward.x.abs() < 0.001);
228 }
229
230 #[test]
231 fn test_rotator_display() {
232 let rot = Rotator::new(45.0, 90.0, -30.0);
233 let display_str = format!("{}", rot);
234 assert!(display_str.contains("P=45.00°"));
235 assert!(display_str.contains("Y=90.00°"));
236 assert!(display_str.contains("R=-30.00°"));
237 }
238
239 #[test]
240 fn test_rotator_json_serialization() {
241 let rot = Rotator::new(45.0, 90.0, -30.0);
242
243 let json = serde_json::to_string(&rot).unwrap();
245 let deserialized: Rotator = serde_json::from_str(&json).unwrap();
246
247 assert!(rot.is_nearly_equal(deserialized, 0.001));
248 }
249
250 #[test]
251 fn test_rotator_binary_serialization() {
252 let rot = Rotator::new(45.0, 90.0, -30.0);
253
254 let binary = rot.to_binary().unwrap();
256 let deserialized = Rotator::from_binary(&binary).unwrap();
257
258 assert!(rot.is_nearly_equal(deserialized, 0.001));
259 }
260}