sidereon_core/astro/math/
vec3.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
9pub enum Vec3Error {
10 #[error("invalid vec3 {field}: {reason}")]
12 InvalidInput {
13 field: &'static str,
14 reason: &'static str,
15 },
16}
17
18#[inline]
24pub fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
25 debug_assert!(finite3(&a));
26 debug_assert!(finite3(&b));
27 let out = [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
28 debug_assert!(finite3(&out));
29 out
30}
31
32#[inline]
34pub fn checked_add3(a: [f64; 3], b: [f64; 3]) -> Result<[f64; 3], Vec3Error> {
35 validate_finite3(&a, "a")?;
36 validate_finite3(&b, "b")?;
37 let out = [a[0] + b[0], a[1] + b[1], a[2] + b[2]];
38 validate_finite3(&out, "sum")?;
39 Ok(out)
40}
41
42#[inline]
43pub fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
44 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
45}
46
47#[inline]
48pub fn neg3(v: [f64; 3]) -> [f64; 3] {
49 [-v[0], -v[1], -v[2]]
50}
51
52#[inline]
53pub fn scale3(v: [f64; 3], s: f64) -> [f64; 3] {
54 [v[0] * s, v[1] * s, v[2] * s]
55}
56
57#[inline]
58pub fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
59 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
60}
61
62#[inline]
63pub fn dot3_ref(a: &[f64; 3], b: &[f64; 3]) -> f64 {
64 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
65}
66
67#[inline]
68pub fn dot3_z_yx_ref(a: &[f64; 3], b: &[f64; 3]) -> f64 {
69 a[2] * b[2] + (a[1] * b[1] + a[0] * b[0])
70}
71
72#[inline]
73pub fn dot3_fused_z_yx_ref(a: &[f64; 3], b: &[f64; 3]) -> f64 {
74 a[2].mul_add(b[2], a[1].mul_add(b[1], a[0] * b[0]))
75}
76
77#[inline]
78pub fn norm3(v: [f64; 3]) -> f64 {
79 dot3(v, v).sqrt()
80}
81
82#[inline]
83pub fn norm3_ref(v: &[f64; 3]) -> f64 {
84 dot3_ref(v, v).sqrt()
85}
86
87#[inline]
88pub fn unit3(v: [f64; 3]) -> Option<[f64; 3]> {
89 match norm3(v) {
90 n if n > 0.0 => Some(scale3(v, 1.0 / n)),
91 _ => None,
92 }
93}
94
95#[inline]
96pub fn unit3_ref_unchecked(v: &[f64; 3]) -> [f64; 3] {
97 let n = norm3_ref(v);
98 [v[0] / n, v[1] / n, v[2] / n]
99}
100
101#[inline]
102pub fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
103 [
104 a[1] * b[2] - a[2] * b[1],
105 a[2] * b[0] - a[0] * b[2],
106 a[0] * b[1] - a[1] * b[0],
107 ]
108}
109
110#[inline]
111pub fn cross3_ref(a: &[f64; 3], b: &[f64; 3]) -> [f64; 3] {
112 [
113 a[1] * b[2] - a[2] * b[1],
114 a[2] * b[0] - a[0] * b[2],
115 a[0] * b[1] - a[1] * b[0],
116 ]
117}
118
119#[inline]
120fn finite3(v: &[f64; 3]) -> bool {
121 v.iter().all(|value| value.is_finite())
122}
123
124#[inline]
125fn validate_finite3(v: &[f64; 3], field: &'static str) -> Result<(), Vec3Error> {
126 if finite3(v) {
127 Ok(())
128 } else {
129 Err(Vec3Error::InvalidInput {
130 field,
131 reason: "not finite",
132 })
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn named_dot_orders_match_their_explicit_recipes() {
142 let a = [1.25, -2.5, 3.75];
143 let b = [-4.0, 5.5, -6.25];
144
145 assert_eq!(
146 dot3(a, b).to_bits(),
147 (a[0] * b[0] + a[1] * b[1] + a[2] * b[2]).to_bits()
148 );
149 assert_eq!(
150 dot3_z_yx_ref(&a, &b).to_bits(),
151 (a[2] * b[2] + (a[1] * b[1] + a[0] * b[0])).to_bits()
152 );
153 assert_eq!(
154 dot3_fused_z_yx_ref(&a, &b).to_bits(),
155 a[2].mul_add(b[2], a[1].mul_add(b[1], a[0] * b[0]))
156 .to_bits()
157 );
158 }
159
160 #[test]
161 fn unit3_zero_vector_returns_none() {
162 assert_eq!(unit3([0.0, 0.0, 0.0]), None);
163 }
164
165 #[test]
166 fn checked_add3_rejects_non_finite_inputs_and_outputs() {
167 assert_eq!(
168 checked_add3([f64::NAN, 0.0, 0.0], [1.0, 2.0, 3.0]),
169 Err(Vec3Error::InvalidInput {
170 field: "a",
171 reason: "not finite"
172 })
173 );
174 assert_eq!(
175 checked_add3([1.0, 2.0, 3.0], [f64::INFINITY, 0.0, 0.0]),
176 Err(Vec3Error::InvalidInput {
177 field: "b",
178 reason: "not finite"
179 })
180 );
181 assert_eq!(
182 checked_add3([f64::MAX, 0.0, 0.0], [f64::MAX, 0.0, 0.0]),
183 Err(Vec3Error::InvalidInput {
184 field: "sum",
185 reason: "not finite"
186 })
187 );
188 }
189}