Skip to content

Commit

Permalink
Implement the basics of built-in vector types
Browse files Browse the repository at this point in the history
For Vector{2,3,4}{,i} this implements:
- public fields
- constructors
- constants
- operators
- indexing by axis
- (private) conversions to/from glam types
- Display
- a couple of functions like `abs()` and `length()` for demonstration

See also #6.
  • Loading branch information
ttencate committed Jan 17, 2023
1 parent edf2fe5 commit f743d3a
Show file tree
Hide file tree
Showing 9 changed files with 824 additions and 125 deletions.
10 changes: 5 additions & 5 deletions examples/dodge-the-creeps/rust/src/player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl GodotExt for Player {
.base
.get_node_as::<AnimatedSprite2D>("AnimatedSprite2D");

let mut velocity = Vector2::new(0.0, 0.0).inner();
let mut velocity = Vector2::new(0.0, 0.0);

// Note: exact=false by default, in Rust we have to provide it explicitly
let input = Input::singleton();
Expand All @@ -80,7 +80,7 @@ impl GodotExt for Player {
}

if velocity.length() > 0.0 {
velocity = velocity.normalize() * self.speed;
velocity = velocity.normalized() * self.speed;

let animation;

Expand All @@ -101,10 +101,10 @@ impl GodotExt for Player {
}

let change = velocity * delta as f32;
let position = self.base.get_global_position().inner() + change;
let position = self.base.get_global_position() + change;
let position = Vector2::new(
position.x.max(0.0).min(self.screen_size.inner().x),
position.y.max(0.0).min(self.screen_size.inner().y),
position.x.max(0.0).min(self.screen_size.x),
position.y.max(0.0).min(self.screen_size.y),
);
self.base.set_global_position(position);
}
Expand Down
35 changes: 34 additions & 1 deletion godot-core/src/builtin/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,36 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

//! Built-in types like `Vector2`, `GodotString` or `Variant`.
//! Built-in types like `Vector2`, `GodotString` and `Variant`.
//!
//! # Implementation note on vector algebra types
//!
//! The basic vector algebra types like `Vector2`, `Matrix4` and `Quaternion` are re-implemented
//! here, with an API similar to that in the Godot engine itself. There are other approaches, but
//! they all have their disadvantages:
//!
//! - We could invoke API methods from the engine. The implementations could be generated, but it
//! is slower and prevents inlining.
//!
//! - We could re-export types from an existing vector algebra crate, like `glam`. This removes the
//! duplication, but it would create a strong dependency on a volatile API outside our control.
//! The `gdnative` crate started out this way, using types from `euler`, but [found it
//! impractical](https://github.com/godot-rust/gdnative/issues/594#issue-705061720). Moreover,
//! the API would not match Godot's own, which would make porting from GDScript (slightly)
//! harder.
//!
//! - We could opaquely wrap types from an existing vector algebra crate. This protects users of
//! `gdextension` from changes in the wrapped crate. However, direct field access using `.x`,
//! `.y`, `.z` is no longer possible. Instead of `v.y += a;` you would have to write
//! `v.set_y(v.get_y() + a);`. (A `union` could be used to add these fields in the public API,
//! but would make every field access unsafe, which is also not great.)
//!
//! - We could re-export types from the [`mint`](https://crates.io/crates/mint) crate, which was
//! explicitly designed to solve this problem. However, it falls short because [operator
//! overloading would become impossible](https://github.com/kvark/mint/issues/75).
mod macros;
mod vector_macros;

mod arrays;
mod color;
Expand All @@ -16,8 +43,11 @@ mod string;
mod string_name;
mod variant;
mod vector2;
mod vector2i;
mod vector3;
mod vector3i;
mod vector4;
mod vector4i;

pub mod meta;

Expand All @@ -29,5 +59,8 @@ pub use string::*;
pub use string_name::*;
pub use variant::*;
pub use vector2::*;
pub use vector2i::*;
pub use vector3::*;
pub use vector3i::*;
pub use vector4::*;
pub use vector4i::*;
129 changes: 81 additions & 48 deletions godot-core/src/builtin/vector2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,80 +4,113 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

use std::fmt;

use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

type Inner = glam::f32::Vec2;
//type Inner = glam::f64::DVec2;
use crate::builtin::Vector2i;

#[derive(Default, Copy, Clone, Debug, PartialEq)]
/// Vector used for 2D math using floating point coordinates.
///
/// # Description
///
/// 2-element structure that can be used to represent positions in 2D space or any other pair of
/// numeric values.
///
/// It uses floating-point coordinates of 32-bit precision, unlike the engine's `float` type which
/// is always 64-bit. The engine can be compiled with the option `precision=double` to use 64-bit
/// vectors, but this is not yet supported in the `gdextension` crate.
///
/// See [`Vector2i`] for its integer counterpart.
#[derive(Debug, Default, Clone, Copy, PartialEq)]
#[repr(C)]
pub struct Vector2 {
inner: Inner,
/// The vector's X component.
pub x: f32,
/// The vector's Y component.
pub y: f32,
}

impl_vector_operators!(Vector2, f32, (x, y));
impl_vector_index!(Vector2, f32, (x, y), Vector2Axis, (X, Y));
impl_common_vector_fns!(Vector2, f32);
impl_float_vector_fns!(Vector2, f32);

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

pub fn from_inner(inner: Inner) -> Self {
Self { inner }
/// Constructs a new `Vector2` with all components set to `v`.
pub const fn splat(v: f32) -> Self {
Self { x: v, y: v }
}

/// only for testing
pub fn inner(self) -> Inner {
self.inner
/// Constructs a new `Vector2` from a [`Vector2i`].
pub const fn from_vector2i(v: Vector2i) -> Self {
Self { x: v.x as f32, y: v.y as f32 }
}

// Hacks for example
// pub fn length(self) -> f32 {
// self.inner.length()
// }
// pub fn normalized(self) -> Vector2 {
// Self::from_inner(self.inner.normalize())
// }
pub fn rotated(self, angle: f32) -> Self {
Self::from_inner(glam::Affine2::from_angle(angle).transform_vector2(self.inner))
}
}
/// Zero vector, a vector with all components set to `0.0`.
pub const ZERO: Self = Self::splat(0.0);

impl GodotFfi for Vector2 {
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}
/// One vector, a vector with all components set to `1.0`.
pub const ONE: Self = Self::splat(1.0);

impl std::fmt::Display for Vector2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
/// Infinity vector, a vector with all components set to `INFIINTY`.
pub const INF: Self = Self::splat(f32::INFINITY);

// ----------------------------------------------------------------------------------------------------------------------------------------------
/// Left unit vector. Represents the direction of left.
pub const LEFT: Self = Self::new(-1.0, 0.0);

type IInner = glam::IVec2;
/// Right unit vector. Represents the direction of right.
pub const RIGHT: Self = Self::new(1.0, 0.0);

#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct Vector2i {
inner: IInner,
/// Up unit vector. Y is down in 2D, so this vector points -Y.
pub const UP: Self = Self::new(0.0, -1.0);

/// Down unit vector. Y is down in 2D, so this vector points +Y.
pub const DOWN: Self = Self::new(0.0, 1.0);

/// 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)
}

/// Converts `self` to the corresponding `glam` type.
fn to_glam(self) -> glam::Vec2 {
glam::Vec2::new(self.x, self.y)
}
}

impl Vector2i {
pub fn new(x: i32, y: i32) -> Self {
Self {
inner: IInner::new(x, y),
}
impl fmt::Display for Vector2 {
/// Formats this vector in the same way the Godot engine would.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

impl GodotFfi for Vector2i {
impl GodotFfi for Vector2 {
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}

impl std::fmt::Display for Vector2i {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
/// Enumerates the axes in a [`Vector2`].
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(i32)]
pub enum Vector2Axis {
/// The X axis.
X,
/// The Y axis.
Y,
}

impl GodotFfi for Vector2Axis {
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}
107 changes: 107 additions & 0 deletions godot-core/src/builtin/vector2i.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* 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 godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};

use crate::builtin::Vector2;

/// Vector used for 2D math using integer coordinates.
///
/// # Description
///
/// 2-element structure that can be used to represent positions in 2D space or any other pair of
/// numeric values.
///
/// It uses integer coordinates and is therefore preferable to [`Vector2`] when exact precision is
/// required. Note that the values are limited to 32 bits, and unlike [`Vector2`] this cannot be
/// configured with an engine build option. Use `i64` or [`PackedInt64Array`] if 64-bit values are
/// needed.
#[derive(Debug, Default, Clone, Copy, Eq, Ord, PartialEq, PartialOrd)]
#[repr(C)]
pub struct Vector2i {
/// The vector's X component.
pub x: i32,
/// The vector's Y component.
pub y: i32,
}

impl_vector_operators!(Vector2i, i32, (x, y));
impl_vector_index!(Vector2i, i32, (x, y), Vector2iAxis, (X, Y));
impl_common_vector_fns!(Vector2i, i32);

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

/// Constructs a new `Vector2i` with all components set to `v`.
pub const fn splat(v: i32) -> Self {
Self { x: v, y: v }
}

/// Constructs a new `Vector2i` from a [`Vector2`]. The floating point coordinates will be
/// truncated.
pub const fn from_vector2(v: Vector2) -> Self {
Self { x: v.x as i32, y: v.y as i32 }
}

/// Zero vector, a vector with all components set to `0`.
pub const ZERO: Self = Self::splat(0);

/// One vector, a vector with all components set to `1`.
pub const ONE: Self = Self::splat(1);

/// Left unit vector. Represents the direction of left.
pub const LEFT: Self = Self::new(-1, 0);

/// Right unit vector. Represents the direction of right.
pub const RIGHT: Self = Self::new(1, 0);

/// Up unit vector. Y is down in 2D, so this vector points -Y.
pub const UP: Self = Self::new(0, -1);

/// Down unit vector. Y is down in 2D, so this vector points +Y.
pub const DOWN: Self = Self::new(0, 1);

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

/// Converts `self` to the corresponding `glam` type.
fn to_glam(self) -> glam::IVec2 {
glam::IVec2::new(self.x, self.y)
}
}

impl fmt::Display for Vector2i {
/// Formats this vector in the same way the Godot engine would.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}

impl GodotFfi for Vector2i {
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}

/// Enumerates the axes in a [`Vector2i`].
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[repr(i32)]
pub enum Vector2iAxis {
/// The X axis.
X,
/// The Y axis.
Y,
}

impl GodotFfi for Vector2iAxis {
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}
Loading

0 comments on commit f743d3a

Please sign in to comment.