diff --git a/godot-core/src/builtin/math.rs b/godot-core/src/builtin/math.rs index d2d8966fc..600cfc3e4 100644 --- a/godot-core/src/builtin/math.rs +++ b/godot-core/src/builtin/math.rs @@ -41,6 +41,16 @@ pub fn snapped(mut value: f32, step: f32) -> f32 { value } +pub fn sign(value: f32) -> f32 { + if value == 0.0 { + 0.0 + } else if value < 0.0 { + -1.0 + } else { + 1.0 + } +} + pub fn bezier_derivative(start: f32, control_1: f32, control_2: f32, end: f32, t: f32) -> f32 { let omt = 1.0 - t; let omt2 = omt * omt; @@ -66,6 +76,7 @@ pub fn cubic_interpolate(from: f32, to: f32, pre: f32, post: f32, weight: f32) - + (-pre + 3.0 * from - 3.0 * to + post) * (weight * weight * weight)) } +#[allow(clippy::too_many_arguments)] pub fn cubic_interpolate_in_time( from: f32, to: f32, diff --git a/godot-core/src/builtin/mod.rs b/godot-core/src/builtin/mod.rs index 30d2a8044..60ad180f4 100644 --- a/godot-core/src/builtin/mod.rs +++ b/godot-core/src/builtin/mod.rs @@ -38,6 +38,7 @@ mod vector_macros; mod arrays; mod color; mod dictionary; +mod math; mod node_path; mod others; mod packed_array; @@ -58,6 +59,7 @@ pub use crate::dict; pub use arrays::*; pub use color::*; pub use dictionary::*; +pub use math::*; pub use node_path::*; pub use others::*; pub use packed_array::*; diff --git a/godot-core/src/builtin/vector2.rs b/godot-core/src/builtin/vector2.rs index b4372be43..318fd3b05 100644 --- a/godot-core/src/builtin/vector2.rs +++ b/godot-core/src/builtin/vector2.rs @@ -3,12 +3,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ - use std::fmt; +use std::ops::*; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; +use crate::builtin::math::*; use crate::builtin::{inner, Vector2i}; /// Vector used for 2D math using floating point coordinates. @@ -70,11 +71,6 @@ impl Vector2 { } } - /// Returns the result of rotating this vector by `angle` (in radians). - pub fn rotated(self, angle: f32) -> Self { - Self::from_glam(glam::Affine2::from_angle(angle).transform_vector2(self.to_glam())) - } - /// Converts the corresponding `glam` type to `Self`. fn from_glam(v: glam::Vec2) -> Self { Self::new(v.x, v.y) @@ -85,6 +81,213 @@ impl Vector2 { glam::Vec2::new(self.x, self.y) } + pub fn angle(self) -> f32 { + self.y.atan2(self.x) + } + + pub fn angle_to(self, to: Self) -> f32 { + self.to_glam().angle_between(to.to_glam()) + } + + pub fn angle_to_point(self, to: Self) -> f32 { + (to - self).angle() + } + + pub fn aspect(self) -> f32 { + self.x / self.y + } + + pub fn bezier_derivative(self, control_1: Self, control_2: Self, end: Self, t: f32) -> Self { + let x = bezier_derivative(self.x, control_1.x, control_2.x, end.x, t); + let y = bezier_derivative(self.y, control_1.y, control_2.y, end.y, t); + + Self::new(x, y) + } + + pub fn bezier_interpolate(self, control_1: Self, control_2: Self, end: Self, t: f32) -> Self { + let x = bezier_interpolate(self.x, control_1.x, control_2.x, end.x, t); + let y = bezier_interpolate(self.y, control_1.y, control_2.y, end.y, t); + + Self::new(x, 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())) + } + + pub fn cross(self, with: Self) -> f32 { + self.to_glam().perp_dot(with.to_glam()) + } + + pub fn cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: f32) -> Self { + let x = cubic_interpolate(self.x, b.x, pre_a.x, post_b.x, weight); + let y = cubic_interpolate(self.y, b.y, pre_a.y, post_b.y, weight); + + Self::new(x, y) + } + + #[allow(clippy::too_many_arguments)] + pub fn cubic_interpolate_in_time( + self, + b: Self, + pre_a: Self, + post_b: Self, + weight: f32, + b_t: f32, + pre_a_t: f32, + post_b_t: f32, + ) -> Self { + let x = cubic_interpolate_in_time( + self.x, b.x, pre_a.x, post_b.x, weight, b_t, pre_a_t, post_b_t, + ); + let y = cubic_interpolate_in_time( + self.y, b.y, pre_a.y, post_b.y, weight, b_t, pre_a_t, post_b_t, + ); + + Self::new(x, y) + } + + pub fn direction_to(self, to: Self) -> Self { + (to - self).normalized() + } + + pub fn distance_squared_to(self, to: Self) -> f32 { + (to - self).length_squared() + } + + pub fn distance_to(self, to: Self) -> f32 { + (to - self).length() + } + + pub fn dot(self, other: Self) -> f32 { + self.to_glam().dot(other.to_glam()) + } + + pub fn floor(self) -> Self { + Self::from_glam(self.to_glam().floor()) + } + + pub fn from_angle(angle: f32) -> Self { + Self::from_glam(glam::Vec2::from_angle(angle)) + } + + pub fn is_equal_approx(self, to: Self) -> bool { + is_equal_approx(self.x, to.x) && is_equal_approx(self.y, to.y) + } + + pub fn is_finite(self) -> bool { + self.to_glam().is_finite() + } + + pub fn is_normalized(self) -> bool { + self.to_glam().is_normalized() + } + + pub fn is_zero_approx(self) -> bool { + is_zero_approx(self.x) && is_zero_approx(self.y) + } + + pub fn length_squared(self) -> f32 { + self.to_glam().length_squared() + } + + pub fn lerp(self, to: Self, weight: f32) -> Self { + Self::from_glam(self.to_glam().lerp(to.to_glam(), weight)) + } + + pub fn limit_length(self, length: Option) -> 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: f32) -> Self { + let vd = to - self; + let len = vd.length(); + if len <= delta || len < CMP_EPSILON { + to + } else { + self + vd / len * delta + } + } + + pub fn orthogonal(self) -> Self { + Self::new(self.y, -self.x) + } + + pub fn posmod(self, pmod: f32) -> Self { + Self::new(fposmod(self.x, pmod), fposmod(self.y, pmod)) + } + + pub fn posmodv(self, modv: Self) -> Self { + Self::new(fposmod(self.x, modv.x), fposmod(self.y, modv.y)) + } + + 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 { + Self::from_glam(self.to_glam().reject_from(normal.to_glam())) + } + + pub fn round(self) -> Self { + Self::from_glam(self.to_glam().round()) + } + + pub fn sign(self) -> Self { + Self::new(sign(self.x), sign(self.y)) + } + + // TODO compare with gdnative implementation: + // https://github.com/godot-rust/gdnative/blob/master/gdnative-core/src/core_types/vector3.rs#L335-L343 + pub fn slerp(self, to: Self, weight: f32) -> Self { + let start_length_sq = self.length_squared(); + let end_length_sq = to.length_squared(); + if start_length_sq == 0.0 || end_length_sq == 0.0 { + return self.lerp(to, weight); + } + let start_length = start_length_sq.sqrt(); + let result_length = lerp(start_length, end_length_sq.sqrt(), weight); + 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) + } + + pub fn snapped(self, step: Self) -> Self { + Self::new(snapped(self.x, step.x), snapped(self.y, step.y)) + } + + /// Returns the result of rotating this vector by `angle` (in radians). + pub fn rotated(self, angle: f32) -> Self { + Self::from_glam(glam::Affine2::from_angle(angle).transform_vector2(self.to_glam())) + } + #[doc(hidden)] pub fn as_inner(&self) -> inner::InnerVector2 { inner::InnerVector2::from_outer(self) diff --git a/godot-core/src/builtin/vector3.rs b/godot-core/src/builtin/vector3.rs index 30786d755..92d156c51 100644 --- a/godot-core/src/builtin/vector3.rs +++ b/godot-core/src/builtin/vector3.rs @@ -3,12 +3,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::ops::*; use std::fmt; use godot_ffi as sys; use sys::{ffi_methods, GodotFfi}; +use crate::builtin::math::*; use crate::builtin::Vector3i; /// Vector used for 3D math using floating point coordinates. @@ -85,6 +87,221 @@ impl Vector3 { fn to_glam(self) -> glam::Vec3 { glam::Vec3::new(self.x, self.y, self.z) } + + pub fn angle_to(self, to: Self) -> f32 { + self.to_glam().angle_between(to.to_glam()) + } + + pub fn bezier_derivative(self, control_1: Self, control_2: Self, end: Self, t: f32) -> Self { + let x = bezier_derivative(self.x, control_1.x, control_2.x, end.x, t); + let y = bezier_derivative(self.y, control_1.y, control_2.y, end.y, t); + let z = bezier_derivative(self.z, control_1.z, control_2.z, end.z, t); + + Self::new(x, y, z) + } + + pub fn bezier_interpolate(self, control_1: Self, control_2: Self, end: Self, t: f32) -> Self { + let x = bezier_interpolate(self.x, control_1.x, control_2.x, end.x, t); + let y = bezier_interpolate(self.y, control_1.y, control_2.y, end.y, t); + let z = bezier_interpolate(self.z, control_1.z, control_2.z, end.z, t); + + Self::new(x, y, z) + } + + 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())) + } + + pub fn cross(self, with: Self) -> Self { + Self::from_glam(self.to_glam().cross(with.to_glam())) + } + + pub fn cubic_interpolate(self, b: Self, pre_a: Self, post_b: Self, weight: f32) -> Self { + let x = cubic_interpolate(self.x, b.x, pre_a.x, post_b.x, weight); + let y = cubic_interpolate(self.y, b.y, pre_a.y, post_b.y, weight); + let z = cubic_interpolate(self.z, b.z, pre_a.z, post_b.z, weight); + + Self::new(x, y, z) + } + + #[allow(clippy::too_many_arguments)] + pub fn cubic_interpolate_in_time( + self, + b: Self, + pre_a: Self, + post_b: Self, + weight: f32, + b_t: f32, + pre_a_t: f32, + post_b_t: f32, + ) -> Self { + let x = cubic_interpolate_in_time( + self.x, b.x, pre_a.x, post_b.x, weight, b_t, pre_a_t, post_b_t, + ); + let y = cubic_interpolate_in_time( + self.y, b.y, pre_a.y, post_b.y, weight, b_t, pre_a_t, post_b_t, + ); + let z = cubic_interpolate_in_time( + self.z, b.z, pre_a.z, post_b.z, weight, b_t, pre_a_t, post_b_t, + ); + + Self::new(x, y, z) + } + + pub fn direction_to(self, to: Self) -> Self { + (to - self).normalized() + } + + pub fn distance_squared_to(self, to: Self) -> f32 { + (to - self).length_squared() + } + + pub fn distance_to(self, to: Self) -> f32 { + (to - self).length() + } + + pub fn dot(self, with: Self) -> f32 { + self.to_glam().dot(with.to_glam()) + } + + pub fn floor(self) -> Self { + Self::from_glam(self.to_glam().floor()) + } + + pub fn inverse(self) -> Self { + Self::new(1.0 / self.x, 1.0 / self.y, 1.0 / self.z) + } + + pub fn is_equal_approx(self, to: Self) -> bool { + is_equal_approx(self.x, to.x) + && is_equal_approx(self.y, to.y) + && is_equal_approx(self.z, to.z) + } + + pub fn is_finite(self) -> bool { + self.to_glam().is_finite() + } + + pub fn is_normalized(self) -> bool { + self.to_glam().is_normalized() + } + + pub fn is_zero_approx(self) -> bool { + is_zero_approx(self.x) && is_zero_approx(self.y) && is_zero_approx(self.z) + } + + pub fn length_squared(self) -> f32 { + self.to_glam().length_squared() + } + + pub fn lerp(self, to: Self, weight: f32) -> Self { + Self::from_glam(self.to_glam().lerp(to.to_glam(), weight)) + } + + pub fn limit_length(self, length: Option) -> Self { + Self::from_glam(self.to_glam().clamp_length_max(length.unwrap_or(1.0))) + } + + pub fn max_axis_index(self) -> Vector3Axis { + if self.x < self.y { + if self.y < self.z { + Vector3Axis::Z + } else { + Vector3Axis::Y + } + } else if self.x < self.z { + Vector3Axis::Z + } else { + Vector3Axis::X + } + } + + pub fn min_axis_index(self) -> Vector3Axis { + if self.x < self.y { + if self.x < self.z { + Vector3Axis::X + } else { + Vector3Axis::Z + } + } else if self.y < self.z { + Vector3Axis::Y + } else { + Vector3Axis::Z + } + } + + pub fn move_toward(self, to: Self, delta: f32) -> Self { + let vd = to - self; + let len = vd.length(); + if len <= delta || len < CMP_EPSILON { + to + } else { + self + vd / len * delta + } + } + + pub fn posmod(self, pmod: f32) -> Self { + Self::new( + fposmod(self.x, pmod), + fposmod(self.y, pmod), + fposmod(self.z, pmod), + ) + } + + pub fn posmodv(self, modv: Self) -> Self { + Self::new( + fposmod(self.x, modv.x), + fposmod(self.y, modv.y), + fposmod(self.z, modv.z), + ) + } + + 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 { + Self::from_glam(self.to_glam().reject_from(normal.to_glam())) + } + + pub fn round(self) -> Self { + Self::from_glam(self.to_glam().round()) + } + + pub fn sign(self) -> Self { + Self::new(sign(self.x), sign(self.y), sign(self.z)) + } + + pub fn signed_angle_to(self, to: Self, axis: Self) -> f32 { + let cross_to = self.cross(to); + let unsigned_angle = self.dot(to).atan2(cross_to.length()); + let sign = cross_to.dot(axis); + if sign < 0.0 { + -unsigned_angle + } else { + unsigned_angle + } + } + + pub fn slide(self, normal: Self) -> Self { + self - normal * self.dot(normal) + } + + pub fn snapped(self, step: Self) -> Self { + Self::new( + snapped(self.x, step.x), + snapped(self.y, step.y), + snapped(self.z, step.z), + ) + } } /// Formats the vector like Godot: `(x, y, z)`.