-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Introduce natural 3D rotation with mouse #28290
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
Introduce natural 3D rotation with mouse #28290
Conversation
This what the mouse rotation looks like on the elev_azim.mov(you can see the figure sometimes rotates, when you'd think it should tilt; or it moves in the opposite direction as the mouse). arcball.mov(Now the movement is much more consistent: the figure tilts up/down, left/right if you move the mouse up/down, left/right near the center; near the edge, the mouse rotates the figure). |
I don't have any problem with the proposed change, but wonder if others would. If we use a private Quaternion class, please name Is a "cardan" angle the same as an Euler angle? Why not use the more common name? So far as I can see in numpy-quaternion, they do not use the term "cardan". |
No, it is different.
Because it is not the same thing. Euler angles are not simply a more common name, these are something else altogether.
They do have something to say about Euler and Tait-Bryan and other such angles, though - https://github.com/moble/quaternion/wiki/Euler-angles-are-horrible. The documentation of |
I'd think so, considering that all that we really need is 1) quaternion multiplication, and 2) our funny angle<>quaternion conversion, which is not part of numpy-quaternion, so it would have to be added anyway.
That would be a great idea, but it is for another PR. |
This is really great, a much needed improvement. I agree with @jklymak that the quaternion class should be private, but wouldn't bother with adding an external dependency for a class less than 100 lines long. |
Thank you :)
I'll try and stash it away then.
Well it comes for free when using numpy, the only part where it is actually noticeable is in
Simpler? You mean, you'd prefer your multiplication to be
rather than
Why would you consider the former simpler? A whole bunch of + and - signs that you have to take at face value (and indeed, some people refuse to, and define their quaternions with ijk=+1, "JPL convention" at NASA, apparently...)? |
I would probably write w0, x0, y0, z0 = self.w, self.x, self.y, self.z
w1, x1, y1, z1 = other.w, other.x, other.y, other.z followed by formulas that directly match those at https://en.wikipedia.org/wiki/Quaternion#Hamilton_product, but again, I don't really mind. I can definitely see why you consider the scalar/vector representation more meaningful. A side motivation for switching to scalars only is that numpy arrays tend to have enormous overheads (compared to python scalars, not to numpy scalars) for such small sizes, but performance hardly matters here. |
Good to go now, hopefully... |
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 want to echo the other comments that this is great work and a welcome change! Left a number of comments on some details, but I think the structure of how this is implemented works well.
One additional suggestion I want to make is that I personally see the new behavior as strictly superior, but I can imagine some implementations where someone would want the old control scheme. I would suggest that we keep the old behavior as an option, accessible through an rcparam
to allow users to switch. But I think this new mode is fine to make the default.
Yes, I agree with this.
I don't think this is so important as long as it's well documented. Could go either way, I think it's fine as-is and we can always change it later for a private class. |
I would also consider adding two more methods that might be useful in the future, though maybe it doesn't make sense to add until/unless we need them:
|
That's right - maybe later. Or if we would make the class public instead of private, then it might serve a purpose. |
The idea has crossed my mind too. Presumably, it would be straightforward to implement. I already was adding more options (Holroyd's trackball, in addition to Shoemake's; also, you could consider the 'original' Shoemake's, at double the rotation rate; etc., etc.) But just because we can, does not mean we should. Maybe it is better to be brave, and make a choice (instead of getting lost in feeping creaturism). |
+1 for later. If we don’t need it now, don’t bother with it now. |
6c2267b
to
cdcc9d8
Compare
It should be good to go again (provided we can do without an rcparams for the trackball). |
lib/mpl_toolkits/mplot3d/axes3d.py
Outdated
current_point = np.array([self._sx/w, self._sy/h]) | ||
new_point = np.array([x/w, y/h]) | ||
current_vec = self._arcball(current_point) | ||
new_vec = self._arcball(new_point) |
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.
With above changes, this can be simplified to
current_point = np.array([self._sx/w, self._sy/h]) | |
new_point = np.array([x/w, y/h]) | |
current_vec = self._arcball(current_point) | |
new_vec = self._arcball(new_point) | |
current_vec = self._arcball(self._sx/w, self._sy/h) | |
new_vec = self._arcball(x/w, y/h) |
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.
Yes.
I'll try and combine the changes and rebase.
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.
Seems something went wrong with that...
- Addresses Issue matplotlib#28288 - Introduces three-dimensional rotation by mouse using a variation on Ken Shoemake's ARCBALL - Provides a minimal Quaternion class, to avoid an additional dependency on a large package like 'numpy-quaternion'
- makes axes3d.Quaternion a private _Quaternion class - shortens as_cardan_angles() - adds two extra tests to test_axes3d::test_quaternion(): from_cardan_angles() should return a unit quaternion, and as_cardan_angles() should be insensitive to quaternion magnitude - updates "mplot3d View Angles" documentation (the mouse can control both azimuth, elevation, and roll; and matlab does have a roll angle nowadays) - put in a reference to quaternion multiplication using scalar and vector parts (wikipedia) - rename class method that constructs a quaternion from two vectors to `rotate_from_to()` - clarify docstring: "The quaternion for the shortest rotation from vector r1 to vector r2" - issue warning when vectors are anti-parallel: "shortest rotation is ambiguous" - construct a perpendicular vector for generic r2 == -r1 - add test case for anti-parallel vectors - add test for the warning - add reference to Ken Shoemake's arcball, in axes3d.py - point out that angles are in radians, not degrees, in quaternion class docstrings - in test_axes3d, add an import for axes3d._Quaternion, to avoid repetition - add Quaternion conjugate(), and tests for it - add Quaternion norm, and tests - add Quaternion normalize(), and tests - add Quaternion reciprocal(), and tests - add Quaternion division, and tests - add Quaternion rotate(vector), and a test
- change argument from 2 element numpy array to x, y - add type hints
654f8a3
to
a0c22ed
Compare
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've added a line break back in that was lost and made the line too long.
Looks good now. Anybody can merge after CI pass. - Please squash before merging.
@MischaMegens2 Thanks for your contribution and the stamina to keep going through the somewhat lengthy review process. 🎉
It did get better :) |
Not sure about the CI - is there still an issue? |
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 looks like it’s in a good spot now to me as well! Thanks @MischaMegens2 for accommodating our reviews.
I’ve been traveling the past 2 weeks so haven’t been able to actually check out the branch and try the behavior, but the videos look great and I’m excited to do so.
* Natural 3D rotation with mouse - Addresses Issue matplotlib#28288 - Introduces three-dimensional rotation by mouse using a variation on Ken Shoemake's ARCBALL - Provides a minimal Quaternion class, to avoid an additional dependency on a large package like 'numpy-quaternion' * Suggestions from reviewers - makes axes3d.Quaternion a private _Quaternion class - shortens as_cardan_angles() - adds two extra tests to test_axes3d::test_quaternion(): from_cardan_angles() should return a unit quaternion, and as_cardan_angles() should be insensitive to quaternion magnitude - updates "mplot3d View Angles" documentation (the mouse can control both azimuth, elevation, and roll; and matlab does have a roll angle nowadays) - put in a reference to quaternion multiplication using scalar and vector parts (wikipedia) - rename class method that constructs a quaternion from two vectors to `rotate_from_to()` - clarify docstring: "The quaternion for the shortest rotation from vector r1 to vector r2" - issue warning when vectors are anti-parallel: "shortest rotation is ambiguous" - construct a perpendicular vector for generic r2 == -r1 - add test case for anti-parallel vectors - add test for the warning - add reference to Ken Shoemake's arcball, in axes3d.py - point out that angles are in radians, not degrees, in quaternion class docstrings - in test_axes3d, add an import for axes3d._Quaternion, to avoid repetition - add Quaternion conjugate(), and tests for it - add Quaternion norm, and tests - add Quaternion normalize(), and tests - add Quaternion reciprocal(), and tests - add Quaternion division, and tests - add Quaternion rotate(vector), and a test * Update axes3d.py's arcball - change argument from 2 element numpy array to x, y - add type hints * Update doc/api/toolkits/mplot3d/view_angles.rst --------- Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Got to play around with this finally, it feels good! Without digging into the math to double check if the naming is right, it seems like this is matching up with "trackball" controls rather than "arcball" controls. See here for an example:
Personally I prefer the latter, since moving the mouse back the original point on the screen with arcball controls will return the view to the original view, whereas the trackball controls will seem to drift (ie, click and spin the mouse in a circle, and the view will precess in one direction or the other). But I know both are pretty common. @MischaMegens2 thoughts? |
Glad that you liked it...
Well most certainly it is not matching up with the "trackball", the matplotlib control is indeed an "arcball". Just try the examples once more, paying attention to how it responds, rather than to how it looks superficially (the matplotlib arcball does not show the circles, like the threejs arcball): In particular: drag the mouse tangentially near an edge or corner; the trackball will still change the 'tilt' (similar to what happens near the center of the figure), whereas an arcball will rotate the scene ('roll').
Well the philosophy is different. One might argue that that 'precession' of the trackball is a feature, rather than a bug - a feature that is much needed to be able to adjust the roll at all. It is just unfortunate that the figure then rotates in the opposite direction of the circular motion of the mouse... As for the math of it: the 'arcball' as implemented does not strictly follow the arcball recipe of Ken Shoemake; Ken's arcball uses vectors on the (unit) sphere to directly define a rotation quaternion q = v1 v2', and then rotates the figure using v_rotated = q v_original q'. As a consequence, it rotates by twice the angle subtended on the sphere. (This is particurlarly noticeable when adjusting the 'roll', near the edge.) In contrast, the matplotlib arcball takes a square root: q = sqrt(v1 v2'), avoiding the double-speed rotation. |
The behavior I was trying to get at isn't so much the tilt vs roll at the edge of the screen (doesn't matter so much IMO), but the property that during a single mouse click, returning to the original position on the screen will restore the original view. The threejs arcball has this property, but the matplotlib arcball doesn't right now and precesses: 2024-06-15.19-01-52-1.mp4 |
Aha! I think this is an unfortunate consequence of the sqrt() that I threw in against the double-speed. I can see that this might not be desirable, and that Ken's original arcball has its advantages. |
Think it's up to you if you want to change the default behavior or add rcparams! Since this feature hasn't been released in v3.10.0 yet, there's pretty wide latitude to make changes before that release. Yeah, new issue & PR I would think. |
PR summary
PR checklist