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::ZYX, yaw_rad, pitch_rad, roll_rad)
82 }
83
84 pub fn from_quaternion(quat: Quaternion) -> Self {
86 let (z_rad, y_rad, x_rad) = quat.to_euler(glam::EulerRot::ZYX);
90 Self {
91 pitch: y_rad.to_degrees(), yaw: z_rad.to_degrees(), roll: x_rad.to_degrees(), }
95 }
96
97 pub fn normalize(mut self) -> Self {
99 self.pitch = normalize_angle(self.pitch);
100 self.yaw = normalize_angle(self.yaw);
101 self.roll = normalize_angle(self.roll);
102 self
103 }
104
105 pub fn get_forward_vector(self) -> Vector {
107 self.to_quaternion() * VectorConstants::FORWARD
108 }
109
110 pub fn get_right_vector(self) -> Vector {
112 self.to_quaternion() * VectorConstants::RIGHT
113 }
114
115 pub fn get_up_vector(self) -> Vector {
117 self.to_quaternion() * VectorConstants::UP
118 }
119
120 pub fn is_nearly_zero(self, tolerance: f32) -> bool {
122 self.pitch.abs() <= tolerance
123 && self.yaw.abs() <= tolerance
124 && self.roll.abs() <= tolerance
125 }
126
127 pub fn is_nearly_equal(self, other: Rotator, tolerance: f32) -> bool {
129 (self.pitch - other.pitch).abs() <= tolerance
130 && (self.yaw - other.yaw).abs() <= tolerance
131 && (self.roll - other.roll).abs() <= tolerance
132 }
133
134 pub fn add(self, other: Rotator) -> Self {
136 Self {
137 pitch: self.pitch + other.pitch,
138 yaw: self.yaw + other.yaw,
139 roll: self.roll + other.roll,
140 }
141 }
142
143 pub fn sub(self, other: Rotator) -> Self {
145 Self {
146 pitch: self.pitch - other.pitch,
147 yaw: self.yaw - other.yaw,
148 roll: self.roll - other.roll,
149 }
150 }
151
152 pub fn scale(self, factor: f32) -> Self {
154 Self {
155 pitch: self.pitch * factor,
156 yaw: self.yaw * factor,
157 roll: self.roll * factor,
158 }
159 }
160}
161
162impl Default for Rotator {
163 fn default() -> Self {
164 Self::ZERO
165 }
166}
167
168pub fn normalize_angle(angle: f32) -> f32 {
170 let mut result = angle % 360.0;
171 if result > 180.0 {
172 result -= 360.0;
173 } else if result < -180.0 {
174 result += 360.0;
175 }
176 result
177}
178
179pub fn angle_difference(angle1: f32, angle2: f32) -> f32 {
182 normalize_angle(angle2 - angle1)
183}
184
185pub fn lerp_rotator(a: Rotator, b: Rotator, alpha: f32) -> Rotator {
187 Rotator {
188 pitch: a.pitch + alpha * angle_difference(a.pitch, b.pitch),
189 yaw: a.yaw + alpha * angle_difference(a.yaw, b.yaw),
190 roll: a.roll + alpha * angle_difference(a.roll, b.roll),
191 }
192}
193
194#[cfg(test)]
195mod tests {
196 use super::*;
197
198 #[test]
199 fn test_rotator_creation() {
200 let rot = Rotator::new(45.0, 90.0, 0.0);
201 assert_eq!(rot.pitch, 45.0);
202 assert_eq!(rot.yaw, 90.0);
203 assert_eq!(rot.roll, 0.0);
204 }
205
206 #[test]
207 fn test_normalize_angle() {
208 assert_eq!(normalize_angle(270.0), -90.0);
209 assert_eq!(normalize_angle(-270.0), 90.0);
210 assert_eq!(normalize_angle(180.0), 180.0);
211 assert_eq!(normalize_angle(-180.0), -180.0);
212 }
213
214 #[test]
215 fn test_quaternion_conversion() {
216 let rot = Rotator::new(0.0, 90.0, 0.0);
217 let quat = rot.to_quaternion();
218 let back_to_rot = Rotator::from_quaternion(quat);
219
220 assert!((rot.yaw - back_to_rot.yaw).abs() < 0.001);
222 }
223
224 #[test]
225 fn test_forward_vector() {
226 let rot = Rotator::from_yaw(90.0);
227 let forward = rot.get_forward_vector();
228
229 assert!((forward.y - 1.0).abs() < 0.001);
231 assert!(forward.x.abs() < 0.001);
232 assert!(forward.z.abs() < 0.001);
233 }
234
235 #[test]
236 fn test_rotation_vectors() {
237 let zero_rot = Rotator::ZERO;
239 let forward = zero_rot.get_forward_vector();
240 let right = zero_rot.get_right_vector();
241 let up = zero_rot.get_up_vector();
242
243 assert!((forward.x - 1.0).abs() < 0.001);
245 assert!(forward.y.abs() < 0.001);
246 assert!(forward.z.abs() < 0.001);
247
248 assert!(right.x.abs() < 0.001);
250 assert!((right.y - 1.0).abs() < 0.001);
251 assert!(right.z.abs() < 0.001);
252
253 assert!(up.x.abs() < 0.001);
255 assert!(up.y.abs() < 0.001);
256 assert!((up.z - 1.0).abs() < 0.001);
257 }
258
259 #[test]
260 fn test_pitch_rotation() {
261 let rot = Rotator::from_pitch(90.0);
262 let forward = rot.get_forward_vector();
263
264 assert!(forward.x.abs() < 0.001);
267 assert!(forward.y.abs() < 0.001);
268 assert!((forward.z + 1.0).abs() < 0.001); }
270
271 #[test]
272 fn test_negative_pitch_rotation() {
273 let rot = Rotator::from_pitch(-90.0);
274 let forward = rot.get_forward_vector();
275
276 assert!(forward.x.abs() < 0.001);
278 assert!(forward.y.abs() < 0.001);
279 assert!((forward.z - 1.0).abs() < 0.001); }
281
282 #[test]
283 fn test_quaternion_conversion_roundtrip() {
284 let original = Rotator::new(30.0, 45.0, 60.0);
285 let quat = original.to_quaternion();
286 let back_to_rot = Rotator::from_quaternion(quat);
287
288 assert!((original.pitch - back_to_rot.pitch).abs() < 0.01);
290 assert!((original.yaw - back_to_rot.yaw).abs() < 0.01);
291 assert!((original.roll - back_to_rot.roll).abs() < 0.01);
292 }
293
294 #[test]
295 fn test_rotator_display() {
296 let rot = Rotator::new(45.0, 90.0, -30.0);
297 let display_str = format!("{}", rot);
298 assert!(display_str.contains("P=45.00°"));
299 assert!(display_str.contains("Y=90.00°"));
300 assert!(display_str.contains("R=-30.00°"));
301 }
302
303 #[test]
304 fn test_rotator_json_serialization() {
305 let rot = Rotator::new(45.0, 90.0, -30.0);
306
307 let json = serde_json::to_string(&rot).unwrap();
309 let deserialized: Rotator = serde_json::from_str(&json).unwrap();
310
311 assert!(rot.is_nearly_equal(deserialized, 0.001));
312 }
313
314 #[test]
315 fn test_rotator_binary_serialization() {
316 let rot = Rotator::new(45.0, 90.0, -30.0);
317
318 let binary = rot.to_binary().unwrap();
320 let deserialized = Rotator::from_binary(&binary).unwrap();
321
322 assert!(rot.is_nearly_equal(deserialized, 0.001));
323 }
324}