Skip to content

Refactor vector_macros to consistently generate all vector types #721

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions godot-core/src/builtin/aabb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,18 +189,18 @@ impl Aabb {

/// Returns the normalized longest axis of the AABB.
#[inline]
pub fn longest_axis(&self) -> Vector3 {
match self.longest_axis_index() {
pub fn longest_axis(&self) -> Option<Vector3> {
self.longest_axis_index().map(|axis| match axis {
Vector3Axis::X => Vector3::RIGHT,
Vector3Axis::Y => Vector3::UP,
Vector3Axis::Z => Vector3::BACK,
}
})
}

/// Returns the index of the longest axis of the AABB (according to Vector3's AXIS_* constants).
#[inline]
pub fn longest_axis_index(&self) -> Vector3Axis {
self.size.max_axis_index()
pub fn longest_axis_index(&self) -> Option<Vector3Axis> {
self.size.max_axis()
}

/// Returns the scalar length of the longest axis of the AABB.
Expand All @@ -211,18 +211,18 @@ impl Aabb {

/// Returns the normalized shortest axis of the AABB.
#[inline]
pub fn shortest_axis(&self) -> Vector3 {
match self.shortest_axis_index() {
pub fn shortest_axis(&self) -> Option<Vector3> {
self.shortest_axis_index().map(|axis| match axis {
Vector3Axis::X => Vector3::RIGHT,
Vector3Axis::Y => Vector3::UP,
Vector3Axis::Z => Vector3::BACK,
}
})
}

/// Returns the index of the shortest axis of the AABB (according to Vector3::AXIS* enum).
#[inline]
pub fn shortest_axis_index(&self) -> Vector3Axis {
self.size.min_axis_index()
pub fn shortest_axis_index(&self) -> Option<Vector3Axis> {
self.size.min_axis()
}

/// Returns the scalar length of the shortest axis of the AABB.
Expand Down Expand Up @@ -436,12 +436,12 @@ mod test {
size: Vector3::new(4.0, 6.0, 8.0),
};

assert_eq!(aabb.shortest_axis(), Vector3::RIGHT);
assert_eq!(aabb.longest_axis(), Vector3::BACK);
assert_eq!(aabb.shortest_axis(), Some(Vector3::RIGHT));
assert_eq!(aabb.longest_axis(), Some(Vector3::BACK));
assert_eq!(aabb.shortest_axis_size(), 4.0);
assert_eq!(aabb.longest_axis_size(), 8.0);
assert_eq!(aabb.shortest_axis_index(), Vector3Axis::X);
assert_eq!(aabb.longest_axis_index(), Vector3Axis::Z);
assert_eq!(aabb.shortest_axis_index(), Some(Vector3Axis::X));
assert_eq!(aabb.longest_axis_index(), Some(Vector3Axis::Z));
}

#[test]
Expand Down
1 change: 1 addition & 0 deletions godot-core/src/builtin/math/float.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub trait FloatExt: private::Sealed + Copy {
/// Check if `self` is within [`Self::CMP_EPSILON`] of `0.0`.
fn is_zero_approx(self) -> bool;

/// Returns the floating-point modulus of `self` divided by `pmod`, wrapping equally in positive and negative.
fn fposmod(self, pmod: Self) -> Self;

/// Returns the multiple of `step` that is closest to `self`.
Expand Down
218 changes: 61 additions & 157 deletions godot-core/src/builtin/vectors/vector2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use core::cmp::Ordering;
use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

use crate::builtin::inner;
use crate::builtin::math::{FloatExt, GlamConv, GlamType};
use crate::builtin::vectors::Vector2Axis;
use crate::builtin::{real, RAffine2, RVec2, Vector2i};
use crate::builtin::{inner, real, RAffine2, RVec2, Vector2i};

use std::fmt;

Expand All @@ -36,172 +36,100 @@ pub struct Vector2 {
pub y: real,
}

impl Vector2 {
/// Vector with all components set to `0.0`.
pub const ZERO: Self = Self::splat(0.0);

/// Vector with all components set to `1.0`.
pub const ONE: Self = Self::splat(1.0);

/// Vector with all components set to `real::INFINITY`.
pub const INF: Self = Self::splat(real::INFINITY);

/// Unit vector in -X direction (right in 2D coordinate system).
pub const LEFT: Self = Self::new(-1.0, 0.0);

/// Unit vector in +X direction (right in 2D coordinate system).
pub const RIGHT: Self = Self::new(1.0, 0.0);

/// Unit vector in -Y direction (up in 2D coordinate system).
pub const UP: Self = Self::new(0.0, -1.0);
impl_vector_operators!(Vector2, real, (x, y));

/// Unit vector in +Y direction (down in 2D coordinate system).
pub const DOWN: Self = Self::new(0.0, 1.0);
impl_vector_consts!(Vector2, real);
impl_float_vector_consts!(Vector2);
impl_vector2x_consts!(Vector2, real);

/// Constructs a new `Vector2` from the given `x` and `y`.
pub const fn new(x: real, y: real) -> Self {
Self { x, y }
}

/// Constructs a new `Vector2` with both components set to `v`.
pub const fn splat(v: real) -> Self {
Self::new(v, v)
}
impl_vector_fns!(Vector2, RVec2, real, (x, y));
impl_float_vector_fns!(Vector2, (x, y));
impl_vector2x_fns!(Vector2, real);
impl_vector2_vector3_fns!(Vector2, (x, y));

impl Vector2 {
/// Constructs a new `Vector2` from a [`Vector2i`].
#[inline]
pub const fn from_vector2i(v: Vector2i) -> Self {
Self {
x: v.x as real,
y: v.y as real,
}
}

/// Converts the corresponding `glam` type to `Self`.
fn from_glam(v: RVec2) -> Self {
Self::new(v.x, v.y)
}

/// Converts `self` to the corresponding `glam` type.
fn to_glam(self) -> RVec2 {
RVec2::new(self.x, self.y)
#[doc(hidden)]
#[inline]
pub fn as_inner(&self) -> inner::InnerVector2 {
inner::InnerVector2::from_outer(self)
}

/// Returns this vector's angle with respect to the positive X axis, or `(1.0, 0.0)` vector, in radians.
///
/// For example, `Vector2::RIGHT.angle()` will return zero, `Vector2::DOWN.angle()` will return `PI / 2` (a quarter turn, or 90 degrees),
/// and `Vector2::new(1.0, -1.0).angle()` will return `-PI / 4` (a negative eighth turn, or -45 degrees).
///
/// [Illustration of the returned angle.](https://raw.githubusercontent.com/godotengine/godot-docs/master/img/vector2_angle.png)
///
/// Equivalent to the result of `y.atan2(x)`.
#[inline]
pub fn angle(self) -> real {
self.y.atan2(self.x)
}

pub fn angle_to(self, to: Self) -> real {
self.to_glam().angle_between(to.to_glam())
}

/// Returns the angle to the given vector, in radians.
///
/// [Illustration of the returned angle.](https://raw.githubusercontent.com/godotengine/godot-docs/master/img/vector2_angle_to.png)
#[inline]
pub fn angle_to_point(self, to: Self) -> real {
(to - self).angle()
}

pub fn aspect(self) -> real {
self.x / self.y
}

pub fn bounce(self, normal: Self) -> Self {
-self.reflect(normal)
}

pub fn ceil(self) -> Self {
Self::from_glam(self.to_glam().ceil())
}

pub fn clamp(self, min: Self, max: Self) -> Self {
Self::from_glam(self.to_glam().clamp(min.to_glam(), max.to_glam()))
}

/// Returns the 2D analog of the cross product for this vector and `with`.
///
/// This is the signed area of the parallelogram formed by the two vectors. If the second vector is clockwise from the first vector,
/// then the cross product is the positive area. If counter-clockwise, the cross product is the negative area. If the two vectors are
/// parallel this returns zero, making it useful for testing if two vectors are parallel.
///
/// Note: Cross product is not defined in 2D mathematically. This method embeds the 2D vectors in the XY plane of 3D space and uses
/// their cross product's Z component as the analog.
#[inline]
pub fn cross(self, with: Self) -> real {
self.to_glam().perp_dot(with.to_glam())
}

pub fn direction_to(self, to: Self) -> Self {
(to - self).normalized()
}

pub fn distance_squared_to(self, to: Self) -> real {
(to - self).length_squared()
}

pub fn distance_to(self, to: Self) -> real {
(to - self).length()
}

pub fn dot(self, other: Self) -> real {
self.to_glam().dot(other.to_glam())
}

pub fn floor(self) -> Self {
Self::from_glam(self.to_glam().floor())
}

/// Creates a unit Vector2 rotated to the given `angle` in radians. This is equivalent to doing `Vector2::new(angle.cos(), angle.sin())`
/// or `Vector2::RIGHT.rotated(angle)`.
///
/// ```no_run
/// use godot::prelude::*;
///
/// let a = Vector2::from_angle(0.0); // (1.0, 0.0)
/// let b = Vector2::new(1.0, 0.0).angle(); // 0.0
/// let c = Vector2::from_angle(real_consts::PI / 2.0); // (0.0, 1.0)
/// ```
#[inline]
pub fn from_angle(angle: real) -> Self {
Self::from_glam(RVec2::from_angle(angle))
}

pub fn is_finite(self) -> bool {
self.to_glam().is_finite()
}

pub fn is_normalized(self) -> bool {
self.to_glam().is_normalized()
}

pub fn length_squared(self) -> real {
self.to_glam().length_squared()
}

pub fn limit_length(self, length: Option<real>) -> Self {
Self::from_glam(self.to_glam().clamp_length_max(length.unwrap_or(1.0)))
}

pub fn max_axis_index(self) -> Vector2Axis {
if self.x < self.y {
Vector2Axis::Y
} else {
Vector2Axis::X
}
}

pub fn min_axis_index(self) -> Vector2Axis {
if self.x < self.y {
Vector2Axis::X
} else {
Vector2Axis::Y
}
}

pub fn move_toward(self, to: Self, delta: real) -> Self {
let vd = to - self;
let len = vd.length();
if len <= delta || len < real::CMP_EPSILON {
to
} else {
self + vd / len * delta
}
}

/// Returns a perpendicular vector rotated 90 degrees counter-clockwise compared to the original, with the same length.
#[inline]
pub fn orthogonal(self) -> Self {
Self::new(self.y, -self.x)
}

pub fn project(self, b: Self) -> Self {
Self::from_glam(self.to_glam().project_onto(b.to_glam()))
}

pub fn reflect(self, normal: Self) -> Self {
2.0 * normal * self.dot(normal) - self
}

pub fn round(self) -> Self {
Self::from_glam(self.to_glam().round())
/// Returns the result of rotating this vector by `angle` (in radians).
#[inline]
pub fn rotated(self, angle: real) -> Self {
Self::from_glam(RAffine2::from_angle(angle).transform_vector2(self.to_glam()))
}

// TODO compare with gdnative implementation:
// https://github.com/godot-rust/gdnative/blob/master/gdnative-core/src/core_types/vector3.rs#L335-L343
/// Returns the result of spherical linear interpolation between this vector and `to`, by amount `weight`.
/// `weight` is on the range of 0.0 to 1.0, representing the amount of interpolation.
///
/// This method also handles interpolating the lengths if the input vectors have different lengths.
/// For the special case of one or both input vectors having zero length, this method behaves like [`Vector2::lerp`].
#[inline]
pub fn slerp(self, to: Self, weight: real) -> Self {
let start_length_sq = self.length_squared();
let end_length_sq = to.length_squared();
Expand All @@ -213,24 +141,6 @@ impl Vector2 {
let angle = self.angle_to(to);
self.rotated(angle * weight) * (result_length / start_length)
}

pub fn slide(self, normal: Self) -> Self {
self - normal * self.dot(normal)
}

/// Returns the result of rotating this vector by `angle` (in radians).
pub fn rotated(self, angle: real) -> Self {
Self::from_glam(RAffine2::from_angle(angle).transform_vector2(self.to_glam()))
}

#[doc(hidden)]
pub fn as_inner(&self) -> inner::InnerVector2 {
inner::InnerVector2::from_outer(self)
}

pub fn coords(&self) -> (real, real) {
(self.x, self.y)
}
}

/// Formats the vector like Godot: `(x, y)`.
Expand All @@ -240,12 +150,6 @@ impl fmt::Display for Vector2 {
}
}

impl_common_vector_fns!(Vector2, real);
impl_float_vector_glam_fns!(Vector2, real);
impl_float_vector_component_fns!(Vector2, real, (x, y));
impl_vector_operators!(Vector2, real, (x, y));
impl_swizzle_trait_for_vector2x!(Vector2, real);

// SAFETY:
// This type is represented as `Self` in Godot, so `*mut Self` is sound.
unsafe impl GodotFfi for Vector2 {
Expand Down
Loading
Loading