1#![deny(unsafe_code, warnings, clippy::all)]
2
3pub mod consts;
4mod convert;
5mod float_lerp;
6mod ops;
7
8use float_lerp::Lerp;
9use rand::{thread_rng, Rng};
10
11pub trait Vector3Coordinate:
12 num::Num
13 + num::ToPrimitive
14 + PartialOrd
15 + std::fmt::Display
16 + std::fmt::Debug
17 + std::ops::AddAssign
18 + std::ops::SubAssign
19 + std::ops::MulAssign
20 + std::ops::DivAssign
21 + Clone
22 + Copy
23{
24}
25impl Vector3Coordinate for f64 {}
26impl Vector3Coordinate for f32 {}
27impl Vector3Coordinate for i8 {}
28impl Vector3Coordinate for i16 {}
29impl Vector3Coordinate for i32 {}
30impl Vector3Coordinate for i64 {}
31impl Vector3Coordinate for i128 {}
32impl Vector3Coordinate for u8 {}
33impl Vector3Coordinate for u16 {}
34impl Vector3Coordinate for u32 {}
35impl Vector3Coordinate for u64 {}
36impl Vector3Coordinate for u128 {}
37
38#[derive(Debug, PartialOrd, PartialEq, Default, Clone, Copy)]
40pub struct Vector3<T: Vector3Coordinate> {
41 x: T,
42 y: T,
43 z: T,
44}
45
46impl<T: Vector3Coordinate + num::Float> Vector3<T>
47where
48 rand::distributions::Standard: rand::prelude::Distribution<T>,
49{
50 pub fn random() -> Self {
52 Vector3 {
53 x: thread_rng().gen(),
54 y: thread_rng().gen(),
55 z: thread_rng().gen(),
56 }
57 }
58
59 pub fn fuzzy_equal(&self, target: &Self, epsilon: f64) -> bool {
74 (self.x - target.x)
75 .abs()
76 .to_f64()
77 .expect("f64 should handle all values")
78 <= epsilon
79 && (self.y - target.y)
80 .abs()
81 .to_f64()
82 .expect("f64 should handle all values")
83 <= epsilon
84 && (self.z - target.z)
85 .abs()
86 .to_f64()
87 .expect("f64 should handle all values")
88 <= epsilon
89 }
90
91 pub fn lerp(&self, target: &Self, alpha: T) -> Self {
93 Vector3 {
94 x: self.x.lerp(target.x, alpha),
95 y: self.y.lerp(target.y, alpha),
96 z: self.z.lerp(target.z, alpha),
97 }
98 }
99}
100
101impl Vector3<f64> {
102 pub fn normalize(&mut self) {
104 *self /= self.magnitude();
105 }
106}
107
108impl Vector3<f32> {
109 pub fn normalize(&mut self) {
111 *self /= self.magnitude() as f32;
112 }
113}
114
115impl<T: Vector3Coordinate> Vector3<T> {
116 pub fn new(x: T, y: T, z: T) -> Self {
126 Vector3 { x, y, z }
127 }
128
129 pub fn magnitude(&self) -> f64 {
131 let mag2 = self.x * self.x + self.y * self.y + self.z * self.z;
132 mag2.to_f64().expect("f64 should handle all values").sqrt()
133 }
134
135 pub fn dot(&self, target: &Self) -> T {
137 self.x * target.x + self.y * target.y + self.z * target.z
138 }
139
140 pub fn cross(&self, target: &Self) -> Self {
142 Vector3 {
143 x: self.y * target.z - self.z * target.y,
144 y: self.z * target.x - self.x * target.z,
145 z: self.x * target.y - self.y * target.x,
146 }
147 }
148
149 pub fn max(&self, target: &Self) -> Self {
151 let x = if self.x > target.x { self.x } else { target.x };
152 let y = if self.y > target.y { self.y } else { target.y };
153 let z = if self.z > target.z { self.z } else { target.z };
154 Vector3 { x, y, z }
155 }
156
157 pub fn min(&self, target: &Self) -> Self {
159 let x = if self.x < target.x { self.x } else { target.x };
160 let y = if self.y < target.y { self.y } else { target.y };
161 let z = if self.z < target.z { self.z } else { target.z };
162 Vector3 { x, y, z }
163 }
164
165 pub fn angle(&self, target: &Self) -> f64 {
167 let dot_product = self
168 .dot(target)
169 .to_f64()
170 .expect("f64 should handle all values");
171 let magnitude_product = self.magnitude() * target.magnitude();
172 (dot_product / magnitude_product).acos()
173 }
174
175 pub fn angle_deg(&self, target: &Self) -> f64 {
177 self.angle(target) * (180.0 / std::f64::consts::PI)
178 }
179
180 pub const fn get_x(&self) -> T {
182 self.x
183 }
184
185 pub const fn get_y(&self) -> T {
187 self.y
188 }
189
190 pub const fn get_z(&self) -> T {
192 self.z
193 }
194}
195
196impl<T: Vector3Coordinate> std::fmt::Display for Vector3<T> {
197 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
198 write!(f, "Vector3({}, {}, {})", self.x, self.y, self.z)
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn angle() {
208 let angle = std::f64::consts::PI / 2.0;
209 let calc_angle = consts::X_AXIS.angle(&consts::Y_AXIS);
210 assert_eq!(calc_angle, angle);
211 }
212
213 #[test]
214 fn create() {
215 let my_vec = Vector3::new(1.3, 0.0, -5.35501);
216 assert_eq!(my_vec.get_x(), 1.3);
217 assert_eq!(my_vec.get_y(), 0.0);
218 assert_eq!(my_vec.get_z(), -5.35501);
219 }
220
221 #[test]
222 fn sum() {
223 let vec1 = Vector3::new(1.0, 2.0, 3.0);
224 let vec2 = Vector3::new(5.0, 0.0, -1.0);
225 assert_eq!(vec1 + vec2, Vector3::new(6.0, 2.0, 2.0));
226 }
227
228 #[test]
229 fn normalization() {
230 let mut test_vec: Vector3<f64> = Vector3::new(1.0, 2.3, 100.123);
231 test_vec.normalize();
232 assert_eq!(
233 test_vec,
234 Vector3::new(0.00998458316076644, 0.02296454126976281, 0.9996864198054183)
235 );
236 assert!((1.0 - test_vec.magnitude()).abs() < 0.00000001);
237 }
238
239 #[test]
240 fn lerp() {
241 let start = Vector3::new(0.0, 0.0, 0.0);
242 let end = Vector3::new(1.0, 2.0, 3.0);
243 let lerp_result = start.lerp(&end, 0.75);
244 assert_eq!(lerp_result, Vector3::new(0.75, 1.5, 2.25));
245 }
246
247 #[test]
248 fn dot_product() {
249 let vec1 = Vector3::new(1.0, 2.0, 3.0);
250 let vec2 = Vector3::new(5.0, 0.0, -1.0);
251 let dot_result = vec1.dot(&vec2);
252 assert_eq!(dot_result, 2.0);
253 }
254
255 #[test]
256 fn cross_product() {
257 let vec1 = Vector3::new(1.0, 0.0, 0.0);
258 let vec2 = Vector3::new(0.0, 1.0, 0.0);
259 let cross_result = vec1.cross(&vec2);
260 assert_eq!(cross_result, Vector3::new(0.0, 0.0, 1.0));
261 }
262
263 #[test]
264 fn max_components() {
265 let vec1 = Vector3::new(1.0, 5.0, 3.0);
266 let vec2 = Vector3::new(3.0, 2.0, 4.0);
267 let max_result = vec1.max(&vec2);
268 assert_eq!(max_result, Vector3::new(3.0, 5.0, 4.0));
269 }
270
271 #[test]
272 fn min_components() {
273 let vec1 = Vector3::new(1.0, 5.0, 3.0);
274 let vec2 = Vector3::new(3.0, 2.0, 4.0);
275 let min_result = vec1.min(&vec2);
276 assert_eq!(min_result, Vector3::new(1.0, 2.0, 3.0));
277 }
278
279 #[test]
280 fn fuzzy_equality() {
281 let vec1 = Vector3::new(1.0, 2.0, 3.0);
282 let vec2 = Vector3::new(1.01, 1.99, 3.01);
283 let epsilon = 0.02;
284 let fuzzy_equal_result = vec1.fuzzy_equal(&vec2, epsilon);
285 assert!(fuzzy_equal_result);
286 }
287 #[test]
288 fn nan_dont_panic() {
289 let mut vec1: Vector3<f64> = Vector3::default();
290 vec1 /= std::f64::NAN;
291 }
292 #[test]
293 fn readme_example() {
294 let mut v1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
295 let mut v2: Vector3<f64> = Vector3::new(3.0, 1.0, 2.0);
296
297 let sum = v1 + v2;
299 let difference = v1 - v2;
300 let dot_product = v1.dot(&v2);
301 let cross_product = v1.cross(&v2);
302
303 let lerp_result = v1.lerp(&v2, 0.5);
305 let angle = v1.angle(&v2);
306 let fuzzy_equal = v1.fuzzy_equal(&v2, 0.001);
307
308 println!("Sum: {sum}");
309 println!("Difference: {difference}");
310 println!("Dot product: {dot_product}");
311 println!("Cross product: {cross_product}");
312 println!("Lerp 50%: {lerp_result}");
313 println!("Angle: {angle}");
314 print!("Are they close enough?: {fuzzy_equal}");
315
316 v1.normalize();
317 v2.normalize();
318
319 println!("v1 normalized: {v1}");
320 println!("v2 normalized: {v2}");
321 }
322}