// -*- coding: utf-8 -*- // // Copyright 2021 Michael Büsch // // Derived from https://github.com/mlochen/dungeon.git // Copyright (C) 2020 Marco Lochen // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // use std::ops::{Add, Mul, Sub}; #[derive(Copy, Clone, Debug)] pub struct Vec2D { x: f32, y: f32, } impl Vec2D { /// Create a new vector. #[inline] pub fn new(x: f32, y: f32) -> Vec2D { Vec2D { x, y } } /// Get the X coordinate. #[inline] pub fn x(&self) -> f32 { self.x } /// Set the X coordinate. #[inline] pub fn set_x(&mut self, x: f32) { self.x = x; } /// Get the Y coordinate. #[inline] pub fn y(&self) -> f32 { self.y } /// Set the Y coordinate. #[inline] pub fn set_y(&mut self, y: f32) { self.y = y; } /// Get the coordinates. #[inline] pub fn get(&self) -> (f32, f32) { (self.x, self.y) } /// Set the coordinates. #[inline] pub fn set(&mut self, coord: (f32, f32)) { self.x = coord.0; self.y = coord.1; } /// Get the length of self. #[inline] pub fn len(&self) -> f32 { ((self.x * self.x) + (self.y * self.y)).sqrt() } /// Get a normalized version of self. #[inline] pub fn normalized(&self) -> Vec2D { let len = self.len(); if len != 0.0 { Vec2D::new(self.x / len, self.y / len) } else { Default::default() } } /// Get the dot product of self and other. #[inline] pub fn dot(&self, other: &Vec2D) -> f32 { (self.x * other.x) + (self.y * other.y) } /// Get the angle of self w.r.t. the coordinate system abscissa. #[inline] pub fn phi(&self) -> f32 { if self.x != 0.0 { if self.x > 0.0 { (self.y / self.x).atan() } else { (self.y / self.x).atan() + std::f32::consts::PI } } else { std::f32::consts::FRAC_PI_2 } } /// Get angle between self and other. #[inline] pub fn phi_to(&self, other: &Vec2D) -> f32 { (self.dot(other) / (self.len() * other.len())).acos() } /// Check if the angle between self and other is positive. #[inline] pub fn phi_to_is_positive(&self, other: &Vec2D) -> bool { ((self.x * other.y) - (self.y * other.x)) >= 0.0 } /// Check if the angle between self and other is negative. #[inline] pub fn phi_to_is_negative(&self, other: &Vec2D) -> bool { !self.phi_to_is_positive(other) } /// Get self projected onto other. #[inline] pub fn projected(&self, other: &Vec2D) -> Vec2D { let dotother = other.dot(other); if dotother != 0.0 { *other * (self.dot(other) / dotother) } else { Default::default() } } /// Get self rotated by angle phi. #[inline] pub fn rotated(&self, phi: f32) -> Vec2D { let (sinphi, cosphi) = phi.sin_cos(); Vec2D::new( (cosphi * self.x) - (sinphi * self.y), (sinphi * self.x) + (cosphi * self.y), ) } } impl Default for Vec2D { /// Get a zero vector. #[inline] fn default() -> Self { Self::new(0.0, 0.0) } } impl Mul for Vec2D { type Output = Vec2D; /// Multiply the vector self with a scalar. #[inline] fn mul(self, rhs: f32) -> Self::Output { Vec2D { x: self.x * rhs, y: self.y * rhs, } } } impl Add for Vec2D { type Output = Vec2D; /// Add two vectors. #[inline] fn add(self, rhs: Self) -> Self::Output { Vec2D { x: self.x + rhs.x, y: self.y + rhs.y, } } } impl Sub for Vec2D { type Output = Vec2D; /// Subtract rhs from self. #[inline] fn sub(self, rhs: Self) -> Self::Output { Vec2D { x: self.x - rhs.x, y: self.y - rhs.y, } } } #[cfg(test)] mod tests { use super::*; use crate::util::radians; use float_eq::assert_float_eq; #[test] fn test_add() { let a = Vec2D::new(1.0, 2.0) + Vec2D::new(10.0, -20.0); assert_float_eq!(a.x(), 11.0, abs <= 1e-6); assert_float_eq!(a.y(), -18.0, abs <= 1e-6); } #[test] fn test_sub() { let a = Vec2D::new(1.0, 2.0) - Vec2D::new(10.0, -20.0); assert_float_eq!(a.x(), -9.0, abs <= 1e-6); assert_float_eq!(a.y(), 22.0, abs <= 1e-6); } #[test] fn test_mul() { let a = Vec2D::new(1.0, -2.0) * 10.0; assert_float_eq!(a.x(), 10.0, abs <= 1e-6); assert_float_eq!(a.y(), -20.0, abs <= 1e-6); } #[test] fn test_len() { let a = Vec2D::new(1.5, 2.2).len(); assert_float_eq!(a, 2.662705, abs <= 1e-6); } #[test] fn test_normalized() { let a = Vec2D::new(1.5, 2.2).normalized(); assert_float_eq!(a.x(), 0.5633368, abs <= 1e-6); assert_float_eq!(a.y(), 0.8262273, abs <= 1e-6); } #[test] fn test_dot() { let a = Vec2D::new(1.5, 2.2).dot(&Vec2D::new(10.0, 11.0)); assert_float_eq!(a, 39.2, abs <= 1e-6); } #[test] fn test_phi() { let a = Vec2D::new(1.5, 2.2).phi(); assert_float_eq!(a, 0.9723774, abs <= 1e-6); let a = Vec2D::new(0.0, 1.23).phi(); assert_float_eq!(a, std::f32::consts::FRAC_PI_2, abs <= 1e-6); let a = Vec2D::new(-3.3, 3.45).phi(); assert_float_eq!(a, 2.333976, abs <= 1e-6); let a = Vec2D::new(4.5, -2.1).phi(); assert_float_eq!(a, -0.4366271, abs <= 1e-6); let a = Vec2D::new(-8.4, -9.1).phi(); assert_float_eq!(a, 3.96697, abs <= 1e-6); } #[test] fn test_phi_to() { let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(1.0, 0.0)); assert_float_eq!(a, 0.0, abs <= 1e-6); let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(1.0, 1.0)); assert_float_eq!(a, std::f32::consts::FRAC_PI_4, abs <= 1e-6); let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(0.0, 1.0)); assert_float_eq!(a, std::f32::consts::FRAC_PI_2, abs <= 1e-6); let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(-1.0, 1.0)); assert_float_eq!(a, std::f32::consts::FRAC_PI_4 * 3.0, abs <= 1e-6); let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(-1.0, 0.0)); assert_float_eq!(a, std::f32::consts::PI, abs <= 1e-6); let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(-1.0, -1.0)); assert_float_eq!(a, std::f32::consts::FRAC_PI_4 * 3.0, abs <= 1e-6); let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(0.0, -1.0)); assert_float_eq!(a, std::f32::consts::FRAC_PI_2, abs <= 1e-6); let a = Vec2D::new(1.0, 0.0).phi_to(&Vec2D::new(1.0, -1.0)); assert_float_eq!(a, std::f32::consts::FRAC_PI_4, abs <= 1e-6); let a = Vec2D::new(1.5, 2.2).phi_to(&Vec2D::new(10.0, 11.0)); assert_float_eq!(a, 0.1393962, abs <= 1e-6); for i in 0..=360 { let a = Vec2D::new(1.0, 0.0); assert!(a.phi_to(&a.rotated(radians(i as f32))) >= 0.0); assert!(a.phi_to(&a.rotated(radians((-i) as f32))) >= 0.0); } } #[test] fn test_phi_to_is_positive() { let a = Vec2D::new(1.0, 0.0); assert!(a.phi_to_is_positive(&a) == true); assert!(a.phi_to_is_positive(&a.rotated(radians(45.0))) == true); assert!(a.phi_to_is_positive(&a.rotated(radians(90.0))) == true); assert!(a.phi_to_is_positive(&a.rotated(radians(135.0))) == true); assert!(a.phi_to_is_positive(&Vec2D::new(-1.0, 0.0)) == true); assert!(a.phi_to_is_positive(&a.rotated(radians(225.0))) == false); assert!(a.phi_to_is_positive(&a.rotated(radians(270.0))) == false); assert!(a.phi_to_is_positive(&a.rotated(radians(315.0))) == false); } #[test] fn test_phi_to_is_negative() { let a = Vec2D::new(1.0, 0.0); assert!(a.phi_to_is_negative(&a) == false); assert!(a.phi_to_is_negative(&a.rotated(radians(45.0))) == false); assert!(a.phi_to_is_negative(&a.rotated(radians(90.0))) == false); assert!(a.phi_to_is_negative(&a.rotated(radians(135.0))) == false); assert!(a.phi_to_is_negative(&Vec2D::new(-1.0, 0.0)) == false); assert!(a.phi_to_is_negative(&a.rotated(radians(225.0))) == true); assert!(a.phi_to_is_negative(&a.rotated(radians(270.0))) == true); assert!(a.phi_to_is_negative(&a.rotated(radians(315.0))) == true); } #[test] fn test_rotated() { let a = Vec2D::new(1.5, 2.2).rotated(radians(45.0)); assert_float_eq!(a.x(), -0.4949747, abs <= 1e-6); assert_float_eq!(a.y(), 2.616295, abs <= 1e-6); } #[test] fn test_proj() { let a = Vec2D::new(1.5, 2.2).projected(&Vec2D::new(10.0, 11.0)); assert_float_eq!(a.x(), 1.773756, abs <= 1e-6); assert_float_eq!(a.y(), 1.951131, abs <= 1e-6); } } // vim: ts=4 sw=4 expandtab