Skip to content

Commit f059296

Browse files
Fix 3D rotation precession
Fix arcsin domain error Revert to simplified trig Linting tests
1 parent f8592cb commit f059296

File tree

2 files changed

+13
-10
lines changed

2 files changed

+13
-10
lines changed

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,10 +1568,11 @@ def _on_move(self, event):
15681568
q = _Quaternion.from_cardan_angles(elev, azim, roll)
15691569

15701570
# Update quaternion - a variation on Ken Shoemake's ARCBALL
1571-
current_vec = self._arcball(self._sx/w, self._sy/h)
1572-
new_vec = self._arcball(x/w, y/h)
1571+
scale = np.sqrt(2)/2 # slow down the rate of rotation
1572+
current_vec = self._arcball(self._sx*scale/w, self._sy*scale/h)
1573+
new_vec = self._arcball(x*scale/w, y*scale/h)
15731574
dq = _Quaternion.rotate_from_to(current_vec, new_vec)
1574-
q = dq * q
1575+
q = dq * dq * q
15751576

15761577
# Convert to elev, azim, roll
15771578
elev, azim, roll = q.as_cardan_angles()
@@ -4020,11 +4021,14 @@ def from_cardan_angles(cls, elev, azim, roll):
40204021
def as_cardan_angles(self):
40214022
"""
40224023
The inverse of `from_cardan_angles()`.
4024+
This function acts on the quaternion as if it were unit normed.
40234025
Note that the angles returned are in radians, not degrees.
40244026
"""
40254027
qw = self.scalar
40264028
qx, qy, qz = self.vector[..., :]
40274029
azim = np.arctan2(2*(-qw*qz+qx*qy), qw*qw+qx*qx-qy*qy-qz*qz)
4028-
elev = np.arcsin( 2*( qw*qy+qz*qx)/(qw*qw+qx*qx+qy*qy+qz*qz)) # noqa E201
4030+
# Clip below is to avoid floating point round-off errors
4031+
elev = np.arcsin(np.clip(2*(qw*qy+qz*qx)
4032+
/ (qw*qw+qx*qx+qy*qy+qz*qz), -1, 1))
40294033
roll = np.arctan2(2*( qw*qx-qy*qz), qw*qw-qx*qx-qy*qy+qz*qz) # noqa E201
40304034
return elev, azim, roll

lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,10 +1950,10 @@ def test_rotate():
19501950
for roll, dx, dy, new_elev, new_azim, new_roll in [
19511951
[0, 0.5, 0, 0, -90, 0],
19521952
[30, 0.5, 0, 30, -90, 0],
1953-
[0, 0, 0.5, -90, 0, 0],
1953+
[0, 0, 0.5, -90, -180, 180],
19541954
[30, 0, 0.5, -60, -90, 90],
1955-
[0, 0.5, 0.5, -45, -90, 45],
1956-
[30, 0.5, 0.5, -15, -90, 45]]:
1955+
[0, np.sqrt(2)/4, np.sqrt(2)/4, -45, -90, 45],
1956+
[30, np.sqrt(2)/4, np.sqrt(2)/4, -15, -90, 45]]:
19571957
fig = plt.figure()
19581958
ax = fig.add_subplot(1, 1, 1, projection='3d')
19591959
ax.view_init(0, 0, roll)
@@ -1967,9 +1967,8 @@ def test_rotate():
19671967
xdata=dx*ax._pseudo_w, ydata=dy*ax._pseudo_h))
19681968
fig.canvas.draw()
19691969

1970-
assert np.isclose(ax.elev, new_elev)
1971-
assert np.isclose(ax.azim, new_azim)
1972-
assert np.isclose(ax.roll, new_roll)
1970+
np.testing.assert_allclose((ax.elev, ax.azim, ax.roll),
1971+
(new_elev, new_azim, new_roll), atol=1e-6)
19731972

19741973

19751974
def test_pan():

0 commit comments

Comments
 (0)
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