Comments (7)
I did some work and created an example for 2. this is what it seems it would look like
EDIT: some of the functions were made in an external math.rs file for things that glam didnt have (or i didnt think they
had)
Fixed minor things with syntax (Thanks chitoyuu!)
/*
* 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::ops::*;
use godot_ffi as sys;
use sys::{ffi_methods, GodotFfi};
use super::math::*;
type Inner = glam::f32::Vec2;
//type Inner = glam::f64::DVec2;
#[derive(Default, Copy, Clone, Debug, PartialEq)]
#[repr(C)]
pub struct Vector2 {
inner: Inner,
}
impl Vector2 {
pub fn new(x: f32, y: f32) -> Self {
Self {
inner: Inner::new(x, y),
}
}
pub fn abs(self) -> Self {
Self::from_inner(glam::Vector2::abs(self.inner))
}
pub fn angle(self) -> f32 {
self.inner.y.atan2(self.inner.x)
}
pub fn angle_to(self, to: Self) -> f32 {
self.inner.angle_between(to.inner)
}
pub fn angle_to_point(self, to: Self) -> f32 {
Self::from_inner(to - self).angle();
}
pub fn aspect(self) -> f32 {
self.inner.x / self.inner.y
}
pub fn bezier_derivative(
mut self,
control_1: Self,
control_2: Self,
end: Self,
t: f32,
) -> Self {
self.inner.x = bezier_derivative(self.inner.x, control_1.x, control_2.x, end.x, t);
self.inner.y = bezier_derivative(self.inner.y, control_1.y, control_2.y, end.y, t);
self
}
pub fn bezier_interpolate(
mut self,
control_1: Self,
control_2: Self,
end: Self,
t: f32,
) -> Self {
self.inner.x = bezier_interpolate(self.inner.x, control_1.x, control_2.x, end.x, t);
self.inner.y = bezier_interpolate(self.inner.y, control_1.y, control_2.y, end.y, t);
self
}
pub fn bounce(self, normal: Self) -> Self {
-self.reflect(normal)
}
pub fn ceil(self) -> Self {
Self::from_inner(self.inner.ceil())
}
pub fn clamp(mut self, min: Self, max: Self) -> Self {
self.inner.x = if self.inner.x > max.inner.x {
max.inner.x
} else if self.inner.x < min.inner.x {
min.inner.x
} else {
self.inner.x
};
self.inner.x = if self.inner.y > max.inner.y {
max.inner.y
} else if self.inner.y < min.inner.y {
min.inner.y
} else {
self.inner.y
};
self
}
pub fn cross(self, with: Self) -> f32 {
Self::from_inner(self.inner.perp_dot(with.inner))
}
pub fn cubic_interpolate(mut self, b: Self, pre_a: Self, post_b: Self, weight: f32) -> Self {
self.inner.x = cubic_interpolate(
self.inner.x,
b.inner.x,
pre_a.inner.x,
post_b.inner.x,
weight,
);
self.inner.y = cubic_interpolate(
self.inner.y,
b.inner.y,
pre_a.inner.y,
post_b.inner.y,
weight,
);
self
}
pub fn cubic_interpolate_in_time(
mut self,
b: Self,
pre_a: Self,
post_b: Self,
weight: f32,
b_t: f32,
pre_a_t: f32,
post_b_t: f32,
) -> Self {
self.inner.x = cubic_interpolate_in_time(
self.inner.x,
b.inner.x,
pre_a.inner.x,
post_b.inner.x,
weight,
b_t,
pre_a_t,
post_b_t,
);
self.inner.y = cubic_interpolate_in_time(
self.inner.y,
b.inner.y,
pre_a.inner.y,
post_b.inner.y,
weight,
b_t,
pre_a_t,
post_b_t,
);
self
}
pub fn direction_to(self, to: Self) -> Self {
(to - self).normalize()
}
pub fn distance_squared_to(self, to: Self) -> Self {
Self::from_inner(self.inner.distance_squared(to.inner))
}
pub fn distance_to(self, to: Self) -> Self {
Self::from_inner(self.inner.distance(to.inner))
}
pub fn dot(self, other: Self) -> Self {
Self::from_inner(self.inner.dot(other.inner))
}
pub fn floor(self) -> Self {
Self::from_inner(self.inner.floor())
}
pub fn from_angle(angle: f32) -> Self {
Self::from_inner(Inner::from_angle(angle))
}
pub fn is_equal_approx(self, to: Self) -> bool {
is_equal_approx(self.inner.x, to.inner.x) && is_equal_approx(self.inner.y, to.inner.y);
}
pub fn is_finite(self) -> bool {
self.inner.is_finite()
}
pub fn is_normalized(self) -> bool {
self.inner.is_normalized()
}
pub fn is_zero_approx(self) -> bool {
is_zero_approx(self.inner.x) && is_zero_approx(self.inner.y)
}
pub fn length(self) -> f32 {
self.inner.length()
}
pub fn length_squared(self) -> f32 {
self.inner.length_squared()
}
pub fn lerp(self, to: Self, weight: f32) -> Self {
Self::from_inner(self.inner.lerp(to, weight))
}
pub fn limit_length(self, length: Option<f32>) -> Self {
Self::from_inner(self.inner.clamp_length_max(length.unwrap_or(1.0)))
}
pub fn max_axis_index(self) -> i32 {
if self.inner.max_element() == self.inner.x {
0
} else {
1
}
}
pub fn min_axis_index(self) -> i32 {
if self.inner.min_element() == self.inner.x {
0
} else {
1
}
}
pub fn move_toward(mut self, to: Self, delta: f32) -> Self {
let vd = to - self;
let len = vd.length();
Self::from_inner(if len <= delta || len < CMP_EPSILON {
to
} else {
self + vd / len * delta
});
}
pub fn normalized(self) -> Self {
Self::from_inner(self.inner.normalized())
}
pub fn orthogonal(self) -> Self {
Self::from_inner(self.inner.orthogonal())
}
pub fn posmod(self, pmod: f32) -> Self {
Self::new(fposmod(self.inner.x, pmod), fposmod(self.inner.y, pmod))
}
pub fn posmodv(self, modv: Self) -> Self {
Self::new(
fposmod(self.inner.x, modv.inner.x),
fposmod(self.inner.y, modv.inner.y),
)
}
pub fn project(self, b: Self) -> Self {
Self::from_inner(self.inner.project_onto(b.inner))
}
pub fn reflect(self, normal: Self) -> Self {
//Self::from_inner(2.0 * normal.inner * self.dot(normal) - self.inner)
Self::from_inner(self.inner.reject_from(normal.inner))
}
pub fn rotated(self, angle: f32) -> Self {
Self::from_inner(self.inner.rotate(Self::from_angle(angle)))
}
pub fn round(self) -> Self {
Self::from_inner(self.inner.round())
}
pub fn sign(self) -> Self {
-self
}
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 {
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.inner.x, step), snapped(self.inner.y, step))
}
fn from_inner(inner: Inner) -> Self {
Self { inner }
}
}
impl Add for Vector2 {
type Output = Self;
fn add(self, other: Self) -> Self {
Self::new(self.inner.x + other.inner.x, self.inner.y + other.inner.y)
}
}
impl AddAssign for Vector2 {
fn add_assign(&mut self, other: Self) {
*self = self + other
}
}
impl Sub for Vector2 {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self::new(self.inner.x - other.inner.x, self.inner.y - other.inner.y)
}
}
impl SubAssign for Vector2 {
fn sub_assign(&mut self, other: Self) {
*self = self - other
}
}
impl Mul for Vector2 {
type Output = Self;
fn mul(self, other: f32) -> Self {
Self::new(self.inner.x * other, self.inner.y * other)
}
}
impl MulAssign for Vector2 {
fn mul_assign(&mut self, other: f32) {
*self = self * other
}
}
impl Div for Vector2 {
type Output = Self;
fn div(self, other: f32) -> Self {
Self::new(self.inner.x / other, self.inner.y / other)
}
}
impl DivAssign for Vector2 {
fn div_assign(&mut self, other: f32) {
*self = self / other
}
}
impl Neg for Vector2 {
type Output = Self;
fn neg(self) -> Self {
Self::new(-self.inner.x, -self.inner.y)
}
}
impl GodotFfi for Vector2 {
ffi_methods! { type sys::GDExtensionTypePtr = *mut Self; .. }
}
impl std::fmt::Display for Vector2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.inner.fmt(f)
}
}
// ----------------------------------------------------------------------------------------------------------------------------------------------
type IInner = glam::IVec2;
#[derive(Default, Copy, Clone, Debug, Eq, PartialEq)]
#[repr(C)]
pub struct Vector2i {
inner: IInner,
}
impl Vector2i {
pub fn new(x: i32, y: i32) -> Self {
Self {
inner: IInner::new(x, y),
}
}
}
impl GodotFfi for Vector2i {
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)
}
}
from gdext.
Talked a bit on Discord with @RealAstolfo about some non-idiomatic expressions in the code.
I think it's a nice beginning if we end up taking option 2 (fresh re-implementation). It feels bizarre to have to consider licensing within the same general project indeed.
Option 1 is interesting, although also a tricky one to end users. I'd imagine it to be hard to wrap one's head around if we make the full API interface available through FFI, only to suggest users to ignore them all and convert away from the type immediately if they want their code to be fast.
I suppose it isn't exactly against the project's goals, that are still completeness and correctness first and foremost, and we aren't generally inclined to be opinionated, but it still feels weird.
from gdext.
A native implementation (option 2/3) seems like the best way to me. It's a bit of work to implement these types once, but the implementations tend to be simple, and these classes are pretty stable in the engine so it's not a lot of work to keep up.
The Vector*
types are currently implemented as wrappers around glam, which is nice for performance and for compatibility with other crates. But it does mean that we can't have public fields like x
; we'd need accessor methods like x()
and set_x()
instead. I think this is idiomatic Rust anyway, and it allows to switch the implementation (e.g. SIMD types) without breaking the API.
I don't see why explicit permission from the gdnative author(s) would be needed in order to reuse implementations from godot-rust/gdnative. It's MIT-licensed, so as long as the original license and copyright notice are included, it should be fine. (Offtopic: why is gdextension licensed under MPL2, instead of MIT like gdnative and Godot itself?)
In any case, I have a current need of working Vector*
, Color
and *Array
classes so I'll start working on those, mirroring the Godot API.
from gdext.
@RealAstolfo thanks a lot, that looks already very nice! 🙂
Could you submit this code as a pull request, so I can review it line-by-line? I saw you and @ttencate were working on some geometric types. Maybe submit only a PR for Vector2
in the beginning, as that will accelerate the review process, and we could take away some learnings for other types.
I think we should provide x
and y
as public fields, like in GDNative. There are no invariants to upheld on a vector and dealing with setters/getters makes user code extremely verbose. Then, we can have two private functions that would mem::transmute()
between godot-rust and glam representations. Of course Vector2
needs to be #[repr(C)]
for that.
chitoyuu:
Option 1 is interesting, although also a tricky one to end users. I'd imagine it to be hard to wrap one's head around if we make the full API interface available through FFI, only to suggest users to ignore them all and convert away from the type immediately if they want their code to be fast.
ttencate:
A native implementation (option 2/3) seems like the best way to me. It's a bit of work to implement these types once, but the implementations tend to be simple, and these classes are pretty stable in the engine so it's not a lot of work to keep up.
Fully agree, in addition:
- Two ways to do the same thing -> why do both exist, when to use which, ...
- Another place where SemVer can be broken through Godot changes
- It only helps in the transition phase; once the API is mapped, it is almost always preferable due to performance. I'd rather spend effort on getting that done quicker than having a temporary API of which we already know it's going to break/disappear.
Maybe we can add a generated code layer internally for testing, but I would do that later.
I don't see why explicit permission from the gdnative author(s) would be needed in order to reuse implementations from godot-rust/gdnative. It's MIT-licensed, so as long as the original license and copyright notice are included, it should be fine.
Yes, and this is going to be a pain. MPL2 is file based, while MIT is typically project-based. So we would need to split code depending on license, and for all future make sure we carefully consider this.
What we could do is do a git blame
on a line per line, get permission from users who contributed most, and only use their contributions. Then it doesn't matter if 10 people have each fixed a single typo. We would still need to make sure we attribute their work, e.g. as Co-authored-by:
in the commit message.
But some APIs have also changed between Godot 3 and 4. All things considered, might be easier to rewrite, but I'm open for input here!
Offtopic: why is gdextension licensed under MPL2, instead of MIT like gdnative and Godot itself?
It's briefly explained in the ReadMe, and there was a longer discussion in #dev-gdextension
on Discord a while ago. TLDR: it encourages that improvements of the library flow back upstream, without restricting commercial closed-source game development.
In any case, I have a current need of working
Vector*
,Color
and*Array
classes so I'll start working on those, mirroring the Godot API.
Those are great news, looking forward 🙂
Also here, maybe consider submitting early PRs as drafts instead of waiting until you have "the whole perfect thing". That way, we can use the time for early adjustments, and don't risk to throw away a lot of work if it doesn't pass review.
from gdext.
Here's a spreadsheet tracking current progress:
https://docs.google.com/spreadsheets/d/10P37AawtSNGH_zZ4DkpJhTpL55pRMR0koz3i1OGdPXA/edit#gid=0
It reflects the state after #71.
from gdext.
We are now only missing Callable
and Signal
. As the last four, Rect2
, Rect2i
, Aabb
, and Plane
now have a basic implementation and are being tracked in #209.
A lot of the work on Callable
and Signal
is likely gonna be hashing out some good APIs to use them though, so will be harder to fully reimplement.
from gdext.
Closed in favor of #310.
from gdext.
Related Issues (20)
- Trait bound 'Player: GodotDefault' is not satisfied error during Hello World example HOT 3
- `#[func]` function arguments with invalid types produce weird errors HOT 2
- Deep copies taking into account Rust state (unlike `Node::duplicate()`) HOT 8
- Array<Option<Gd<_>>> Crashes on Null Pointer in Godot's Editor HOT 2
- Inheritance and polymorphism proposals
- Virtual function dispatch for Resources not working HOT 4
- Add mocks for some builtin types for use when Godot isn't running HOT 5
- Missing classes in `engine` module relating to navigation HOT 1
- Recent commit causes "GDExtension initialization function 'gdext_rust_init' returned an error" HOT 1
- Vector3 signed_angle_to is incorrect
- Simple VideoStreamPlayback causes thread panic HOT 4
- It is possible to have a `Base` pointing to a dead object HOT 2
- Issues with string & sse4 support HOT 3
- Problems related to multithreaded access imposed by Godot
- It is tricky to figure out how to properly use `WithBaseField` generically
- commit() failing for SurfaceTool in simple case HOT 6
- godot endlessly prompting to restart HOT 5
- Foreign Classes / Interfaces HOT 1
- Converting Typed Arrays to Variant Arrays Panics HOT 12
- Can't export variable with suffix HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from gdext.