From f0592969b64ddc1bbdeb8a4dd1d7a405446a0432 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Sat, 14 Sep 2024 21:56:07 -0600 Subject: [PATCH] Fix 3D rotation precession Fix arcsin domain error Revert to simplified trig Linting tests --- lib/mpl_toolkits/mplot3d/axes3d.py | 12 ++++++++---- lib/mpl_toolkits/mplot3d/tests/test_axes3d.py | 11 +++++------ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index 5d522cd0988a..10f693ace0cf 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1568,10 +1568,11 @@ def _on_move(self, event): q = _Quaternion.from_cardan_angles(elev, azim, roll) # Update quaternion - a variation on Ken Shoemake's ARCBALL - current_vec = self._arcball(self._sx/w, self._sy/h) - new_vec = self._arcball(x/w, y/h) + scale = np.sqrt(2)/2 # slow down the rate of rotation + current_vec = self._arcball(self._sx*scale/w, self._sy*scale/h) + new_vec = self._arcball(x*scale/w, y*scale/h) dq = _Quaternion.rotate_from_to(current_vec, new_vec) - q = dq * q + q = dq * dq * q # Convert to elev, azim, roll elev, azim, roll = q.as_cardan_angles() @@ -4020,11 +4021,14 @@ def from_cardan_angles(cls, elev, azim, roll): def as_cardan_angles(self): """ The inverse of `from_cardan_angles()`. + This function acts on the quaternion as if it were unit normed. Note that the angles returned are in radians, not degrees. """ qw = self.scalar qx, qy, qz = self.vector[..., :] 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 + # Clip below is to avoid floating point round-off errors + elev = np.arcsin(np.clip(2*(qw*qy+qz*qx) + / (qw*qw+qx*qx+qy*qy+qz*qz), -1, 1)) roll = np.arctan2(2*( qw*qx-qy*qz), qw*qw-qx*qx-qy*qy+qz*qz) # noqa E201 return elev, azim, roll diff --git a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py index 0afcae99c980..07ecb0086030 100644 --- a/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py +++ b/lib/mpl_toolkits/mplot3d/tests/test_axes3d.py @@ -1950,10 +1950,10 @@ def test_rotate(): for roll, dx, dy, new_elev, new_azim, new_roll in [ [0, 0.5, 0, 0, -90, 0], [30, 0.5, 0, 30, -90, 0], - [0, 0, 0.5, -90, 0, 0], + [0, 0, 0.5, -90, -180, 180], [30, 0, 0.5, -60, -90, 90], - [0, 0.5, 0.5, -45, -90, 45], - [30, 0.5, 0.5, -15, -90, 45]]: + [0, np.sqrt(2)/4, np.sqrt(2)/4, -45, -90, 45], + [30, np.sqrt(2)/4, np.sqrt(2)/4, -15, -90, 45]]: fig = plt.figure() ax = fig.add_subplot(1, 1, 1, projection='3d') ax.view_init(0, 0, roll) @@ -1967,9 +1967,8 @@ def test_rotate(): xdata=dx*ax._pseudo_w, ydata=dy*ax._pseudo_h)) fig.canvas.draw() - assert np.isclose(ax.elev, new_elev) - assert np.isclose(ax.azim, new_azim) - assert np.isclose(ax.roll, new_roll) + np.testing.assert_allclose((ax.elev, ax.azim, ax.roll), + (new_elev, new_azim, new_roll), atol=1e-6) def test_pan(): 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