From 82b7884cc74809fe87e25d009ca89988890a1811 Mon Sep 17 00:00:00 2001 From: Scott Shambaugh Date: Fri, 6 Dec 2024 09:03:11 -0700 Subject: [PATCH] Switch to a 3d rotation trackball implementation with path independence --- doc/api/toolkits/mplot3d/view_angles.rst | 45 +++++++++++++----------- lib/mpl_toolkits/mplot3d/axes3d.py | 15 ++++++-- 2 files changed, 36 insertions(+), 24 deletions(-) diff --git a/doc/api/toolkits/mplot3d/view_angles.rst b/doc/api/toolkits/mplot3d/view_angles.rst index 75b24ba9c7b0..f5914b778187 100644 --- a/doc/api/toolkits/mplot3d/view_angles.rst +++ b/doc/api/toolkits/mplot3d/view_angles.rst @@ -57,34 +57,33 @@ To keep it this way, set ``mouserotationstyle: azel``. This approach works fine for spherical coordinate plots, where the *z* axis is special; however, it leads to a kind of 'gimbal lock' when looking down the *z* axis: the plot reacts differently to mouse movement, dependent on the particular -orientation at hand. Also, 'roll' cannot be controlled. +orientation at hand. Also, the 'roll' axis about the viewing direction cannot +be controlled. As an alternative, there are various mouse rotation styles where the mouse manipulates a virtual 'trackball'. In its simplest form (``mouserotationstyle: trackball``), the trackball rotates around an in-plane axis perpendicular to the mouse motion -(it is as if there is a plate laying on the trackball; the plate itself is fixed +(it is as if there is a flat plate laying on a trackball; the plate itself is fixed in orientation, but you can drag the plate with the mouse, thus rotating the ball). -This is more natural to work with than the ``azel`` style; however, -the plot cannot be easily rotated around the viewing direction - one has to -move the mouse in circles with a handedness opposite to the desired rotation, -counterintuitively. +This is more natural to work with than the ``azel`` style; however, it is +difficult and unintuitive to control roll with it. A different variety of trackball rotates along the shortest arc on the virtual -sphere (``mouserotationstyle: sphere``). Rotating around the viewing direction -is straightforward with it: grab the ball near its edge instead of near the center. +sphere (``mouserotationstyle: sphere``). Rotating roll is straightforward with it: +grab the ball near its edge instead of near the center. Ken Shoemake's ARCBALL [Shoemake1992]_ is also available (``mouserotationstyle: Shoemake``); -it resembles the ``sphere`` style, but is free of hysteresis, -i.e., returning mouse to the original position -returns the figure to its original orientation; the rotation is independent -of the details of the path the mouse took, which could be desirable. -However, Shoemake's arcball rotates at twice the angular rate of the -mouse movement (it is quite noticeable, especially when adjusting roll), -and it lacks an obvious mechanical equivalent; arguably, the path-independent -rotation is not natural (however convenient), it could take some getting used to. -So it is a trade-off. - -Henriksen et al. [Henriksen2002]_ provide an overview. In summary: +it resembles the ``sphere`` style, but has the benefit of being free of hysteresis, +such that returning the mouse to the original position returns the figure to its +original orientation. This path independence of the rotation has the nice property +of being able to 'undo' an errant rotation. However, Shoemake's arcball rotates at +twice the angular rate of the mouse movement (noticeable especially when adjusting +roll), and it lacks an obvious mechanical equivalent; arguably, the path-independent +rotation is not natural and it could take some getting used to. +So there is a trade-off. + +Henriksen et al. [Henriksen2002]_ and Shambaugh [Shambaugh2024]_ provide +overviews. In summary: .. list-table:: :width: 100% @@ -106,7 +105,7 @@ Henriksen et al. [Henriksen2002]_ provide an overview. In summary: - ❌ - ✓ [6]_ - ✔️ - - ❌ + - ✔️ - ✔️ * - sphere - ❌ @@ -127,7 +126,7 @@ Henriksen et al. [Henriksen2002]_ provide an overview. In summary: .. [3] Figure reacts the same way to mouse movements, regardless of orientation (no difference between 'poles' and 'equator') .. [4] Returning mouse to original position returns figure to original orientation (rotation is independent of the details of the path the mouse took) .. [5] The style has a corresponding natural implementation as a mechanical device -.. [6] While it is possible to control roll with the ``trackball`` style, this is not immediately obvious (it requires moving the mouse in large circles) and a bit counterintuitive (the resulting roll is in the opposite direction) +.. [6] While it is possible to control roll with the ``trackball`` style, this is not immediately obvious (it requires chaining multiple rotations together) You can try out one of the various mouse rotation styles using: @@ -202,4 +201,8 @@ The border is a circular arc, wrapped around the arcball sphere cylindrically and Computer Graphics, Volume 10, Issue 2, March-April 2004, pp. 206-216, https://doi.org/10.1109/TVCG.2004.1260772 `[full-text]`__; +.. [Shambaugh2024] Scott Shambaugh, "Virtual Trackballs: An Interactive + Taxonomy", 11 November 2024, + https://theshamblog.com/virtual-trackballs-a-taxonomy/ + __ https://www.researchgate.net/publication/8329656_Virtual_Trackballs_Revisited#fullTextFileContent diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index f58d2eedf80e..1137015291e4 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -1364,7 +1364,12 @@ def clear(self): def _button_press(self, event): if event.inaxes == self: self.button_pressed = event.button - self._sx, self._sy = event.xdata, event.ydata + self._sx0, self._sy0 = event.xdata, event.ydata + self._sx, self._sy = self._sx0, self._sy0 + q0 = _Quaternion.from_cardan_angles( + *np.deg2rad((self.elev, self.azim, self.roll))) + self._q0 = q0 + toolbar = self.get_figure(root=True).canvas.toolbar if toolbar and toolbar._nav_stack() is None: toolbar.push_current() @@ -1566,6 +1571,7 @@ def _on_move(self, event): return dx, dy = x - self._sx, y - self._sy + dx0, dy0 = x - self._sx0, y - self._sy0 w = self._pseudo_w h = self._pseudo_h @@ -1589,10 +1595,13 @@ def _on_move(self, event): *np.deg2rad((self.elev, self.azim, self.roll))) if style == 'trackball': - k = np.array([0, -dy/h, dx/w]) + # To avoid precession, we need to rotate relative to the + # original orientation, not the current orientation. + k = np.array([0, -dy0/h, dx0/w]) nk = np.linalg.norm(k) th = nk / mpl.rcParams['axes3d.trackballsize'] dq = _Quaternion(np.cos(th), k*np.sin(th)/nk) + q = dq * self._q0 else: # 'sphere', 'arcball' current_vec = self._arcball(self._sx/w, self._sy/h) new_vec = self._arcball(x/w, y/h) @@ -1600,8 +1609,8 @@ def _on_move(self, event): dq = _Quaternion.rotate_from_to(current_vec, new_vec) else: # 'arcball' dq = _Quaternion(0, new_vec) * _Quaternion(0, -current_vec) + q = dq * q - q = dq * q elev, azim, roll = np.rad2deg(q.as_cardan_angles()) # update view 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