-
-
Notifications
You must be signed in to change notification settings - Fork 21.6k
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
Add normalize_and_get_length() to Vector2 and Vector3 #56567
Conversation
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.
Note: This would only be for internal use. normalized
is what's exposed, and it returns a Vector2
.
core/math/vector2.h
Outdated
real_t l = x * x + y * y; | ||
if (l != 0) { | ||
l = Math::sqrt(l); | ||
x /= l; | ||
y /= l; | ||
return l; | ||
} | ||
return 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.
Could be simplified:
real_t l = x * x + y * y; | |
if (l != 0) { | |
l = Math::sqrt(l); | |
x /= l; | |
y /= l; | |
return l; | |
} | |
return 0.0; | |
real_t l = x * x + y * y; | |
if (l != 0) { | |
l = Math::sqrt(l); | |
x /= l; | |
y /= l; | |
} | |
return l; |
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.
Yup sure. 👍
I was intrigued to find in godbolt that the assembly is slightly different.
Even adjusting for the literal issue I mentioned in #56396 (and is probably addressed in a separate PR if we get some agreement on it), I wonder if it is trying to account for signed zero. We'd have to time them to see which was "better", I don't expect there's a lot in it! 😁
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.
-0.0
would be the only case that is different, so that has to be it. I also checked NaN but that should enter the if
and then explode things there so it wouldn't be affected by this.
PR Meeting: Add |
c1ccb95
to
3a3e1ea
Compare
3a3e1ea
to
288b93c
Compare
core/math/vector2.h
Outdated
@@ -241,6 +242,17 @@ _FORCE_INLINE_ bool Vector2::operator!=(const Vector2 &p_vec2) const { | |||
return x != p_vec2.x || y != p_vec2.y; | |||
} | |||
|
|||
real_t Vector2::normalize_and_get_length() { | |||
real_t lengthsq = length_squared(); | |||
if (lengthsq >= (real_t)CMP_EPSILON) { |
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.
Should it be CMP_EPSILON
or CMP_EPSILON2
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.
Ah yes, I'm expecting some discussion on the epsilon, and will need a bit of testing. I saw CMP_EPSILON2
but wanted to make sure it is ok for this use with 32 bit real_t
. But you are correct we are using squares here.
It has to be a value that is large enough that when sqrt
ed afterwards, it still provides accuracy when used as the divisor. So I think a bit of testing will be good to check this in practice rather than going straight for the smaller CMP_EPSILON2
(without knowing how it was tested).
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'm fully agnostic and the only difference with 64 bit would be the lower limit on magnitude
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.
32 / 64 bit epsilon differences can be quite difficult.
Last time we discussed it (that I remember), reduz was in favour of keeping the 32 bit epsilons when compiling as 64 bit. This loses out on some potential accuracy in 64 bit, but if you use different epsilons you open up a massive surface of difficult to find bugs. Epsilon bugs are some of the worst imo. 😁
Anyway I'm doing some empirical testing on the epsilon as we speak. 👍
Ogre looks like it uses 1e-8f
, which is larger than CMP_EPSILON2
(which is 1e-10f
).
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.
Epsilon bugs are some of the worst imo.
Agreed
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.
Ok after (I hope) some testing, I'm satisfied that CMP_EPSILON2
should be okay at 32 bit, so I've pushed a version with this epsilon.
I tested in gdscript then in a c++ testbed. Of course there's always the chance I messed up, it's pretty easy to mess up, with math precision. 😁
917b24e
to
dec5096
Compare
<return type="float" /> | ||
<description> | ||
Scales the vector to unit length, and returns the original length. | ||
If the original vector was too small to normalize, the length returned is [code]zero[/code]. |
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 would be the only method exposed to GDScript that mutates the struct. That's a bit odd. Is this method necessary to expose, or is it intended to be an internal helper for the C++ code?
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.
There are some issues with it because of access issues with GDScript, primitive types are non-mutable when returned, including as properties, GDScript passes them by copy not reference, unless that has changed, as in mutating functions cannot affect them
See #70995
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.
If you can think of an easy way to return two values efficiently from bound functions, I'm happy to modify the PR.
If we don't want it to mutate the struct, we need to return the new Vector and the length (or the new Vector and success or failure).
Originally this was non-bound, but it is now also a potential candidate solution to the problem in #74852, and whichever solution we go with needs to be available from script.
Another non-mutating alternative would be a normalized()
function that returns either the normalized vector or failure (maybe a Vector or NULL? can that work?).
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.
Perhaps then for binding returning a variant that is either the normalized value or NULL is the way to go try_normalized()
or something like that?
I can modify this particular PR to have this function unbound as before, but will be good to get some discussion going on how to solve the gdscript problem.
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.
It could return an array or dictionary, somewhat cumbersome and inefficient but it gets it done, also I like try_normalized
Length is calculated as part of the normalize routine. This is often desired, and also can be used to flag the condition where the Vector was too small to normalize (by returning zero length).
dec5096
to
e7ba712
Compare
Closing in favour of #74912 , as it appears that modifying in place isn't good for bound functions. |
Length is calculated as part of the normalize routine. This is often desired, and also can be used to flag the condition where the Vector was too small to normalize (by returning zero length).
Helps address #74852
Notes
Most engines I've worked on have returned the length from normalize, as this can be a useful value in a lot of cases, and should cost nothing from c++ if not used (as it should be optimized out).
normalize()
.normalized()
bound function) the function is bound, in order to be available from script etc.Vector3::normalize()
sets all values to zero if length is zero (curiouslyVector2
does not do this). I'm presuming this is to get a standard value for later comparisons because of the different bit representations near zero. This didn't seem appropriate to do in this PR with an epsilon, but can change if desired.This can be done longhand which is what I normally end up doing, e.g.:
but the form enabled by the PR is more concise and less error prone (especially epsilons can be standardized in the normalize function):
or simply
(This is one of those rare occasions we can do an exact floating point comparison)