vec.rs
4.3 kB · rust · 154 lines
1use mrlycore::trig;23fn sini(i: i64) -> f32 {4 trig::sin_idx(i.rem_euclid(trig::N as i64) as usize)5}67fn cosi(i: i64) -> f32 {8 trig::cos_idx(i.rem_euclid(trig::N as i64) as usize)9}1011/// A three-component vector of f32.12#[derive(Clone, Copy, Debug, PartialEq)]13pub struct Vec3 {14 /// The x component.15 pub x: f32,16 /// The y component.17 pub y: f32,18 /// The z component.19 pub z: f32,20}2122impl std::ops::Add for Vec3 {23 type Output = Vec3;24 fn add(self, o: Vec3) -> Vec3 {25 Vec3::new(self.x + o.x, self.y + o.y, self.z + o.z)26 }27}2829impl std::ops::Sub for Vec3 {30 type Output = Vec3;31 fn sub(self, o: Vec3) -> Vec3 {32 Vec3::new(self.x - o.x, self.y - o.y, self.z - o.z)33 }34}3536impl Vec3 {37 /// Builds a vector from its components.38 pub fn new(x: f32, y: f32, z: f32) -> Vec3 {39 Vec3 { x, y, z }40 }41 /// Multiplies every component by the scalar.42 pub fn scale(self, s: f32) -> Vec3 {43 Vec3::new(self.x * s, self.y * s, self.z * s)44 }45 /// Returns the dot product of the two vectors.46 pub fn dot(self, o: Vec3) -> f32 {47 self.x * o.x + self.y * o.y + self.z * o.z48 }49 /// Returns the cross product, perpendicular to both vectors.50 ///51 /// ```52 /// use mrlymath::space::Vec3;53 /// let n = Vec3::new(1.0, 0.0, 0.0).cross(Vec3::new(0.0, 1.0, 0.0));54 /// assert_eq!(n, Vec3::new(0.0, 0.0, 1.0));55 /// ```56 pub fn cross(self, o: Vec3) -> Vec3 {57 Vec3::new(58 self.y * o.z - self.z * o.y,59 self.z * o.x - self.x * o.z,60 self.x * o.y - self.y * o.x,61 )62 }63}6465/// A three-by-three rotation matrix of f32.66#[derive(Clone, Copy, Debug, PartialEq)]67pub struct Mat3 {68 /// The rows of the matrix.69 pub m: [[f32; 3]; 3],70}7172impl std::ops::Mul for Mat3 {73 type Output = Mat3;74 fn mul(self, o: Mat3) -> Mat3 {75 let mut m = [[0.0f32; 3]; 3];76 for (r, row) in m.iter_mut().enumerate() {77 for (c, cell) in row.iter_mut().enumerate() {78 *cell =79 self.m[r][0] * o.m[0][c] + self.m[r][1] * o.m[1][c] + self.m[r][2] * o.m[2][c];80 }81 }82 Mat3 { m }83 }84}8586impl Mat3 {87 /// Builds the identity matrix.88 pub fn identity() -> Mat3 {89 Mat3 {90 m: [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]],91 }92 }93 /// Builds the rotation about the y axis by i angle steps, wrapping in either direction.94 pub fn yaw(i: i64) -> Mat3 {95 let (c, s) = (cosi(i), sini(i));96 Mat3 {97 m: [[c, 0.0, s], [0.0, 1.0, 0.0], [-s, 0.0, c]],98 }99 }100 /// Builds the rotation about the x axis by i angle steps, wrapping in either direction.101 pub fn pitch(i: i64) -> Mat3 {102 let (c, s) = (cosi(i), sini(i));103 Mat3 {104 m: [[1.0, 0.0, 0.0], [0.0, c, -s], [0.0, s, c]],105 }106 }107 /// Transforms the vector by the matrix.108 pub fn apply(self, v: Vec3) -> Vec3 {109 Vec3::new(110 self.m[0][0] * v.x + self.m[0][1] * v.y + self.m[0][2] * v.z,111 self.m[1][0] * v.x + self.m[1][1] * v.y + self.m[1][2] * v.z,112 self.m[2][0] * v.x + self.m[2][1] * v.y + self.m[2][2] * v.z,113 )114 }115}116117#[cfg(test)]118mod tests {119 use super::*;120121 #[test]122 fn quarter_turns_are_exact() {123 let q = (trig::N / 4) as i64;124 let v = Mat3::yaw(q).apply(Vec3::new(1.0, 0.0, 0.0));125 assert!(v.x.abs() < 1e-6 && (v.z + 1.0).abs() < 1e-6);126 let v = Mat3::pitch(q).apply(Vec3::new(0.0, 1.0, 0.0));127 assert!(v.y.abs() < 1e-6 && (v.z - 1.0).abs() < 1e-6);128 }129 #[test]130 fn rotation_preserves_length() {131 let v = Vec3::new(0.3, -0.7, 0.64);132 for i in [0i64, 17, 100, -40, 255] {133 let r = (Mat3::yaw(i) * Mat3::pitch(i * 3)).apply(v);134 assert!((r.dot(r) - v.dot(v)).abs() < 1e-5);135 }136 }137 #[test]138 fn negative_indices_wrap() {139 assert_eq!(Mat3::yaw(-3).m, Mat3::yaw(253).m);140 }141 #[test]142 fn cross_is_perpendicular() {143 let a = Vec3::new(1.0, 2.0, 3.0);144 let b = Vec3::new(-2.0, 0.5, 1.0);145 let c = a.cross(b);146 assert!(c.dot(a).abs() < 1e-6);147 assert!(c.dot(b).abs() < 1e-6);148 }149 #[test]150 fn identity_leaves_vectors() {151 let v = Vec3::new(0.1, 0.2, 0.3);152 assert_eq!(Mat3::identity().apply(v), v);153 }154}