1use crate::{AstroError, AstroResult, MathErrorKind};
77use std::fmt;
78
79#[derive(Debug, Clone, Copy, PartialEq)]
111#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
112pub struct Vector3 {
113 pub x: f64,
114 pub y: f64,
115 pub z: f64,
116}
117
118impl Vector3 {
119 #[inline]
121 pub fn new(x: f64, y: f64, z: f64) -> Self {
122 Self { x, y, z }
123 }
124
125 #[inline]
127 pub fn zeros() -> Self {
128 Self::new(0.0, 0.0, 0.0)
129 }
130
131 #[inline]
135 pub fn x_axis() -> Self {
136 Self::new(1.0, 0.0, 0.0)
137 }
138
139 #[inline]
143 pub fn y_axis() -> Self {
144 Self::new(0.0, 1.0, 0.0)
145 }
146
147 #[inline]
151 pub fn z_axis() -> Self {
152 Self::new(0.0, 0.0, 1.0)
153 }
154
155 pub fn get(&self, index: usize) -> AstroResult<f64> {
160 match index {
161 0 => Ok(self.x),
162 1 => Ok(self.y),
163 2 => Ok(self.z),
164 _ => Err(AstroError::math_error(
165 "Vector3::get",
166 MathErrorKind::InvalidInput,
167 &format!("index {} out of bounds (valid range: 0-2)", index),
168 )),
169 }
170 }
171
172 pub fn set(&mut self, index: usize, value: f64) -> AstroResult<()> {
177 match index {
178 0 => {
179 self.x = value;
180 Ok(())
181 }
182 1 => {
183 self.y = value;
184 Ok(())
185 }
186 2 => {
187 self.z = value;
188 Ok(())
189 }
190 _ => Err(AstroError::math_error(
191 "Vector3::set",
192 MathErrorKind::InvalidInput,
193 &format!("index {} out of bounds (valid range: 0-2)", index),
194 )),
195 }
196 }
197
198 #[inline]
202 pub fn magnitude(&self) -> f64 {
203 libm::sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
204 }
205
206 #[inline]
211 pub fn magnitude_squared(&self) -> f64 {
212 self.x * self.x + self.y * self.y + self.z * self.z
213 }
214
215 pub fn normalize(&self) -> Self {
228 let mag = self.magnitude();
229 if mag == 0.0 {
230 *self
231 } else {
232 Self::new(self.x / mag, self.y / mag, self.z / mag)
233 }
234 }
235
236 #[inline]
254 pub fn dot(&self, other: &Self) -> f64 {
255 self.x * other.x + self.y * other.y + self.z * other.z
256 }
257
258 pub fn cross(&self, other: &Self) -> Self {
272 Self::new(
273 self.y * other.z - self.z * other.y,
274 self.z * other.x - self.x * other.z,
275 self.x * other.y - self.y * other.x,
276 )
277 }
278
279 #[inline]
281 pub fn to_array(&self) -> [f64; 3] {
282 [self.x, self.y, self.z]
283 }
284
285 #[inline]
287 pub fn from_array(arr: [f64; 3]) -> Self {
288 Self::new(arr[0], arr[1], arr[2])
289 }
290
291 pub fn from_spherical(ra: f64, dec: f64) -> Self {
315 let (sin_ra, cos_ra) = libm::sincos(ra);
316 let (sin_dec, cos_dec) = libm::sincos(dec);
317 Self::new(cos_dec * cos_ra, cos_dec * sin_ra, sin_dec)
318 }
319
320 pub fn to_spherical(&self) -> (f64, f64) {
339 let d2 = self.x * self.x + self.y * self.y;
340
341 let theta = if d2 == 0.0 {
342 0.0
343 } else {
344 libm::atan2(self.y, self.x)
345 };
346 let phi = if self.z == 0.0 {
347 0.0
348 } else {
349 libm::atan2(self.z, libm::sqrt(d2))
350 };
351
352 (theta, phi)
353 }
354}
355
356impl std::ops::Add for Vector3 {
358 type Output = Self;
359
360 fn add(self, rhs: Self) -> Self {
361 Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
362 }
363}
364
365impl std::ops::Sub for Vector3 {
367 type Output = Self;
368
369 fn sub(self, rhs: Self) -> Self {
370 Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
371 }
372}
373
374impl std::ops::Mul<f64> for Vector3 {
376 type Output = Self;
377
378 fn mul(self, scalar: f64) -> Self {
379 Self::new(self.x * scalar, self.y * scalar, self.z * scalar)
380 }
381}
382
383impl std::ops::Mul<Vector3> for f64 {
385 type Output = Vector3;
386
387 fn mul(self, vec: Vector3) -> Vector3 {
388 vec * self
389 }
390}
391
392impl std::ops::Div<f64> for Vector3 {
394 type Output = Self;
395
396 fn div(self, scalar: f64) -> Self {
397 Self::new(self.x / scalar, self.y / scalar, self.z / scalar)
398 }
399}
400
401impl std::ops::DivAssign<f64> for Vector3 {
403 fn div_assign(&mut self, scalar: f64) {
404 self.x /= scalar;
405 self.y /= scalar;
406 self.z /= scalar;
407 }
408}
409
410impl std::ops::Neg for Vector3 {
412 type Output = Self;
413
414 fn neg(self) -> Self {
415 Self::new(-self.x, -self.y, -self.z)
416 }
417}
418
419impl std::ops::Index<usize> for Vector3 {
421 type Output = f64;
422
423 fn index(&self, index: usize) -> &f64 {
424 match index {
425 0 => &self.x,
426 1 => &self.y,
427 2 => &self.z,
428 _ => panic!("Vector3 index out of bounds: {}", index),
429 }
430 }
431}
432
433impl std::ops::IndexMut<usize> for Vector3 {
435 fn index_mut(&mut self, index: usize) -> &mut f64 {
436 match index {
437 0 => &mut self.x,
438 1 => &mut self.y,
439 2 => &mut self.z,
440 _ => panic!("Vector3 index out of bounds: {}", index),
441 }
442 }
443}
444
445impl fmt::Display for Vector3 {
446 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
447 write!(f, "Vector3({:.9}, {:.9}, {:.9})", self.x, self.y, self.z)
448 }
449}
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454
455 #[test]
456 fn test_vector3_construction() {
457 let v = Vector3::new(1.0, 2.0, 3.0);
458 assert_eq!(v.x, 1.0);
459 assert_eq!(v.y, 2.0);
460 assert_eq!(v.z, 3.0);
461
462 let zeros = Vector3::zeros();
463 assert_eq!(zeros.x, 0.0);
464 assert_eq!(zeros.y, 0.0);
465 assert_eq!(zeros.z, 0.0);
466
467 let x_axis = Vector3::x_axis();
468 assert_eq!(x_axis, Vector3::new(1.0, 0.0, 0.0));
469
470 let from_array = Vector3::from_array([4.0, 5.0, 6.0]);
471 assert_eq!(from_array, Vector3::new(4.0, 5.0, 6.0));
472 }
473
474 #[test]
475 fn test_vector3_magnitude() {
476 let v = Vector3::new(3.0, 4.0, 0.0);
477 assert_eq!(v.magnitude(), 5.0);
478 assert_eq!(v.magnitude_squared(), 25.0);
479
480 let unit = v.normalize();
481 assert!((unit.magnitude() - 1.0).abs() < 1e-15);
482 assert_eq!(unit, Vector3::new(0.6, 0.8, 0.0));
483 }
484
485 #[test]
486 fn test_vector3_arithmetic() {
487 let a = Vector3::new(1.0, 2.0, 3.0);
488 let b = Vector3::new(4.0, 5.0, 6.0);
489
490 let sum = a + b;
491 assert_eq!(sum, Vector3::new(5.0, 7.0, 9.0));
492
493 let diff = b - a;
494 assert_eq!(diff, Vector3::new(3.0, 3.0, 3.0));
495
496 let scaled = a * 2.0;
497 assert_eq!(scaled, Vector3::new(2.0, 4.0, 6.0));
498
499 let scaled2 = 3.0 * a;
500 assert_eq!(scaled2, Vector3::new(3.0, 6.0, 9.0));
501
502 let divided = a / 2.0;
503 assert_eq!(divided, Vector3::new(0.5, 1.0, 1.5));
504
505 let negated = -a;
506 assert_eq!(negated, Vector3::new(-1.0, -2.0, -3.0));
507 }
508
509 #[test]
510 fn test_vector3_dot_cross() {
511 let a = Vector3::new(1.0, 0.0, 0.0);
512 let b = Vector3::new(0.0, 1.0, 0.0);
513
514 assert_eq!(a.dot(&b), 0.0);
515
516 let c = a.cross(&b);
517 assert_eq!(c, Vector3::new(0.0, 0.0, 1.0));
518
519 let d = Vector3::new(1.0, 2.0, 3.0);
520 let e = Vector3::new(4.0, 5.0, 6.0);
521 assert_eq!(d.dot(&e), 32.0);
522 }
523
524 #[test]
525 fn test_vector3_spherical_conversion() {
526 let v1 = Vector3::from_spherical(0.0, 0.0);
527 assert!((v1.x - 1.0).abs() < 1e-15);
528 assert!(v1.y.abs() < 1e-15);
529 assert!(v1.z.abs() < 1e-15);
530
531 let (ra, dec) = v1.to_spherical();
532 assert!(ra.abs() < 1e-15);
533 assert!(dec.abs() < 1e-15);
534
535 let v2 = Vector3::from_spherical(crate::constants::HALF_PI, 0.0);
536 assert!(v2.x.abs() < 1e-15);
537 assert!((v2.y - 1.0).abs() < 1e-15);
538 assert!(v2.z.abs() < 1e-15);
539
540 let v3 = Vector3::from_spherical(0.0, crate::constants::HALF_PI);
541 assert!(v3.x.abs() < 1e-15);
542 assert!(v3.y.abs() < 1e-15);
543 assert!((v3.z - 1.0).abs() < 1e-15);
544 }
545
546 #[test]
547 fn test_axis_constructors() {
548 let y_axis = Vector3::y_axis();
550 assert_eq!(y_axis, Vector3::new(0.0, 1.0, 0.0));
551
552 let z_axis = Vector3::z_axis();
553 assert_eq!(z_axis, Vector3::new(0.0, 0.0, 1.0));
554 }
555
556 #[test]
557 fn test_get_set_methods() {
558 let mut v = Vector3::new(1.0, 2.0, 3.0);
559
560 assert_eq!(v.get(0).unwrap(), 1.0);
562 assert_eq!(v.get(1).unwrap(), 2.0);
563 assert_eq!(v.get(2).unwrap(), 3.0);
564
565 v.set(0, 10.0).unwrap();
567 v.set(1, 20.0).unwrap();
568 v.set(2, 30.0).unwrap();
569 assert_eq!(v, Vector3::new(10.0, 20.0, 30.0));
570 }
571
572 #[test]
573 fn test_get_error() {
574 let v = Vector3::new(1.0, 2.0, 3.0);
575 let result = v.get(3);
576 assert!(result.is_err());
577
578 if let Err(err) = result {
579 assert!(err.to_string().contains("index 3 out of bounds"));
580 }
581 }
582
583 #[test]
584 fn test_set_error() {
585 let mut v = Vector3::new(1.0, 2.0, 3.0);
586 let result = v.set(5, 42.0);
587 assert!(result.is_err());
588
589 if let Err(err) = result {
590 assert!(err.to_string().contains("index 5 out of bounds"));
591 }
592 }
593
594 #[test]
595 fn test_normalize_zero_vector() {
596 let zero = Vector3::zeros();
597 let normalized = zero.normalize();
598 assert_eq!(normalized, zero); }
600
601 #[test]
602 fn test_to_array() {
603 let v = Vector3::new(1.5, 2.5, 3.5);
604 let arr = v.to_array();
605 assert_eq!(arr, [1.5, 2.5, 3.5]);
606 }
607
608 #[test]
609 fn test_div_assign_operator() {
610 let mut v = Vector3::new(10.0, 20.0, 30.0);
611 v /= 2.0;
612 assert_eq!(v, Vector3::new(5.0, 10.0, 15.0));
613 }
614
615 #[test]
616 fn test_indexing_operators() {
617 let mut v = Vector3::new(1.0, 2.0, 3.0);
618
619 assert_eq!(v[0], 1.0);
621 assert_eq!(v[1], 2.0);
622 assert_eq!(v[2], 3.0);
623
624 v[0] = 10.0;
626 v[1] = 20.0;
627 v[2] = 30.0;
628 assert_eq!(v, Vector3::new(10.0, 20.0, 30.0));
629 }
630
631 #[test]
632 #[should_panic(expected = "Vector3 index out of bounds: 4")]
633 fn test_index_panic() {
634 let v = Vector3::new(1.0, 2.0, 3.0);
635 let _ = v[4];
636 }
637
638 #[test]
639 #[should_panic(expected = "Vector3 index out of bounds: 7")]
640 fn test_index_mut_panic() {
641 let mut v = Vector3::new(1.0, 2.0, 3.0);
642 v[7] = 42.0;
643 }
644
645 #[test]
646 fn test_display_formatting() {
647 let v = Vector3::new(1.234567890, -2.345678901, 3.456789012);
648 let display_output = format!("{}", v);
649
650 assert!(display_output.contains("Vector3("));
652 assert!(display_output.contains("1.234567890"));
653 assert!(display_output.contains("-2.345678901"));
654 assert!(display_output.contains("3.456789012"));
655 assert!(display_output.ends_with(")"));
656 }
657
658 #[test]
659 fn test_to_spherical_north_pole() {
660 let north_pole = Vector3::new(0.0, 0.0, 1.0);
661 let (theta, phi) = north_pole.to_spherical();
662
663 assert_eq!(theta, 0.0);
664 assert_eq!(phi, crate::constants::HALF_PI);
665 }
666
667 #[test]
668 fn test_to_spherical_south_pole() {
669 let south_pole = Vector3::new(0.0, 0.0, -1.0);
670 let (theta, phi) = south_pole.to_spherical();
671
672 assert_eq!(theta, 0.0);
673 assert_eq!(phi, -crate::constants::HALF_PI);
674 }
675
676 #[test]
677 fn test_to_spherical_zero_z() {
678 let on_equator = Vector3::new(1.0, 0.0, 0.0);
679 let (theta, phi) = on_equator.to_spherical();
680
681 assert_eq!(theta, 0.0);
682 assert_eq!(phi, 0.0);
683 }
684
685 #[test]
686 fn test_to_spherical_zero_vector() {
687 let zero = Vector3::zeros();
688 let (theta, phi) = zero.to_spherical();
689
690 assert_eq!(theta, 0.0);
691 assert_eq!(phi, 0.0);
692 }
693
694 #[test]
695 fn test_spherical_roundtrip_at_poles() {
696 let north_pole = Vector3::new(0.0, 0.0, 1.0);
697 let (theta, phi) = north_pole.to_spherical();
698 let roundtrip = Vector3::from_spherical(theta, phi);
699
700 assert_eq!(roundtrip.z, north_pole.z);
701 assert!(roundtrip.x.abs() < 1e-15, "x component: {}", roundtrip.x);
702 assert!(roundtrip.y.abs() < 1e-15, "y component: {}", roundtrip.y);
703
704 let south_pole = Vector3::new(0.0, 0.0, -1.0);
705 let (theta, phi) = south_pole.to_spherical();
706 let roundtrip = Vector3::from_spherical(theta, phi);
707
708 assert_eq!(roundtrip.z, south_pole.z);
709 assert!(roundtrip.x.abs() < 1e-15, "x component: {}", roundtrip.x);
710 assert!(roundtrip.y.abs() < 1e-15, "y component: {}", roundtrip.y);
711 }
712}