-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Add a Dir4 to resolve #17983 #19223
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
base: main
Are you sure you want to change the base?
Add a Dir4 to resolve #17983 #19223
Conversation
Welcome, new contributor! Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨ |
crates/bevy_math/src/direction.rs
Outdated
Self(value) | ||
} | ||
|
||
/// Create a direction from a finite, nonzero [`Vec3`], normalizing it and |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// Create a direction from a finite, nonzero [`Vec3`], normalizing it and | |
/// Create a direction from a finite, nonzero [`Vec4`], normalizing it and |
crates/bevy_math/src/direction.rs
Outdated
.ok_or(InvalidDirectionError::from_length(length)) | ||
} | ||
|
||
/// Create a direction from its `w`, `x`, `y`, and `z` components. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order seems a bit peculiar.
Despite not being alphabetic, the typical ordering is xyzw (also AXES are ordered that way).
Also Vec4::W is (0,0,0,1).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah this needs to to be uniformly xyzw order, this is just the sort of thing that'd trap me for hours because I didn't notice the difference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I fixed it.
@@ -1090,4 +1279,26 @@ mod tests { | |||
); | |||
assert!(dir_b.is_normalized(), "Renormalisation did not work."); | |||
} | |||
|
|||
#[test] | |||
fn dir4_creation() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would propose the following tests:
assert_eq!(Dir4::from_xyzw(1.0, 0.0, 0.0, 0.0), Ok(Dir4::X));
assert_eq!(Dir4::from_xyzw(0.0, 1.0, 0.0, 0.0), Ok(Dir4::Y));
assert_eq!(Dir4::from_xyzw(0.0, 0.0, 1.0, 0.0), Ok(Dir4::Z));
assert_eq!(Dir4::from_xyzw(0.0, 0.0, 0.0, 1.0), Ok(Dir4::W));
assert_eq!(Dir4::from_xyzw(-1.0, 0.0, 0.0, 0.0), Ok(Dir4::NEG_X));
assert_eq!(Dir4::from_xyzw(0.0, -1.0, 0.0, 0.0), Ok(Dir4::NEG_Y));
assert_eq!(Dir4::from_xyzw(0.0, 0.0, -1.0, 0.0), Ok(Dir4::NEG_Z));
assert_eq!(Dir4::from_xyzw(0.0, 0.0, 0.0, -1.0), Ok(Dir4::NEG_W));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those tests shouldn't be necessary since they should always give that same output unless something goes catastrophically wrong.
Should impl |
Implementing |
Ahhhhhhhh my bad! |
No problem. I didn't even know that this existed until you mentioned it. |
We can use the formula from here: It might need to fall back to a lerp or something for very close values. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good.
Slerp support isn't essential, fine with leaving it for a follow up if you want.
It will have to be a follow up since glam is missing an angle_between function for Vec4 which would be essential if we use the geometric slerp mentioned in the Wikipedia article. I guess I'm going to work on putting that into glam next since I have nothing better to do with my time lol. |
The vectors are already normalized so the angle is just |
Oh. Well now I feel like an idiot. Thanks for pointing that out. I'll get a commit for that prepared right away. :) |
crates/bevy_math/src/direction.rs
Outdated
#[inline] | ||
pub fn slerp(self, rhs: Self, s: f32) -> Self { | ||
// This uses a geometric slerp as opposed to the Quaternion slerp Dir3 uses | ||
let angle = acos(self.dot(*rhs)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can produce NaNs due to f32 imprecision (and/or imperfect normalisation) if both directions point the same way, or opposite ways.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Perhaps not the best solution, but it is a solution.
crates/bevy_math/src/direction.rs
Outdated
let p0 = (sin((1.0 - s) * angle) / sin(angle)) * self; | ||
let p1 = (sin(s * angle) / sin(angle)) * rhs; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two lines will divide by zero (or numbers very close) for directions which are pointing the same way, or opposite ways. (Their angle is either 0 or pi)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I straight up just don't know how to fix this. I haven't been using rust for that long and I thought that this would've been much more simple than it ended up being. Perhaps I'll just cut the function and make it a problem for later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to use ickshonpe's approach after reading through the comments looking for a solution and just added a branching path to use lerp instead of slerp when the sine of the angle is 0.
Edit:
Perhaps I should also wait until I'm not in a bad mood to post comments like the previous one lol.
crates/bevy_math/src/direction.rs
Outdated
@@ -1323,4 +1363,34 @@ mod tests { | |||
); | |||
assert!(dir_b.is_normalized(), "Renormalisation did not work."); | |||
} | |||
|
|||
#[test] | |||
fn dir4_slerp() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests all slerp only between perpendicular directions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added two that test along parallel axes. More may be needed in the future. However it is also a problem shared among the other slerp functions, so more work may need to be done there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The slerp function is rather unstable in its current state.
crates/bevy_math/src/direction.rs
Outdated
// Perhaps it should be improved, but I'm reaching my limit | ||
result = self.map(|f| if f != 0.0 { 8.940697e-8 } else { 0.0 }); | ||
} | ||
return Dir4(result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This returns denormalised Dir4's most of the time.
crates/bevy_math/src/direction.rs
Outdated
// When the values are too small to use slerp it falls back on a regular lerp. | ||
let angle = acos(self.dot(*rhs)); | ||
if angle.is_nan() { | ||
return self; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is an okay solution, although I prefer the method used in glam where they just clamp the input to acos to <-1.0, 1.0> to avoid this completely as this could potentially create sporadic jumps rarely.
crates/bevy_math/src/direction.rs
Outdated
if angle.is_nan() { | ||
return self; | ||
} | ||
if angle == 0.0 || angle == PI { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exact float comparison doesn't help much here. While it avoids the case sin(0.0) == 0.0, it doesn't avoid all of the nearby values where the value is very small and the result of the division is a fairly absurdly large number.
crates/bevy_math/src/direction.rs
Outdated
|
||
assert_relative_eq!( | ||
Dir4::X.slerp(Dir4::NEG_X, 0.5), | ||
Dir4(Vec4::new(8.940697e-8, 0.0, 0.0, 0.0)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not a valid Dir4.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest just dropping the slerp, it's very complicated to get right and I think focusing on it would just bog down the rest of the PR from getting merged which would be a shame as the PR is quite good.
Alright. I'll revert the slerp and make it not my problem |
Objective
Fixes #17983
Solution
Implemented a basic
Dir4
struct with methods similar toDir3
andDir2
.Testing
Added unit tests that follow the same pattern of the other Dir structs.
Since the other Dir structs use rotations to test renormalization and I have been following them as a pattern for the Dir4 struct I haven't implemented a test to cover renormalization of Dir4's.
Use Dir4 in the wild.
N/A (Tested on Linux/X11 but it shouldn't be a problem)