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 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
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) = ra.sin_cos();
316 let (sin_dec, cos_dec) = dec.sin_cos();
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 { 0.0 } else { self.y.atan2(self.x) };
342 let phi = if self.z == 0.0 {
343 0.0
344 } else {
345 self.z.atan2(d2.sqrt())
346 };
347
348 (theta, phi)
349 }
350}
351
352impl std::ops::Add for Vector3 {
354 type Output = Self;
355
356 fn add(self, rhs: Self) -> Self {
357 Self::new(self.x + rhs.x, self.y + rhs.y, self.z + rhs.z)
358 }
359}
360
361impl std::ops::Sub for Vector3 {
363 type Output = Self;
364
365 fn sub(self, rhs: Self) -> Self {
366 Self::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
367 }
368}
369
370impl std::ops::Mul<f64> for Vector3 {
372 type Output = Self;
373
374 fn mul(self, scalar: f64) -> Self {
375 Self::new(self.x * scalar, self.y * scalar, self.z * scalar)
376 }
377}
378
379impl std::ops::Mul<Vector3> for f64 {
381 type Output = Vector3;
382
383 fn mul(self, vec: Vector3) -> Vector3 {
384 vec * self
385 }
386}
387
388impl std::ops::Div<f64> for Vector3 {
390 type Output = Self;
391
392 fn div(self, scalar: f64) -> Self {
393 Self::new(self.x / scalar, self.y / scalar, self.z / scalar)
394 }
395}
396
397impl std::ops::DivAssign<f64> for Vector3 {
399 fn div_assign(&mut self, scalar: f64) {
400 self.x /= scalar;
401 self.y /= scalar;
402 self.z /= scalar;
403 }
404}
405
406impl std::ops::Neg for Vector3 {
408 type Output = Self;
409
410 fn neg(self) -> Self {
411 Self::new(-self.x, -self.y, -self.z)
412 }
413}
414
415impl std::ops::Index<usize> for Vector3 {
417 type Output = f64;
418
419 fn index(&self, index: usize) -> &f64 {
420 match index {
421 0 => &self.x,
422 1 => &self.y,
423 2 => &self.z,
424 _ => panic!("Vector3 index out of bounds: {}", index),
425 }
426 }
427}
428
429impl std::ops::IndexMut<usize> for Vector3 {
431 fn index_mut(&mut self, index: usize) -> &mut f64 {
432 match index {
433 0 => &mut self.x,
434 1 => &mut self.y,
435 2 => &mut self.z,
436 _ => panic!("Vector3 index out of bounds: {}", index),
437 }
438 }
439}
440
441impl fmt::Display for Vector3 {
442 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443 write!(f, "Vector3({:.9}, {:.9}, {:.9})", self.x, self.y, self.z)
444 }
445}
446
447#[cfg(test)]
448mod tests {
449 use super::*;
450
451 #[test]
452 fn test_vector3_construction() {
453 let v = Vector3::new(1.0, 2.0, 3.0);
454 assert_eq!(v.x, 1.0);
455 assert_eq!(v.y, 2.0);
456 assert_eq!(v.z, 3.0);
457
458 let zeros = Vector3::zeros();
459 assert_eq!(zeros.x, 0.0);
460 assert_eq!(zeros.y, 0.0);
461 assert_eq!(zeros.z, 0.0);
462
463 let x_axis = Vector3::x_axis();
464 assert_eq!(x_axis, Vector3::new(1.0, 0.0, 0.0));
465
466 let from_array = Vector3::from_array([4.0, 5.0, 6.0]);
467 assert_eq!(from_array, Vector3::new(4.0, 5.0, 6.0));
468 }
469
470 #[test]
471 fn test_vector3_magnitude() {
472 let v = Vector3::new(3.0, 4.0, 0.0);
473 assert_eq!(v.magnitude(), 5.0);
474 assert_eq!(v.magnitude_squared(), 25.0);
475
476 let unit = v.normalize();
477 assert!((unit.magnitude() - 1.0).abs() < 1e-15);
478 assert_eq!(unit, Vector3::new(0.6, 0.8, 0.0));
479 }
480
481 #[test]
482 fn test_vector3_arithmetic() {
483 let a = Vector3::new(1.0, 2.0, 3.0);
484 let b = Vector3::new(4.0, 5.0, 6.0);
485
486 let sum = a + b;
487 assert_eq!(sum, Vector3::new(5.0, 7.0, 9.0));
488
489 let diff = b - a;
490 assert_eq!(diff, Vector3::new(3.0, 3.0, 3.0));
491
492 let scaled = a * 2.0;
493 assert_eq!(scaled, Vector3::new(2.0, 4.0, 6.0));
494
495 let scaled2 = 3.0 * a;
496 assert_eq!(scaled2, Vector3::new(3.0, 6.0, 9.0));
497
498 let divided = a / 2.0;
499 assert_eq!(divided, Vector3::new(0.5, 1.0, 1.5));
500
501 let negated = -a;
502 assert_eq!(negated, Vector3::new(-1.0, -2.0, -3.0));
503 }
504
505 #[test]
506 fn test_vector3_dot_cross() {
507 let a = Vector3::new(1.0, 0.0, 0.0);
508 let b = Vector3::new(0.0, 1.0, 0.0);
509
510 assert_eq!(a.dot(&b), 0.0);
511
512 let c = a.cross(&b);
513 assert_eq!(c, Vector3::new(0.0, 0.0, 1.0));
514
515 let d = Vector3::new(1.0, 2.0, 3.0);
516 let e = Vector3::new(4.0, 5.0, 6.0);
517 assert_eq!(d.dot(&e), 32.0);
518 }
519
520 #[test]
521 fn test_vector3_spherical_conversion() {
522 let v1 = Vector3::from_spherical(0.0, 0.0);
523 assert!((v1.x - 1.0).abs() < 1e-15);
524 assert!(v1.y.abs() < 1e-15);
525 assert!(v1.z.abs() < 1e-15);
526
527 let (ra, dec) = v1.to_spherical();
528 assert!(ra.abs() < 1e-15);
529 assert!(dec.abs() < 1e-15);
530
531 let v2 = Vector3::from_spherical(crate::constants::HALF_PI, 0.0);
532 assert!(v2.x.abs() < 1e-15);
533 assert!((v2.y - 1.0).abs() < 1e-15);
534 assert!(v2.z.abs() < 1e-15);
535
536 let v3 = Vector3::from_spherical(0.0, crate::constants::HALF_PI);
537 assert!(v3.x.abs() < 1e-15);
538 assert!(v3.y.abs() < 1e-15);
539 assert!((v3.z - 1.0).abs() < 1e-15);
540 }
541
542 #[test]
543 fn test_axis_constructors() {
544 let y_axis = Vector3::y_axis();
546 assert_eq!(y_axis, Vector3::new(0.0, 1.0, 0.0));
547
548 let z_axis = Vector3::z_axis();
549 assert_eq!(z_axis, Vector3::new(0.0, 0.0, 1.0));
550 }
551
552 #[test]
553 fn test_get_set_methods() {
554 let mut v = Vector3::new(1.0, 2.0, 3.0);
555
556 assert_eq!(v.get(0).unwrap(), 1.0);
558 assert_eq!(v.get(1).unwrap(), 2.0);
559 assert_eq!(v.get(2).unwrap(), 3.0);
560
561 v.set(0, 10.0).unwrap();
563 v.set(1, 20.0).unwrap();
564 v.set(2, 30.0).unwrap();
565 assert_eq!(v, Vector3::new(10.0, 20.0, 30.0));
566 }
567
568 #[test]
569 fn test_get_error() {
570 let v = Vector3::new(1.0, 2.0, 3.0);
571 let result = v.get(3);
572 assert!(result.is_err());
573
574 if let Err(err) = result {
575 assert!(err.to_string().contains("index 3 out of bounds"));
576 }
577 }
578
579 #[test]
580 fn test_set_error() {
581 let mut v = Vector3::new(1.0, 2.0, 3.0);
582 let result = v.set(5, 42.0);
583 assert!(result.is_err());
584
585 if let Err(err) = result {
586 assert!(err.to_string().contains("index 5 out of bounds"));
587 }
588 }
589
590 #[test]
591 fn test_normalize_zero_vector() {
592 let zero = Vector3::zeros();
593 let normalized = zero.normalize();
594 assert_eq!(normalized, zero); }
596
597 #[test]
598 fn test_to_array() {
599 let v = Vector3::new(1.5, 2.5, 3.5);
600 let arr = v.to_array();
601 assert_eq!(arr, [1.5, 2.5, 3.5]);
602 }
603
604 #[test]
605 fn test_div_assign_operator() {
606 let mut v = Vector3::new(10.0, 20.0, 30.0);
607 v /= 2.0;
608 assert_eq!(v, Vector3::new(5.0, 10.0, 15.0));
609 }
610
611 #[test]
612 fn test_indexing_operators() {
613 let mut v = Vector3::new(1.0, 2.0, 3.0);
614
615 assert_eq!(v[0], 1.0);
617 assert_eq!(v[1], 2.0);
618 assert_eq!(v[2], 3.0);
619
620 v[0] = 10.0;
622 v[1] = 20.0;
623 v[2] = 30.0;
624 assert_eq!(v, Vector3::new(10.0, 20.0, 30.0));
625 }
626
627 #[test]
628 #[should_panic(expected = "Vector3 index out of bounds: 4")]
629 fn test_index_panic() {
630 let v = Vector3::new(1.0, 2.0, 3.0);
631 let _ = v[4];
632 }
633
634 #[test]
635 #[should_panic(expected = "Vector3 index out of bounds: 7")]
636 fn test_index_mut_panic() {
637 let mut v = Vector3::new(1.0, 2.0, 3.0);
638 v[7] = 42.0;
639 }
640
641 #[test]
642 fn test_display_formatting() {
643 let v = Vector3::new(1.234567890, -2.345678901, 3.456789012);
644 let display_output = format!("{}", v);
645
646 assert!(display_output.contains("Vector3("));
648 assert!(display_output.contains("1.234567890"));
649 assert!(display_output.contains("-2.345678901"));
650 assert!(display_output.contains("3.456789012"));
651 assert!(display_output.ends_with(")"));
652 }
653
654 #[test]
655 fn test_to_spherical_north_pole() {
656 let north_pole = Vector3::new(0.0, 0.0, 1.0);
657 let (theta, phi) = north_pole.to_spherical();
658
659 assert_eq!(theta, 0.0);
660 assert_eq!(phi, crate::constants::HALF_PI);
661 }
662
663 #[test]
664 fn test_to_spherical_south_pole() {
665 let south_pole = Vector3::new(0.0, 0.0, -1.0);
666 let (theta, phi) = south_pole.to_spherical();
667
668 assert_eq!(theta, 0.0);
669 assert_eq!(phi, -crate::constants::HALF_PI);
670 }
671
672 #[test]
673 fn test_to_spherical_zero_z() {
674 let on_equator = Vector3::new(1.0, 0.0, 0.0);
675 let (theta, phi) = on_equator.to_spherical();
676
677 assert_eq!(theta, 0.0);
678 assert_eq!(phi, 0.0);
679 }
680
681 #[test]
682 fn test_to_spherical_zero_vector() {
683 let zero = Vector3::zeros();
684 let (theta, phi) = zero.to_spherical();
685
686 assert_eq!(theta, 0.0);
687 assert_eq!(phi, 0.0);
688 }
689
690 #[test]
691 fn test_spherical_roundtrip_at_poles() {
692 let north_pole = Vector3::new(0.0, 0.0, 1.0);
693 let (theta, phi) = north_pole.to_spherical();
694 let roundtrip = Vector3::from_spherical(theta, phi);
695
696 assert_eq!(roundtrip.z, north_pole.z);
697 assert!(roundtrip.x.abs() < 1e-15, "x component: {}", roundtrip.x);
698 assert!(roundtrip.y.abs() < 1e-15, "y component: {}", roundtrip.y);
699
700 let south_pole = Vector3::new(0.0, 0.0, -1.0);
701 let (theta, phi) = south_pole.to_spherical();
702 let roundtrip = Vector3::from_spherical(theta, phi);
703
704 assert_eq!(roundtrip.z, south_pole.z);
705 assert!(roundtrip.x.abs() < 1e-15, "x component: {}", roundtrip.x);
706 assert!(roundtrip.y.abs() < 1e-15, "y component: {}", roundtrip.y);
707 }
708}