1mod consts;
4mod convert;
5mod float_lerp;
6mod ops;
7mod ops_scalar;
8
9use float_lerp::Lerp;
10use num_traits::clamp;
11#[cfg(feature = "random")]
12use rand::{
13 RngExt,
14 distr::uniform::{SampleRange, SampleUniform},
15 make_rng,
16 rngs::SmallRng,
17};
18#[cfg(feature = "serde")]
19use serde::{Deserialize, Serialize};
20
21thread_local! {
22 #[cfg(feature = "random")]
23 static RNG: std::cell::RefCell<SmallRng> = std::cell::RefCell::new(make_rng());
24}
25
26pub trait Vector3Coordinate:
28 num_traits::Num
29 + num_traits::ToPrimitive
30 + PartialOrd
31 + std::fmt::Display
32 + std::ops::AddAssign
33 + std::ops::SubAssign
34 + std::ops::MulAssign
35 + std::ops::DivAssign
36 + Clone
37{
38}
39
40impl<T> Vector3Coordinate for T where
41 T: num_traits::Num
42 + num_traits::ToPrimitive
43 + PartialOrd
44 + std::fmt::Display
45 + std::ops::AddAssign
46 + std::ops::SubAssign
47 + std::ops::MulAssign
48 + std::ops::DivAssign
49 + Clone
50{
51}
52
53#[derive(Debug, PartialEq, Eq, Default, Clone, Copy, Hash)]
55#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
56pub struct Vector3<T: Vector3Coordinate> {
57 x: T,
58 y: T,
59 z: T,
60}
61
62impl<T: Vector3Coordinate + num_traits::Float> Vector3<T> {
63 #[must_use]
82 #[inline]
83 pub fn fuzzy_equal(&self, target: &Self, epsilon: T) -> bool {
84 assert!(epsilon.is_sign_positive());
85 (self.x - target.x).abs() <= epsilon
87 && (self.y - target.y).abs() <= epsilon
88 && (self.z - target.z).abs() <= epsilon
89 }
90
91 #[must_use]
93 #[inline]
94 pub fn lerp(&self, target: &Self, alpha: T) -> Self {
95 Self {
96 x: self.x.lerp(target.x, alpha),
97 y: self.y.lerp(target.y, alpha),
98 z: self.z.lerp(target.z, alpha),
99 }
100 }
101
102 #[must_use]
104 #[inline]
105 pub fn magnitude(&self) -> T {
106 (self.x * self.x + self.y * self.y + self.z * self.z).sqrt()
107 }
108
109 #[must_use]
111 #[inline]
112 pub fn angle(&self, target: Self) -> T {
113 let dot_product = self.dot(target);
114 let magnitude_product = self.magnitude() * target.magnitude();
115 (dot_product / magnitude_product).acos()
116 }
117
118 #[must_use]
120 #[inline]
121 pub fn angle_deg(&self, target: Self) -> T
122 where
123 T: From<f64>,
124 {
125 const COEFF: f64 = 180.0 / std::f64::consts::PI;
126 self.angle(target) * From::from(COEFF)
127 }
128
129 #[inline]
131 pub fn normalize(&mut self) {
132 *self /= self.magnitude();
133 }
134
135 #[must_use]
137 #[inline]
138 pub fn distance(&self, target: Self) -> T {
139 (*self - target).magnitude()
140 }
141
142 #[must_use]
144 #[inline]
145 pub fn project(&self, on_normal: Self) -> Self {
146 on_normal * (self.dot(on_normal) / on_normal.dot(on_normal))
147 }
148
149 #[must_use]
151 #[inline]
152 pub fn reflect(&self, normal: Self) -> Self {
153 let two = T::one() + T::one();
154 *self - (normal * (self.dot(normal) * two))
155 }
156
157 #[must_use]
159 #[inline]
160 pub fn inverse(&self) -> Self {
161 let one = T::one();
162 Self {
163 x: one / self.x,
164 y: one / self.y,
165 z: one / self.z,
166 }
167 }
168
169 #[must_use]
171 #[inline]
172 pub fn abs(&self) -> Self {
173 Self {
174 x: self.x.abs(),
175 y: self.y.abs(),
176 z: self.z.abs(),
177 }
178 }
179
180 #[must_use]
182 #[inline]
183 pub fn ceil(&self) -> Self {
184 Self {
185 x: self.x.ceil(),
186 y: self.y.ceil(),
187 z: self.z.ceil(),
188 }
189 }
190
191 #[must_use]
193 #[inline]
194 pub fn floor(&self) -> Self {
195 Self {
196 x: self.x.floor(),
197 y: self.y.floor(),
198 z: self.z.floor(),
199 }
200 }
201
202 #[must_use]
204 #[inline]
205 pub fn round(&self) -> Self {
206 Self {
207 x: self.x.round(),
208 y: self.y.round(),
209 z: self.z.round(),
210 }
211 }
212
213 #[must_use]
215 #[inline]
216 pub fn clamp(&self, min: T, max: T) -> Self {
217 Self {
218 x: clamp(self.x, min, max),
219 y: clamp(self.y, min, max),
220 z: clamp(self.z, min, max),
221 }
222 }
223
224 #[must_use]
226 #[inline]
227 pub fn rotated(&self, axis: Self, angle: T) -> Self {
228 let (sin, cos) = angle.sin_cos();
229 let axis_normalized = axis / axis.magnitude();
230
231 let term1 = *self * cos;
232 let term2 = axis_normalized.cross(*self) * sin;
233 let term3_scalar = axis_normalized.dot(*self) * (T::one() - cos);
234 let term3 = axis_normalized * term3_scalar;
235
236 term1 + term2 + term3
237 }
238
239 #[must_use]
247 #[inline]
248 pub fn from_spherical(radius: T, polar: T, azimuth: T) -> Self {
249 let (sin_polar, cos_polar) = polar.sin_cos();
250 let (sin_azimuth, cos_azimuth) = azimuth.sin_cos();
251 Self {
252 x: radius * sin_polar * cos_azimuth,
253 y: radius * sin_polar * sin_azimuth,
254 z: radius * cos_polar,
255 }
256 }
257
258 #[cfg(feature = "random")]
260 #[must_use]
261 #[inline]
262 pub fn random() -> Self
263 where
264 rand::distr::StandardUniform: rand::prelude::Distribution<T>,
265 {
266 RNG.with_borrow_mut(|thread| Self {
267 x: thread.random(),
268 y: thread.random(),
269 z: thread.random(),
270 })
271 }
272
273 #[cfg(feature = "random")]
275 #[must_use]
276 #[inline]
277 pub fn random_range(
278 range_x: impl SampleRange<T>,
279 range_y: impl SampleRange<T>,
280 range_z: impl SampleRange<T>,
281 ) -> Self
282 where
283 rand::distr::StandardUniform: rand::prelude::Distribution<T>,
284 T: SampleUniform,
285 {
286 RNG.with_borrow_mut(|thread| Self {
287 x: thread.random_range(range_x),
288 y: thread.random_range(range_y),
289 z: thread.random_range(range_z),
290 })
291 }
292}
293
294impl<T: Vector3Coordinate> Vector3<T> {
295 pub const fn new(x: T, y: T, z: T) -> Self {
305 Self { x, y, z }
306 }
307
308 #[must_use]
310 #[inline]
311 pub fn dot(&self, target: Self) -> T {
312 let (x, y, z) = target.into();
313 self.x.clone() * x + self.y.clone() * y + self.z.clone() * z
314 }
315
316 #[must_use]
318 #[inline]
319 pub fn cross(&self, target: Self) -> Self {
320 let (x, y, z) = target.into();
321 Self {
322 x: self.y.clone() * z.clone() - self.z.clone() * y.clone(),
323 y: self.z.clone() * x.clone() - self.x.clone() * z,
324 z: self.x.clone() * y - self.y.clone() * x,
325 }
326 }
327
328 #[must_use]
330 #[inline]
331 pub fn max(&self, target: &Self) -> Self {
332 let x = if self.x > target.x {
333 self.x.clone()
334 } else {
335 target.x.clone()
336 };
337 let y = if self.y > target.y {
338 self.y.clone()
339 } else {
340 target.y.clone()
341 };
342 let z = if self.z > target.z {
343 self.z.clone()
344 } else {
345 target.z.clone()
346 };
347 Self { x, y, z }
348 }
349
350 #[must_use]
352 #[inline]
353 pub fn min(&self, target: &Self) -> Self {
354 let x = if self.x < target.x {
355 self.x.clone()
356 } else {
357 target.x.clone()
358 };
359 let y = if self.y < target.y {
360 self.y.clone()
361 } else {
362 target.y.clone()
363 };
364 let z = if self.z < target.z {
365 self.z.clone()
366 } else {
367 target.z.clone()
368 };
369 Self { x, y, z }
370 }
371
372 pub const fn x(&self) -> &T {
374 &self.x
375 }
376
377 pub const fn y(&self) -> &T {
379 &self.y
380 }
381
382 pub const fn z(&self) -> &T {
384 &self.z
385 }
386}
387
388impl<T: Vector3Coordinate> std::fmt::Display for Vector3<T> {
389 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
390 write!(f, "Vector3({}, {}, {})", self.x, self.y, self.z)
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use super::*;
397 use std::ops::Sub;
398
399 #[test]
400 fn angle() {
401 let angle = std::f64::consts::PI / 2.0;
402 let calc_angle = Vector3::<f64>::x_axis().angle(Vector3::<f64>::y_axis());
403 assert!(calc_angle.sub(angle) <= f64::EPSILON);
404 }
405
406 #[test]
407 fn create() {
408 let my_vec: Vector3<f64> = Vector3::new(1.3, 0.0, -5.35501);
409 assert!((my_vec.x() - 1.3f64).abs() <= f64::EPSILON);
410 assert!((my_vec.y() - 0.0f64).abs() <= f64::EPSILON);
411 assert!((my_vec.z() - -5.35501f64).abs() <= f64::EPSILON);
412 }
413
414 #[test]
415 fn sum() {
416 let vec1 = Vector3::new(1.0, 2.0, 3.0);
417 let vec2 = Vector3::new(5.0, 0.0, -1.0);
418 assert_eq!(vec1 + vec2, Vector3::new(6.0, 2.0, 2.0));
419 }
420
421 #[test]
422 fn normalization() {
423 let mut test_vec: Vector3<f64> = Vector3::new(1.0, 2.3, 100.123);
424 test_vec.normalize();
425 assert_eq!(
426 test_vec,
427 Vector3::new(
428 0.009_984_583_160_766_44,
429 0.022_964_541_269_762_81,
430 0.999_686_419_805_418_3
431 )
432 );
433 assert!((1.0 - test_vec.magnitude()).abs() <= f64::EPSILON);
434 }
435
436 #[test]
437 fn lerp() {
438 let start = Vector3::new(0.0, 0.0, 0.0);
439 let end = Vector3::new(1.0, 2.0, 3.0);
440 let lerp_result = start.lerp(&end, 0.75);
441 assert_eq!(lerp_result, Vector3::new(0.75, 1.5, 2.25));
442 }
443
444 #[test]
445 fn dot_product() {
446 let vec1 = Vector3::new(1.0, 2.0, 3.0);
447 let vec2 = Vector3::new(5.0, 0.0, -1.0);
448 let dot_result = vec1.dot(vec2);
449 assert!((dot_result - 2.0f64).abs() <= f64::EPSILON);
450 }
451
452 #[test]
453 fn cross_product() {
454 let vec1 = Vector3::new(1.0, 0.0, 0.0);
455 let vec2 = Vector3::new(0.0, 1.0, 0.0);
456 let cross_result = vec1.cross(vec2);
457 assert_eq!(cross_result, Vector3::new(0.0, 0.0, 1.0));
458 }
459
460 #[test]
461 fn max_components() {
462 let vec1 = Vector3::new(1.0, 5.0, 3.0);
463 let vec2 = Vector3::new(3.0, 2.0, 4.0);
464 let max_result = vec1.max(&vec2);
465 assert_eq!(max_result, Vector3::new(3.0, 5.0, 4.0));
466 }
467
468 #[test]
469 fn min_components() {
470 let vec1 = Vector3::new(1.0, 5.0, 3.0);
471 let vec2 = Vector3::new(3.0, 2.0, 4.0);
472 let min_result = vec1.min(&vec2);
473 assert_eq!(min_result, Vector3::new(1.0, 2.0, 3.0));
474 }
475
476 #[test]
477 fn fuzzy_equality() {
478 let vec1 = Vector3::new(1.0, 2.0, 3.0);
479 let vec2 = Vector3::new(1.01, 1.99, 3.01);
480 let epsilon = 0.02;
481 let fuzzy_equal_result = vec1.fuzzy_equal(&vec2, epsilon);
482 assert!(fuzzy_equal_result);
483 }
484
485 #[test]
486 fn distance() {
487 let v1 = Vector3::new(1.0, 2.0, 3.0);
488 let v2 = Vector3::new(4.0, 6.0, 8.0);
489 assert!((v1.distance(v2) - (50.0f64).sqrt()).abs() <= f64::EPSILON);
490 }
491
492 #[test]
493 fn project() {
494 let v = Vector3::new(1.0, 2.0, 3.0);
495 let on_normal = Vector3::new(1.0, 0.0, 0.0);
496 let expected = Vector3::new(1.0, 0.0, 0.0);
497 assert_eq!(v.project(on_normal), expected);
498 }
499
500 #[test]
501 fn reflect() {
502 let v = Vector3::new(1.0, -1.0, 0.0);
503 let normal = Vector3::new(0.0, 1.0, 0.0);
504 let expected = Vector3::new(1.0, 1.0, 0.0);
505 assert_eq!(v.reflect(normal), expected);
506 }
507
508 #[test]
509 fn inverse() {
510 let v = Vector3::new(2.0, 4.0, 8.0);
511 let expected = Vector3::new(0.5, 0.25, 0.125);
512 assert_eq!(v.inverse(), expected);
513 }
514
515 #[test]
516 fn abs() {
517 let v = Vector3::new(-1.0, -2.0, 3.0);
518 let expected = Vector3::new(1.0, 2.0, 3.0);
519 assert_eq!(v.abs(), expected);
520 }
521
522 #[test]
523 fn ceil() {
524 let v = Vector3::new(1.1, 2.9, 3.0);
525 let expected = Vector3::new(2.0, 3.0, 3.0);
526 assert_eq!(v.ceil(), expected);
527 }
528
529 #[test]
530 fn floor() {
531 let v = Vector3::new(1.1, 2.9, 3.0);
532 let expected = Vector3::new(1.0, 2.0, 3.0);
533 assert_eq!(v.floor(), expected);
534 }
535
536 #[test]
537 fn round() {
538 let v = Vector3::new(1.1, 2.9, 3.5);
539 let expected = Vector3::new(1.0, 3.0, 4.0);
540 assert_eq!(v.round(), expected);
541 }
542
543 #[test]
544 fn clamp() {
545 let v = Vector3::new(0.0, 5.0, 10.0);
546 let min = 1.0;
547 let max = 9.0;
548 let expected = Vector3::new(1.0, 5.0, 9.0);
549 assert_eq!(v.clamp(min, max), expected);
550 }
551
552 #[test]
553 fn rotated() {
554 let v = Vector3::new(1.0, 0.0, 0.0);
555 let axis = Vector3::new(0.0, 0.0, 1.0);
556 let angle = std::f64::consts::FRAC_PI_2;
557 let rotated = v.rotated(axis, angle);
558 let expected = Vector3::new(0.0, 1.0, 0.0);
559 assert!(rotated.fuzzy_equal(&expected, 1e-15));
560 }
561
562 #[test]
563 fn from_spherical() {
564 let radius = 1.0;
565 let polar = std::f64::consts::FRAC_PI_2;
566 let azimuth = 0.0;
567 let v = Vector3::from_spherical(radius, polar, azimuth);
568 let expected = Vector3::new(1.0, 0.0, 0.0);
569 assert!(v.fuzzy_equal(&expected, 1e-15));
570 }
571
572 #[test]
573 fn nan_dont_panic() {
574 let mut vec1: Vector3<f64> = Vector3::default();
575 vec1 /= f64::NAN;
576 }
577
578 #[test]
579 fn readme_example() {
580 let mut v1: Vector3<f64> = Vector3::new(1.0, 2.0, 3.0);
581 let mut v2: Vector3<f64> = Vector3::new(3.0, 1.0, 2.0);
582
583 let sum = v1 + v2;
585 let difference = v1 - v2;
586 let dot_product = v1.dot(v2);
587 let cross_product = v1.cross(v2);
588
589 let lerp_result = v1.lerp(&v2, 0.5);
591 let angle = v1.angle(v2);
592 let fuzzy_equal = v1.fuzzy_equal(&v2, 0.001);
593
594 println!("Sum: {sum}");
595 println!("Difference: {difference}");
596 println!("Dot product: {dot_product}");
597 println!("Cross product: {cross_product}");
598 println!("Lerp 50%: {lerp_result}");
599 println!("Angle: {angle}");
600 print!("Are they close enough?: {fuzzy_equal}");
601
602 v1.normalize();
603 v2.normalize();
604
605 println!("v1 normalized: {v1}");
606 println!("v2 normalized: {v2}");
607 }
608 #[test]
609 fn conversion_box() {
610 let correct = Vector3::new(1, 2, 3);
611 let x = String::from("Vector3(1,2,3)").into_boxed_str();
612 assert_eq!(x.parse::<Vector3<i32>>().unwrap(), correct);
613 }
614}