use super::IsEqualApprox;
use glam::Vec2;
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[repr(C)]
pub struct Vector2 {
pub x: f32,
pub y: f32,
}
impl Vector2 {
pub const ZERO: Vector2 = Vector2::new(0.0, 0.0);
pub const ONE: Vector2 = Vector2::new(1.0, 1.0);
pub const INF: Vector2 = Vector2::new(f32::INFINITY, f32::INFINITY);
pub const LEFT: Vector2 = Vector2::new(-1.0, 0.0);
pub const RIGHT: Vector2 = Vector2::new(1.0, 0.0);
pub const UP: Vector2 = Vector2::new(0.0, -1.0);
pub const DOWN: Vector2 = Vector2::new(0.0, 1.0);
#[inline]
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
#[inline]
pub fn abs(self) -> Self {
Self::gd(self.glam().abs())
}
#[inline]
pub fn angle(self) -> f32 {
self.glam().angle_between(Vec2::X)
}
#[inline]
pub fn angle_to(self, to: Self) -> f32 {
self.glam().angle_between(to.glam())
}
#[inline]
pub fn angle_to_point(self, to: Self) -> f32 {
self.glam().angle_between(to.glam() - self.glam())
}
#[inline]
pub fn aspect(self) -> f32 {
self.x / self.y
}
#[inline]
pub fn bounce(self, n: Self) -> Self {
-self.reflect(n)
}
#[inline]
pub fn ceil(self) -> Self {
Self::gd(self.glam().ceil())
}
#[inline]
pub fn clamped(self, length: f32) -> Self {
Self::gd(self.glam().clamp_length_max(length))
}
#[inline]
pub fn cross(self, with: Self) -> f32 {
self.x * with.y - self.y * with.x
}
#[inline]
pub fn cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, t: f32) -> Self {
let v0 = pre_a;
let v1 = self;
let v2 = b;
let v3 = post_b;
let t2 = t * t;
let t3 = t2 * t;
((v1 * 2.0)
+ (-v0 + v2) * t
+ (v0 * 2.0 - v1 * 5.0 + v2 * 4.0 - v3) * t2
+ (-v0 + v1 * 3.0 - v2 * 3.0 + v3) * t3)
* 0.5
}
#[inline]
pub fn direction_to(self, other: Self) -> Self {
(other - self).normalized()
}
#[inline]
pub fn distance_squared_to(self, other: Self) -> f32 {
self.glam().distance_squared(other.glam())
}
#[inline]
pub fn distance_to(self, other: Self) -> f32 {
self.glam().distance(other.glam())
}
#[inline]
pub fn dot(self, with: Self) -> f32 {
self.glam().dot(with.glam())
}
#[inline]
pub fn floor(self) -> Self {
Self::gd(self.glam().floor())
}
#[inline]
pub fn is_equal_approx(self, v: Self) -> bool {
self.x.is_equal_approx(v.x) && self.y.is_equal_approx(v.y)
}
#[inline]
pub fn is_normalized(self) -> bool {
self.glam().is_normalized()
}
#[inline]
pub fn length(self) -> f32 {
self.glam().length()
}
#[inline]
pub fn length_squared(self) -> f32 {
self.glam().length_squared()
}
#[inline]
pub fn linear_interpolate(self, b: Self, t: f32) -> Self {
Self::gd(self.glam().lerp(b.glam(), t))
}
#[inline]
pub fn move_toward(self, to: Vector2, delta: f32) -> Self {
let vd = to - self;
let len = vd.length();
if len <= delta || approx::abs_diff_eq!(0.0, len) {
to
} else {
Self::linear_interpolate(self, to, delta / len)
}
}
#[inline]
pub fn normalized(self) -> Self {
Self::gd(self.glam().normalize())
}
#[inline]
pub fn posmod(self, rem: f32) -> Self {
self.posmodv(Self::new(rem, rem))
}
#[inline]
pub fn posmodv(self, remv: Self) -> Self {
Self::new(self.x.rem_euclid(remv.x), self.y.rem_euclid(remv.y))
}
#[inline]
pub fn project(self, b: Self) -> Self {
b * (self.dot(b) / b.length_squared())
}
#[inline]
pub fn reflect(self, n: Self) -> Self {
n * self.dot(n) * 2.0 - self
}
#[inline]
pub fn rotated(self, angle: f32) -> Self {
let (cos, sin) = (angle.cos(), angle.sin());
Self::new(cos * self.x - sin * self.y, sin * self.x + cos * self.y)
}
#[inline]
pub fn round(self) -> Self {
Self::gd(self.glam().round())
}
#[inline]
pub fn sign(self) -> Self {
Self::gd(self.glam().signum())
}
#[inline]
pub fn slerp(self, b: Self, t: f32) -> Self {
let theta = self.angle_to(b);
self.rotated(theta * t)
}
#[inline]
pub fn slide(self, normal: Self) -> Self {
self - normal * self.dot(normal)
}
#[inline]
pub fn snapped(self, by: Self) -> Self {
Vector2::new(
if by.x != 0.0 {
(self.x / by.x + 0.5).floor() * by.x
} else {
self.x
},
if by.y != 0.0 {
(self.y / by.y + 0.5).floor() * by.y
} else {
self.y
},
)
}
#[inline]
pub fn tangent(self) -> Self {
Vector2::new(self.y, -self.x)
}
#[doc(hidden)]
#[allow(clippy::wrong_self_convention)]
#[inline]
pub fn to_sys(self) -> sys::godot_vector2 {
unsafe { std::mem::transmute(self) }
}
#[doc(hidden)]
#[inline]
pub fn sys(&self) -> *const sys::godot_vector2 {
self as *const _ as *const _
}
#[doc(hidden)]
#[inline]
pub fn from_sys(v: sys::godot_vector2) -> Self {
unsafe { std::mem::transmute(v) }
}
fn glam(self) -> Vec2 {
Vec2::new(self.x, self.y)
}
fn gd(from: Vec2) -> Self {
Self::new(from.x, from.y)
}
}
macro_rules! derive_op_impl {
($trait:ident, $func:ident) => {
impl $trait for Vector2 {
type Output = Self;
#[inline]
fn $func(self, with: Self) -> Self {
Self::gd(self.glam().$func(with.glam()))
}
}
};
($trait:ident, $func:ident, $in_type:ty) => {
impl $trait<$in_type> for Vector2 {
type Output = Self;
#[inline]
fn $func(self, with: $in_type) -> Self {
Self::gd(self.glam().$func(with))
}
}
};
}
macro_rules! derive_assign_op_impl {
($trait:ident, $func:ident, $op_func:ident) => {
impl $trait for Vector2 {
#[inline]
fn $func(&mut self, with: Self) {
*self = self.$op_func(with);
}
}
};
($trait:ident, $func:ident, $op_func:ident, $in_type:ty) => {
impl $trait<$in_type> for Vector2 {
#[inline]
fn $func(&mut self, with: $in_type) {
*self = self.$op_func(with);
}
}
};
}
derive_op_impl!(Add, add);
derive_op_impl!(Sub, sub);
derive_op_impl!(Mul, mul);
derive_op_impl!(Div, div);
derive_op_impl!(Mul, mul, f32);
derive_op_impl!(Div, div, f32);
derive_assign_op_impl!(AddAssign, add_assign, add);
derive_assign_op_impl!(SubAssign, sub_assign, sub);
derive_assign_op_impl!(MulAssign, mul_assign, mul);
derive_assign_op_impl!(DivAssign, div_assign, div);
derive_assign_op_impl!(MulAssign, mul_assign, mul, f32);
derive_assign_op_impl!(DivAssign, div_assign, div, f32);
impl Neg for Vector2 {
type Output = Self;
#[inline]
fn neg(self) -> Self {
Self::gd(-self.glam())
}
}
godot_test!(
test_vector2_variants {
use crate::core_types::ToVariant;
fn test(vector: Vector2, set_to: Vector2) {
use crate::core_types::FromVariant;
let api = crate::private::get_api();
let copied = vector;
unsafe {
assert_relative_eq!(
vector.x,
(api.godot_vector2_get_x)(&copied as *const _ as *const sys::godot_vector2),
);
assert_relative_eq!(
vector.y,
(api.godot_vector2_get_y)(&copied as *const _ as *const sys::godot_vector2),
);
}
assert_eq!(vector, copied);
let mut copied = vector;
unsafe {
(api.godot_vector2_set_x)(&mut copied as *mut _ as *mut sys::godot_vector2, set_to.x);
(api.godot_vector2_set_y)(&mut copied as *mut _ as *mut sys::godot_vector2, set_to.y);
}
assert_eq!(set_to, copied);
let variant = vector.to_variant();
let vector_from_variant = Vector2::from_variant(&variant).unwrap();
assert_eq!(vector, vector_from_variant);
}
test(Vector2::new(1.0, 2.0), Vector2::new(3.0, 4.0));
test(Vector2::new(3.0, 4.0), Vector2::new(5.0, 6.0));
}
);
#[cfg(test)]
mod tests {
use crate::core_types::Vector2;
#[test]
fn it_is_copy() {
fn copy<T: Copy>() {}
copy::<Vector2>();
}
#[test]
fn it_has_the_same_size() {
use std::mem::size_of;
assert_eq!(size_of::<sys::godot_vector2>(), size_of::<Vector2>());
}
#[test]
fn it_supports_equality() {
assert_eq!(Vector2::new(1.0, 2.0), Vector2::new(1.0, 2.0));
}
#[test]
fn it_supports_inequality() {
assert_ne!(Vector2::new(1.0, 10.0), Vector2::new(1.0, 2.0));
}
#[test]
fn cubic_interpolate_is_sane() {
use Vector2 as V;
assert!(
V::new(4.7328, -6.7936).is_equal_approx(V::new(5.4, -6.8).cubic_interpolate(
V::new(-1.2, 0.8),
V::new(1.2, 10.3),
V::new(-5.4, 4.2),
0.2,
))
);
assert!(
V::new(-3.8376, 2.9384).is_equal_approx(V::new(-4.2, 1.4).cubic_interpolate(
V::new(-3.7, 2.1),
V::new(5.4, -8.5),
V::new(-10.8, -6.6),
0.6,
))
);
}
#[test]
fn slide_is_sane() {
use Vector2 as V;
let cases = &[
(V::new(1.0, 1.0), V::new(0.0, 1.0), V::new(1.0, 0.0)),
(
V::new(3.0, 4.0),
V::new(-3.0, 1.0).normalized(),
V::new(1.5, 4.5),
),
(
V::new(-2.0, 1.0),
V::new(-1.0, 3.0).normalized(),
V::new(-1.5, -0.5),
),
];
for &(v, normal, expected) in cases {
assert!(expected.is_equal_approx(v.slide(normal)));
}
}
#[test]
fn snapped_is_sane() {
use Vector2 as V;
let cases = &[
(V::new(1.5, 5.6), V::new(1.0, 4.0), V::new(2.0, 4.0)),
(V::new(5.4, 4.2), V::new(-2.0, -3.5), V::new(6.0, 3.5)),
(V::new(5.4, -6.8), V::new(0.0, 0.3), V::new(5.4, -6.9)),
];
for &(v, by, expected) in cases {
assert!(expected.is_equal_approx(v.snapped(by)));
}
}
}