-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix 3D rotation precession #28823
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 3D rotation precession #28823
Conversation
29e4c75
to
343b3dc
Compare
Before, showing that returning to the original mouse position has a view that has precessed: 2024-09-15.10-32-43-1.mp4After, showing that returning to the original mouse position recovers the original view: 2024-09-15.10-31-13-1.mp4 |
9939481
to
e497c77
Compare
I took a quick look at it and I don't think it is proper. Both proposed modifications miss the mark. The second proposed modification is: As for the movie clips, purporting to show that returning to the original mouse position now recovers the original view: you might well be deceived by the shrinking of the arcball - in the clips, the mouse is already somewhat near the edge, and with the shrinking in the 2nd clip, you probably ended up outside the arcball proper, on its border, and consequently, rotations are around a fixed axis (pointing towards you). Of course you return to the original view then. Would you not rather wait for #28408 (comment), instead of trying to make changes haphazardly in a rush, without really understanding what you are doing? |
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 don't really see the benefit.
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
azim = np.arctan2(2*(-qw*qz+qx*qy), qw*qw+qx*qx-qy*qy-qz*qz) | ||
elev = np.arcsin( 2*( qw*qy+qz*qx)/(qw*qw+qx*qx+qy*qy+qz*qz)) # noqa E201 | ||
elev = np.arcsin(np.clip(2*(qx*qz + qw*qy), -1, 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.
But why (the np.clip)? I have a hard time imagining how you'd possibly ever get the arcsin argument out of range...
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 were floating point round-off errors on the macos github CI runners, which was resulting in values inside the expression of 1.000000001 (for example) and leading to an error being thrown here. I'm not sure of a cleaner robust way to do this than clipping after norming above, but am open to suggestions.
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.
All right, touché. I apologize for being so curt yesterday (it was not a good day for me). Let's see if we can make it work as good as we can.
Is there any chance we can extract the qw, qx, qy, and qz from the macos runners where the error shows up? And some of the intermediate results? Hopefully that will give some insight as to why we're confronted with this. (What hardware is used to do the calculations, what processor?)
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.
Floating point inaccuracies are something that pop up fairly frequently for matplotlib's tests, and unfortunately we won't ever get perfectly reproducible results across architectures. It's why we use assert_allclose
a lot of places in testing. That doesn't work in cases like this where you are close to a discrete threshold however, hence the clipping. The good news is that in 99% of cases for matplotlib, the end result is visual so floating point inaccuracies don't matter to the end user.
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.
Well I understand the expediency of adding an np.clip()
and moving on, it gets this job done; but I thought we had the IEEE 754 Standard for Floating-Point Arithmetic for quite a while now, to help make things like this reliable and portable. And it is not like we're doing something arcane: just addition, multiplication and division, and not even a whole lot of it. So I'm somewhat surprised by the incompatibility (why only on macos?). Seems you're on to something.
@MischaMegens2 thanks for the review.
In my ears, this comes across a bit harsh. I'm sure this PR was well intended. 3.10 will be released in October and it would be great to have the rotation fixed there. Asking you on the status per your above comment would have been the right first step. But I have to admit, I myself had forgotten your comment on planning to fix this. So to get things straight: We'd very much welcome if you could do the fix. Is there anything we can help you with to move forward? - You said you wanted to wait for #28395. My interpretation of the status is that we're waiting on your rewrite there? But given that it has become documentation-only, can we decouple this from rotation fix. I'd prioritize the fix higher. |
Hi @MischaMegens2, addressing the points you have brought up:
This is on purpose, to restore the original Shoemake arcball recipe by undoing the "sqrt" that was done to the rotation quaternion as you explained here #28290 (comment) (quoted below). This could be implemented in other ways but is necessary for the fixed behavior.
This scaling makes it so that when moving your mouse from the origin in pure x or y directions, the view angle change exactly matches the original behavior you added with the "sqrt" of the rotation quaternion. You can see this in the unchanged tests. This was done with the purpose of "avoiding the double-speed rotation" you called out. The
I was trying to keep the math inside
Nope, this works whatever your starting position and however you move your mouse. I encourage you to try it out locally!
My understanding of the proposed change was that it was to add the rcparam, not necessarily to fix the default rotation behavior. With 3.10 releasing soon and this new rotation mode going out to users for the first time, I thought a small fix may be appropriate since larger changes can take a while to review. But it's not set in stone, and getting your status and opinion is why I asked for your feedback. If that larger MR is ready I'd be happy to review it and we can discuss whether it makes sense to have this smaller one as a stepping stone at all.
We expect all participants, regardless of their experience or position, to engage respectfully and assume good faith. Please focus on the technical merits of the proposal rather than making personal accusations. All contributors to matplotlib are expected to abide by our code of conduct. |
Fix arcsin domain error Revert to simplified trig Linting tests
a521ef7
to
f059296
Compare
Hi @timhoffm, Hi @scottshambaugh :- It also makes the trackball/arcball size an rcparameter; originally, the size was hard-coded, and set a tad large; and then this PR actually grew it even larger (rather than shrinking, as I mistakenly thought initially). (This made the arcball almost work as a trackball.) What is still absent is the introduction of an In any case, could you take a look and let me know what you think about it? |
Sorry, I'm out of this discussion. As stated I'm a rare and naive 3D user and want the rotation to "just work". From that perspective (which I assume is also that of many users) I would argue that configurability is not necessary and we could have just one fixed algorithm. There may be aspects that I overlook, which make configuration neccessary. But that's that's exactly the point why I leave the discussion to you experts. |
Hi @scottshambaugh, It's up to the two of us then now, it looks like... |
After reviewing #28841, I think it would be most preferable for that to go in instead of this one. But if it's not ready before the v3.10 merge point, I think this PR should go in as a stop-gap to improve the default behavior in the new release. |
Add np.clip() to avoid floating point round-off errors on the macos github CI runners, see also matplotlib#28823 (comment)
With #28841 merged in, this PR is no longer necessary. Closing. |
PR summary
#28290 introduced "arcball" style rotation to 3D plots, which IMO is a significant enhancement over the previous rotation style. However, there was a change to the original arcball implementation to slow down the speed of rotation, which had the inadvertent side effect that it caused the plot to precess when moving the mouse around. This removed the desirable property that returning the mouse to the same point on the screen restored the original view. See the comments at the end of that issue for some example videos.
This tweaks the math to have the best of both worlds - a 1:1 mapping between screen position and view angle during a single mouse click, and also scaling down the "double angle" high speed rotations to a "single angle" speed.
@MischaMegens2 could you please check the math and weigh in on if you think this makes sense to change for the default?
I think that since this behavior is new for 3.10, it makes sense to prioritize getting it in. The what's new note shouldn't have to change.
See also some more discussion in #28408.
Original arcball paper: https://graphicsinterface.org/wp-content/uploads/gi1992-18.pdf
PR checklist