|
5 | 5 | import pytest |
6 | 6 |
|
7 | 7 | from mpl_toolkits.mplot3d import Axes3D, axes3d, proj3d, art3d |
| 8 | +from mpl_toolkits.mplot3d.axes3d import _Quaternion as Quaternion |
8 | 9 | import matplotlib as mpl |
9 | 10 | from matplotlib.backend_bases import (MouseButton, MouseEvent, |
10 | 11 | NavigationToolbar2) |
@@ -1766,29 +1767,127 @@ def test_shared_axes_retick(): |
1766 | 1767 | assert ax2.get_zlim() == (-0.5, 2.5) |
1767 | 1768 |
|
1768 | 1769 |
|
| 1770 | +def test_quaternion(): |
| 1771 | + # 1: |
| 1772 | + q1 = Quaternion(1, [0, 0, 0]) |
| 1773 | + assert q1.scalar == 1 |
| 1774 | + assert (q1.vector == [0, 0, 0]).all |
| 1775 | + # __neg__: |
| 1776 | + assert (-q1).scalar == -1 |
| 1777 | + assert ((-q1).vector == [0, 0, 0]).all |
| 1778 | + # i, j, k: |
| 1779 | + qi = Quaternion(0, [1, 0, 0]) |
| 1780 | + assert qi.scalar == 0 |
| 1781 | + assert (qi.vector == [1, 0, 0]).all |
| 1782 | + qj = Quaternion(0, [0, 1, 0]) |
| 1783 | + assert qj.scalar == 0 |
| 1784 | + assert (qj.vector == [0, 1, 0]).all |
| 1785 | + qk = Quaternion(0, [0, 0, 1]) |
| 1786 | + assert qk.scalar == 0 |
| 1787 | + assert (qk.vector == [0, 0, 1]).all |
| 1788 | + # i^2 = j^2 = k^2 = -1: |
| 1789 | + assert qi*qi == -q1 |
| 1790 | + assert qj*qj == -q1 |
| 1791 | + assert qk*qk == -q1 |
| 1792 | + # identity: |
| 1793 | + assert q1*qi == qi |
| 1794 | + assert q1*qj == qj |
| 1795 | + assert q1*qk == qk |
| 1796 | + # i*j=k, j*k=i, k*i=j: |
| 1797 | + assert qi*qj == qk |
| 1798 | + assert qj*qk == qi |
| 1799 | + assert qk*qi == qj |
| 1800 | + assert qj*qi == -qk |
| 1801 | + assert qk*qj == -qi |
| 1802 | + assert qi*qk == -qj |
| 1803 | + # __mul__: |
| 1804 | + assert (Quaternion(2, [3, 4, 5]) * Quaternion(6, [7, 8, 9]) |
| 1805 | + == Quaternion(-86, [28, 48, 44])) |
| 1806 | + # conjugate(): |
| 1807 | + for q in [q1, qi, qj, qk]: |
| 1808 | + assert q.conjugate().scalar == q.scalar |
| 1809 | + assert (q.conjugate().vector == -q.vector).all |
| 1810 | + assert q.conjugate().conjugate() == q |
| 1811 | + assert ((q*q.conjugate()).vector == 0).all |
| 1812 | + # norm: |
| 1813 | + q0 = Quaternion(0, [0, 0, 0]) |
| 1814 | + assert q0.norm == 0 |
| 1815 | + assert q1.norm == 1 |
| 1816 | + assert qi.norm == 1 |
| 1817 | + assert qj.norm == 1 |
| 1818 | + assert qk.norm == 1 |
| 1819 | + for q in [q0, q1, qi, qj, qk]: |
| 1820 | + assert q.norm == (q*q.conjugate()).scalar |
| 1821 | + # normalize(): |
| 1822 | + for q in [ |
| 1823 | + Quaternion(2, [0, 0, 0]), |
| 1824 | + Quaternion(0, [3, 0, 0]), |
| 1825 | + Quaternion(0, [0, 4, 0]), |
| 1826 | + Quaternion(0, [0, 0, 5]), |
| 1827 | + Quaternion(6, [7, 8, 9]) |
| 1828 | + ]: |
| 1829 | + assert q.normalize().norm == 1 |
| 1830 | + # reciprocal(): |
| 1831 | + for q in [q1, qi, qj, qk]: |
| 1832 | + assert q*q.reciprocal() == q1 |
| 1833 | + assert q.reciprocal()*q == q1 |
| 1834 | + # rotate(): |
| 1835 | + assert (qi.rotate([1, 2, 3]) == np.array([1, -2, -3])).all |
| 1836 | + # rotate_from_to(): |
| 1837 | + for r1, r2, q in [ |
| 1838 | + ([1, 0, 0], [0, 1, 0], Quaternion(np.sqrt(1/2), [0, 0, np.sqrt(1/2)])), |
| 1839 | + ([1, 0, 0], [0, 0, 1], Quaternion(np.sqrt(1/2), [0, -np.sqrt(1/2), 0])), |
| 1840 | + ([1, 0, 0], [1, 0, 0], Quaternion(1, [0, 0, 0])) |
| 1841 | + ]: |
| 1842 | + assert Quaternion.rotate_from_to(r1, r2) == q |
| 1843 | + # rotate_from_to(), special case: |
| 1844 | + for r1 in [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 1, 1]]: |
| 1845 | + r1 = np.array(r1) |
| 1846 | + with pytest.warns(UserWarning): |
| 1847 | + q = Quaternion.rotate_from_to(r1, -r1) |
| 1848 | + assert np.isclose(q.norm, 1) |
| 1849 | + assert np.dot(q.vector, r1) == 0 |
| 1850 | + # from_cardan_angles(), as_cardan_angles(): |
| 1851 | + for elev, azim, roll in [(0, 0, 0), |
| 1852 | + (90, 0, 0), (0, 90, 0), (0, 0, 90), |
| 1853 | + (0, 30, 30), (30, 0, 30), (30, 30, 0), |
| 1854 | + (47, 11, -24)]: |
| 1855 | + for mag in [1, 2]: |
| 1856 | + q = Quaternion.from_cardan_angles( |
| 1857 | + np.deg2rad(elev), np.deg2rad(azim), np.deg2rad(roll)) |
| 1858 | + assert np.isclose(q.norm, 1) |
| 1859 | + q = Quaternion(mag * q.scalar, mag * q.vector) |
| 1860 | + e, a, r = np.rad2deg(Quaternion.as_cardan_angles(q)) |
| 1861 | + assert np.isclose(e, elev) |
| 1862 | + assert np.isclose(a, azim) |
| 1863 | + assert np.isclose(r, roll) |
| 1864 | + |
| 1865 | + |
1769 | 1866 | def test_rotate(): |
1770 | 1867 | """Test rotating using the left mouse button.""" |
1771 | | - for roll in [0, 30]: |
| 1868 | + for roll, dx, dy, new_elev, new_azim, new_roll in [ |
| 1869 | + [0, 0.5, 0, 0, -90, 0], |
| 1870 | + [30, 0.5, 0, 30, -90, 0], |
| 1871 | + [0, 0, 0.5, -90, 0, 0], |
| 1872 | + [30, 0, 0.5, -60, -90, 90], |
| 1873 | + [0, 0.5, 0.5, -45, -90, 45], |
| 1874 | + [30, 0.5, 0.5, -15, -90, 45]]: |
1772 | 1875 | fig = plt.figure() |
1773 | 1876 | ax = fig.add_subplot(1, 1, 1, projection='3d') |
1774 | 1877 | ax.view_init(0, 0, roll) |
1775 | 1878 | ax.figure.canvas.draw() |
1776 | 1879 |
|
1777 | | - # drag mouse horizontally to change azimuth |
1778 | | - dx = 0.1 |
1779 | | - dy = 0.2 |
| 1880 | + # drag mouse to change orientation |
1780 | 1881 | ax._button_press( |
1781 | 1882 | mock_event(ax, button=MouseButton.LEFT, xdata=0, ydata=0)) |
1782 | 1883 | ax._on_move( |
1783 | 1884 | mock_event(ax, button=MouseButton.LEFT, |
1784 | 1885 | xdata=dx*ax._pseudo_w, ydata=dy*ax._pseudo_h)) |
1785 | 1886 | ax.figure.canvas.draw() |
1786 | | - roll_radians = np.deg2rad(ax.roll) |
1787 | | - cs = np.cos(roll_radians) |
1788 | | - sn = np.sin(roll_radians) |
1789 | | - assert ax.elev == (-dy*180*cs + dx*180*sn) |
1790 | | - assert ax.azim == (-dy*180*sn - dx*180*cs) |
1791 | | - assert ax.roll == roll |
| 1887 | + |
| 1888 | + assert np.isclose(ax.elev, new_elev) |
| 1889 | + assert np.isclose(ax.azim, new_azim) |
| 1890 | + assert np.isclose(ax.roll, new_roll) |
1792 | 1891 |
|
1793 | 1892 |
|
1794 | 1893 | def test_pan(): |
|
0 commit comments