Skip to content

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

Open
wants to merge 12 commits into
base: main
Choose a base branch
from

Conversation

stevehello166
Copy link
Contributor

@stevehello166 stevehello166 commented May 15, 2025

Objective

Fixes #17983

Solution

Implemented a basic Dir4 struct with methods similar to Dir3 and Dir2.

Testing

  • Did you test these changes? If so, how?
    Added unit tests that follow the same pattern of the other Dir structs.
  • Are there any parts that need more testing?
    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.
  • How can other people (reviewers) test your changes? Is there anything specific they need to know?
    Use Dir4 in the wild.
  • If relevant, what platforms did you test these changes on, and are there any important ones you can't test?
    N/A (Tested on Linux/X11 but it shouldn't be a problem)

Copy link
Contributor

Welcome, new contributor!

Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨

Self(value)
}

/// Create a direction from a finite, nonzero [`Vec3`], normalizing it and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Create a direction from a finite, nonzero [`Vec3`], normalizing it and
/// Create a direction from a finite, nonzero [`Vec4`], normalizing it and

@IQuick143 IQuick143 added A-Math Fundamental domain-agnostic mathematical operations S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 16, 2025
.ok_or(InvalidDirectionError::from_length(length))
}

/// Create a direction from its `w`, `x`, `y`, and `z` components.
Copy link
Contributor

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).

Copy link
Contributor

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.

Copy link
Contributor Author

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() {
Copy link
Contributor

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));

Copy link
Contributor Author

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.

@IQuick143 IQuick143 added the D-Straightforward Simple bug fixes and API improvements, docs, test and examples label May 16, 2025
@jnhyatt
Copy link
Contributor

jnhyatt commented May 16, 2025

Should impl StableInterpolate in common_traits.rs. If you're in VS code you can click the "n implementations" lens above the other Dir types to see all the traits they impl. Not sure how to do that without rust analyzer.

@stevehello166
Copy link
Contributor Author

stevehello166 commented May 16, 2025

Should impl StableInterpolate in common_traits.rs. If you're in VS code you can click the "n implementations" lens above the other Dir types to see all the traits they impl. Not sure how to do that without rust analyzer.

Implementing StableInterpolate for Dir4 would require implementing a slerp method which doesn't exist for Dir4 since I haven't found a way to implement it for 4 dimensions.

@jnhyatt
Copy link
Contributor

jnhyatt commented May 16, 2025

Should impl StableInterpolate in common_traits.rs. If you're in VS code you can click the "n implementations" lens above the other Dir types to see all the traits they impl. Not sure how to do that without rust analyzer.

Implementing StableInterpolate for Dir4 would require implementing a slerp method which doesn't exist for Dir4 since I haven't found a way to implement it for 4 dimensions.

Ahhhhhhhh my bad!

@stevehello166
Copy link
Contributor Author

Should impl StableInterpolate in common_traits.rs. If you're in VS code you can click the "n implementations" lens above the other Dir types to see all the traits they impl. Not sure how to do that without rust analyzer.

Implementing StableInterpolate for Dir4 would require implementing a slerp method which doesn't exist for Dir4 since I haven't found a way to implement it for 4 dimensions.

Ahhhhhhhh my bad!

No problem. I didn't even know that this existed until you mentioned it.

@ickshonpe
Copy link
Contributor

ickshonpe commented May 16, 2025

Should impl StableInterpolate in common_traits.rs. If you're in VS code you can click the "n implementations" lens above the other Dir types to see all the traits they impl. Not sure how to do that without rust analyzer.

Implementing StableInterpolate for Dir4 would require implementing a slerp method which doesn't exist for Dir4 since I haven't found a way to implement it for 4 dimensions.

We can use the formula from here:
https://en.wikipedia.org/wiki/Slerp

It might need to fall back to a lerp or something for very close values.

Copy link
Contributor

@ickshonpe ickshonpe left a 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.

@stevehello166
Copy link
Contributor Author

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.

@ickshonpe
Copy link
Contributor

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 acos(dir_a.dot(dir_b)) I think.

@stevehello166
Copy link
Contributor Author

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 acos(dir_a.dot(dir_b)) I think.

Oh. Well now I feel like an idiot. Thanks for pointing that out. I'll get a commit for that prepared right away. :)

#[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));
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Comment on lines 996 to 997
let p0 = (sin((1.0 - s) * angle) / sin(angle)) * self;
let p1 = (sin(s * angle) / sin(angle)) * rhs;
Copy link
Contributor

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)

Copy link
Contributor Author

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.

Copy link
Contributor Author

@stevehello166 stevehello166 May 18, 2025

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.

@@ -1323,4 +1363,34 @@ mod tests {
);
assert!(dir_b.is_normalized(), "Renormalisation did not work.");
}

#[test]
fn dir4_slerp() {
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@IQuick143 IQuick143 left a 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.

@Vrixyz Vrixyz added S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 17, 2025
// 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);
Copy link
Contributor

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.

// 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;
Copy link
Contributor

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.

if angle.is_nan() {
return self;
}
if angle == 0.0 || angle == PI {
Copy link
Contributor

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.


assert_relative_eq!(
Dir4::X.slerp(Dir4::NEG_X, 0.5),
Dir4(Vec4::new(8.940697e-8, 0.0, 0.0, 0.0)),
Copy link
Contributor

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.

Copy link
Contributor

@IQuick143 IQuick143 left a 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.

@stevehello166
Copy link
Contributor Author

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

@IQuick143 IQuick143 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Waiting-on-Author The author needs to make changes or address concerns before this can be merged labels May 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Math Fundamental domain-agnostic mathematical operations D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a Dir4
6 participants
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy