-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
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
Fix Basis is_orthogonal
and is_rotation
methods, add is_orthonormal
#83229
Conversation
I think that is expected. According to wikipedia: I wonder if that's an english specificity though. In french we differentiate "orthonormal" from "orthogonal", where one means unit vectors and the other does not. |
@groud Orthogonal and orthonormal are not the same thing. Orthonormal means normalized orthogonal. Orthogonal does not need to be normalized. We could add a new method for is_orthonormal. But anyway, I still need a real is_orthogonal method for the glTF transform logic. The Wikipedia article you read is not correct. The article for Orthogonality does not mention being normalized: https://en.wikipedia.org/wiki/Orthogonality |
Well, that's what I mentioned, I was not sure about the definition in English. Here is the article: https://en.wikipedia.org/wiki/Orthogonal_matrix What I understand from the current If those are indeed different, then I think it would be good to add an |
@groud Actually, these methods are not exposed, so it's not a compatibility-breaking change. I have opened a request for Wikipedia to rename that page. There are a lot of people on the talk page also confused by this, because the current page incorrectly uses the term "orthogonal". |
5f68bc7
to
d9a3563
Compare
I updated the PR to add |
is_orthogonal
and is_rotation
methodsis_orthogonal
and is_rotation
methods, add is_orthonormal
Yeah I can see that too, and it feels like the general consensus isn't here. What I suggest if you need the two versions of the function, is that we add a comment explaining the difference between the two, and explain the naming choice we made here. That should avoid any future confusion. With that comment added I think the PR should be good to merge. |
@groud Also note, while Wikipedia has an article for "Orthogonal matrix" that is contested, it has this other article for "Orthonormal basis", and our type in Godot is Basis. https://en.wikipedia.org/wiki/Orthonormal_basis Also, if the term "orthogonal" is confusing, we could instead call this method As for writing a comment explaining these, I think that would be better to do in documentation after we expose these. For the moment I decided against exposing them in this PR in favor of just doing internal fixes, but we could expose them if that's desired (either in this PR or another PR 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.
Hmm, I'd prefer keeping "orthogonal". I think I'd rather go for using a widespread term and explain the meaning we give to it than using an uncommon term. But well, I am not strongly against it either.
Yeah, I agree that if it is exposed it's better to have this documented in the documentation. But if we choose not to expose them, then I think a comment is welcome to avoid any confusion. That would help prevent any future PRs claiming they would "fix" this function because their author would not agree with the meaning we gave it. Now I am not against exposing them, I guess they can be useful functions. Though I don't think anyone asked for it yet so that might be a bit against our "the need shall come first" policy. |
a7d46ac
to
33b53a0
Compare
Orthogonal etymology seems pretty clear:
Unfortunately if one influential person or teacher misinterprets a term, it can get regarded as canon. Personally I always go with what the derivation says. 😁 🍿 |
33b53a0
to
83ebaf2
Compare
The end state of these changes makes sense to me! However, if folks have existing code using The problem with trying to do this in a non-breaking way is the unfortunate naming if we leave |
@dsnopek Note that this method is not exposed to GDScript or GDExtension, so it only affects engine code, or third-party modules. |
Oh, ok! Nevermind about that then :-) |
const Vector3 x = get_column(0); | ||
const Vector3 y = get_column(1); | ||
const Vector3 z = get_column(2); | ||
return Math::is_equal_approx(x.length_squared(), 1) && Math::is_equal_approx(y.length_squared(), 1) && Math::is_equal_approx(z.length_squared(), 1) && Math::is_zero_approx(x.dot(y)) && Math::is_zero_approx(x.dot(z)) && Math::is_zero_approx(y.dot(z)); | ||
} |
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.
What are the pros and cons of doing this versus the old transpose + identity check? Is one faster than the other / handle more cases etc? 🤔
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 old check ensured that
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.
That said, I question whether this method has any practical applications. At least, in math and physics, I have never needed to check whether a matrix was orthogonal in this sense, whereas checking whether or not a matrix is orthonormal (ie: a rotation) or conformal (ie: a uniformly scaled rotation) is fairly common. It's perhaps worth noting that
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 need a real is_orthgonal method in the GLTF code. That's why I opened this PR.
I am using this method to check if a Basis is decomposable into TRS.
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.
By TRS do you mean translation, rotation, scale? If so, I believe every matrix admits such a decomposition. Ignoring the T part for now so we just are just dealing with the square (ie 3 x 3) part of the matrix, every square matrix can be written as a rotation times a scale according to the polar decomposition https://en.m.wikipedia.org/wiki/Polar_decomposition. You can then take the square matrix and combine it with the translation to recover the original transformation. In case I'm mistaken, did you run into an example in the wild of a matrix that was not decomposable in the way you wanted?
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.
Based on another comment, it does seem like "scale" in this context is being used to mean a diagonal matrix, not a diagonalizable matrix (or more precisely a positive-semi-definite matrix) as I initially presumed it to mean. I would be very careful with this terminology as it suggests that stretching an image along say, the 45 degree line is not a "scale" transformation, but stretching an image along the 0 degree or 90 degree line is.
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 not sure what you mean by "diagonal in some orthonormal basis". Let's go with a simple example:
This cannot be decomposed into rotation and scale.
Proof by degrees of freedom: In 2D, rotation is a single angle number, and scale is 2 numbers, so a total of 3 numbers. A 2x2 matrix has 4 numbers. So there is necessarily one degree of freedom that cannot be represented by rotation and scale. For 3D, rotation has 3 degrees of freedom (Euler angles), and scale also has 3, so 6 total numbers. A 3x3 matrix has 9 numbers. So there are 3 degrees of freedom in the matrix that cannot be represented by rotation and scale.
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 understand now. The discussion is perhaps related to this post? https://math.stackexchange.com/questions/2022588/multiplying-trs-affine-transformations
When I say a matrix
In math and physics, there is no "preferred" axis/orientation to look at things in, so we tend to care more about whether a matrix is diagonal in some basis rather than diagonal in the particular basis we happened to choose. That said, maybe things are different in computer graphics as the fact that your monitor is oriented a particular way does mean there is a preferred axis.
I agree that if by "scale" you mean a diagonal matrix, then you can't decompose every matrix as R S. However, I'm doubtful how useful such a representation is anyway. As that post on stack exchange shows, there isn't a straightforward way to update such a representation after a new transformation.
Part of the reason I'm harping on this is that Godot seems to try to keep it's APIs pretty lean, so I'd want to be sure this method deserves it's spot on the API.
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.
How things relate to the monitor or computer graphics is not the problem here. The problem is with what we are calling rotation and scale.
You are thinking of scale as a diagonal matrix, but I am thinking of scale as a Vector3. The other values aren't 0, they don't exist. You can convert a Vector3 scale into a diagonal matrix, but a scale is not the same as a scale matrix, at least this is how it works in game engines. If a matrix can't be losslessly converted to a scale Vector3 (except for floating point error) then it's not a pure scale matrix and therefore the matrix contains more than just scale.
Similarly, you are thinking of rotation as a matrix. A rotation can be represented as Euler angles, a normalized Quaternion, and a matrix, and you can convert between these, but a rotation is not the same as a rotation matrix. If a matrix can't be losslessly converted to Euler angles (except for floating point error) then it's not a pure rotation matrix and therefore the matrix contains more than just rotation.
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.
Looking around a bit, it does seem there are at least a few contexts where "scaling" does seem to refer to scaling the x, y, and z axes specifically. I'm kind of annoyed people take it to mean that, but I'll defer to that definition if that is how people commonly understand it, especially in game engines.
As for why I'm annoyed, it's because I would consider a transformation like this (produced from https://web.ma.utexas.edu/users/ysulyma/matrix/):
a "scale" because you obtain the transformation by stretching everything aligned with the 45 degree axis by a factor of 2.
Personally, I would think of the "scale" of a transformation as the eigenvalues of its corresponding transformation matrix. That said, it's not a hill I'm willing to die on :)
Barring the question about whether to have the transpose check or the new one for One problem is that if you do define The alternative if hotly disputed as aaron says is to remove all mention to |
I was just worried there would be induced confusion if we did not state clearly (in a comment) which definition of the names we used. But I think it's the right decision to change it, I was just worried about it being a source of confusion for those who would expect "orthogonality" to imply normalization. Right now, I think the PR is good in its current state. The comments in the .cpp are enough, and the |
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.
Overall LGTM.
83ebaf2
to
6ca97b4
Compare
6ca97b4
to
7ee2737
Compare
Thanks! |
@aaronfranke The name "orthogonal" comes from Lie groups. There is no confusion about it, and it is not contested it is only in your mind. An orthogonal matrix M is a matrix representation of an element of the orthogonal group Please don't change long established mathematical definitions arbitrarily just because you don't know group theory and get confused by it. Ironically, any one who knows what an orthogonal matrix is going to be confused by your implementation. @reduz @akien-mga I recommend reverting this commit. It is based on a misunderstand of what orthogonal matrix is. This commit changes the correct implementation of |
@tagcup3 An orthogonal basis is a basis which has orthogonal vectors: https://en.wikipedia.org/wiki/Orthogonal_basis Even if you perfectly argue your case, the solution would not be to revert. We still need a method to check if the basis vectors are orthogonal/perpendicular, so at most you could rename this to
It is not unnecessarily complicated. The original method was wrong, because it did not handle all cases. |
If you need a function to determine whether a polar decomposition of the matrix is possible or not, you should open a separate PR for it. For the function that you are looking for, "perpendicular matrix" is your words again and is not a good name. I suggest
You talk a lot but without any substance. What is wrong? If you are referring to scaling matrices in your original post as "all cases", those are not orthogonal matrices, and the original |
@tagcup3 I see where you are coming from. I have a math/physics background and the naming also bothered me initially. However, @aaronfranke explained their reasoning very well in a discussion you can read above. Personally, I think |
@tagcup3 I apologize, I did some testing, I had a few things mixed up before, some of my statements were not correct. For Now, since both produce the correct behavior, we need to figure out which is faster. The code I have in this PR uses fewer approximate equality checks so I would assume it's faster. After benchmarking with looping the test cases one million times, running the old code took 381851 microseconds and the new code took 357442 microseconds, so the code in this PR is 6.4% faster (Apple M1 Pro arm64 macOS). I also tested with 10 million iterations with random normalized data (1183076µs -> 1093664µs, this PR is 7.5% faster in this case). and random non-normalized data (925739µs -> 820930µs, this PR is 11.3% faster in this case). In all cases the code in this PR is faster. So it's a good improvement. When I mentioned incorrect behavior what I was thinking about before is the As for TL;DR: There will be no reverts. Everything is good in master except for bikeshedding about the naming of this method. If anyone proposes to change it to |
By the way, are you planning to use this in a pattern like if (is_orthogonal()) {
get_scale()
} or something? If speed is a concern, you might want to consider that |
If you write Another point is, I noticed a test case for |
Does a conformal map have to be invertible? Based on the Wikipedia article for Conformal Map, there seems to be different conventions https://en.wikipedia.org/wiki/Conformal_map."
In other words, it seems like some authors require For that reason, it seems like the "correct" choice in a computational setting is whichever one has simpler/faster code. |
@nlupugla My use case is in the GLTF export code, which is extremely non-performance-critical. It is much more important to have readable code that can be easily looked over to ensure the behavior is correct. Saving a few nanoseconds is not important here, it would be premature optimization for a non-hot code path. @tagcup3 For my uses, it does not really matter what the result of Please also keep in mind that for game engines, being 100% accurate to the original math definitions is not the goal. Instead, being useful is the goal. This is why for example we have |
@nlupugla Conformal maps need to be invertible (non-zero In physics, the rotation part is typically special orthogonal group @aaronfranke Too many false dichotomies in your arguments. There is "not 100% accurate" and there is "conceptually incorrect, incoherent and broken". If you let Just fix it instead of coming up with random excuses. |
@tagcup3 Do you have any references you can point to that suggest the convention I'm referring to requires non-zero If letting Also, it's nice to chat with other people that are passionate about getting these definitions right. Let's try to remember we're all on the same team here. People might be more inclined to heed your arguments if you use a bit of a warmer tone <3 |
@tagcup3 As I've stated, what happens for this edge case is not important to me. You are welcome to propose an alternative implementation, but note that high performance for 99.999% of use cases is not worth sacrificing for purity with an arbitrary mathematical definition not based on practical use cases. |
I noticed that the behavior of these methods was not correct for all inputs. This PR adds unit tests for these methods and fixes the methods. If desired, I could expose them and write docs too.
The
is_orthogonal
method was not working for scaled inputs. The new changes also greatly simplify the code, we only need 3 dot products to check if the Basis is orthogonal, because the dot product of orthogonal vectors is zero. Without these changes, these unit tests would fail:The
is_rotation
method was not taking into account squeeze matrices (a special form of non-uniform scale where the volume is preserved but the scale changes). Admittedly this was an edge case, but still, now it's fixed. Without this fix, this unit test would fail:If anyone is wondering, here is the truth table of the Basis validation methods as of this PR: