From 46b7da82faa2619607a67987bd53325896d654c0 Mon Sep 17 00:00:00 2001 From: Sergio Aguirre Date: Sat, 31 Oct 2026 22:57:04 +0100 Subject: [PATCH 1/2] [FEATURE] add pytta.vbap with vector base amplitude panning pytta.vbap pans sources on 2D and 3D loudspeaker layouts (Pulkki 1997): loudspeaker_pairs and loudspeaker_triplets find the groups (the convex hull of the directions in 3D, without the faces that close a hemisphere), inverse_matrices inverts the matrix of each group and gains gives the gains of a set of source directions, with an optional spread (MDAP). It is a port of the MATLAB library Vector-Base-Amplitude-Panning of Archontis Politis, whose BSD 3-Clause licence is reproduced in the module header. Tests check the tangent law of a stereo pair, that the 3D gains weight the loudspeaker vectors into the source direction, and parity with the MATLAB library on golden data from tests/golden/make_vbap_polarch.m (groups identical, inverses and gains within 4.4e-16). Co-Authored-By: Claude Fable 5.1 --- docs/pyttadoc/index.rst | 4 + docs/pyttadoc/vbap.rst | 7 + pytta/__init__.py | 5 + pytta/vbap.py | 254 +++++++++++++++++++++++++++++++ tests/golden/make_vbap_polarch.m | 20 +++ tests/golden/vbap_polarch.mat | Bin 0 -> 4942 bytes tests/test_vbap.py | 90 +++++++++++ 7 files changed, 380 insertions(+) create mode 100644 docs/pyttadoc/vbap.rst create mode 100644 pytta/vbap.py create mode 100644 tests/golden/make_vbap_polarch.m create mode 100644 tests/golden/vbap_polarch.mat create mode 100644 tests/test_vbap.py diff --git a/docs/pyttadoc/index.rst b/docs/pyttadoc/index.rst index ca3da8d..57877a0 100644 --- a/docs/pyttadoc/index.rst +++ b/docs/pyttadoc/index.rst @@ -57,6 +57,9 @@ Modules: * pytta.iso3741: calculations according to the standard; + * pytta.vbap: + vector base amplitude panning for 2D and 3D loudspeaker layouts; + .. toctree:: classes/index utils/index @@ -64,4 +67,5 @@ Modules: functions generate rooms + vbap properties diff --git a/docs/pyttadoc/vbap.rst b/docs/pyttadoc/vbap.rst new file mode 100644 index 0000000..3d1b0d0 --- /dev/null +++ b/docs/pyttadoc/vbap.rst @@ -0,0 +1,7 @@ +.. currentmodule:: pytta + +VBAP +==== + +.. automodule:: pytta.vbap + :members: diff --git a/pytta/__init__.py b/pytta/__init__.py index d8740aa..908e6ab 100644 --- a/pytta/__init__.py +++ b/pytta/__init__.py @@ -72,6 +72,9 @@ * pytta.iso3741: calculations according to the standard; + * pytta.vbap: + vector base amplitude panning for 2D and 3D loudspeaker layouts; + You can find usage examples in the toolbox's examples folder. @@ -108,6 +111,7 @@ from . import utils from . import rooms from . import iso3741 +from . import vbap from .functions import read_wav, write_wav, merge, split, \ list_devices, print_devices, get_device_from_user,\ @@ -130,6 +134,7 @@ 'generate', 'utils', 'iso3741', + 'vbap', 'rooms', # Functions diff --git a/pytta/vbap.py b/pytta/vbap.py new file mode 100644 index 0000000..6448614 --- /dev/null +++ b/pytta/vbap.py @@ -0,0 +1,254 @@ +""" +PyTTa VBAP: +----------- + +Vector base amplitude panning (VBAP) of Pulkki (1997) for 2D and 3D +loudspeaker layouts, and its spread version, multiple-direction amplitude +panning (MDAP). + +Available functions: + + >>> groups = pytta.vbap.loudspeaker_pairs(azimuths) # 2D + >>> groups = pytta.vbap.loudspeaker_triplets(directions) # 3D + >>> inverses = pytta.vbap.inverse_matrices(directions, groups) + >>> G = pytta.vbap.gains(source_directions, groups, inverses, spread=0) + +Directions are in degrees: azimuth for 2D layouts, [azimuth, elevation] for +3D ones, with x = cos(azi) cos(elev), y = sin(azi) cos(elev) and z = +sin(elev) (0 deg in front, 90 deg to the left). Loudspeaker indices are +0-based. + +Origin of the code +------------------ + +This module is a port to Python of the MATLAB library +Vector-Base-Amplitude-Panning by Archontis Politis and Ville Pulkki (2015), +https://github.com/polarch/Vector-Base-Amplitude-Panning (findLsPairs.m, +findLsTriplets.m, invertLsMtx.m, vbap.m, getSpreadSrcDirs.m), under the BSD +3-Clause licence reproduced below. The gains follow the MATLAB code +operation by operation. + +References +---------- + +Pulkki, V. (1997). Virtual sound source positioning using vector base +amplitude panning. Journal of the Audio Engineering Society, 45(6), 456-466. + +Pulkki, V. (1999). Uniform spreading of amplitude panned virtual sources. +Proceedings of the IEEE Workshop on Applications of Signal Processing to +Audio and Acoustics, New Paltz, NY. + +Licence of the ported code +-------------------------- + + Copyright (c) 2015, Archontis Politis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import numpy as np +from scipy.spatial import ConvexHull + + +def _cartesian(directions): + d = np.radians(np.asarray(directions, dtype=float)) + if d.ndim == 1: + return np.column_stack([np.cos(d), np.sin(d)]) + azi, elev = d[:, 0], d[:, 1] + return np.column_stack([np.cos(azi) * np.cos(elev), + np.sin(azi) * np.cos(elev), np.sin(elev)]) + + +def loudspeaker_pairs(azimuths, max_aperture=None): + """ + Adjacent loudspeaker pairs of a 2D layout (findLsPairs). + + Parameters + ---------- + azimuths : array_like + Loudspeaker azimuths in degrees. + max_aperture : float, optional + Pairs that span this angle in degrees or more are left out. + + Returns + ------- + numpy.ndarray + (pairs, 2) loudspeaker indices, around the circle by azimuth. + """ + azimuths = np.asarray(azimuths, dtype=float) + order = np.argsort(azimuths, kind='stable') + groups = np.column_stack([order, np.roll(order, -1)]) + if max_aperture is not None: + s = azimuths[np.append(order, order[0])] + apertures = np.diff(s) + apertures[-1] = 360 - abs(apertures[-1]) + groups = groups[apertures < max_aperture] + return groups + + +def loudspeaker_triplets(directions, max_aperture=None): + """ + Loudspeaker triplets of a 3D layout (findLsTriplets). + + The triplets are the faces of the convex hull of the loudspeaker + directions, which on the unit sphere is their Delaunay triangulation. + Faces whose outward normal is at 90 deg or more from their centroid are + left out: those are the faces that close a layout with no loudspeakers + below or above, through which no source direction passes. + + Parameters + ---------- + directions : array_like + (loudspeakers, 2) [azimuth, elevation] in degrees. + max_aperture : float, optional + Triplets with a side longer than this angle in degrees are left out. + + Returns + ------- + numpy.ndarray + (triplets, 3) loudspeaker indices, each triplet counter-clockwise + seen from outside and starting at its smallest index, sorted. + """ + U = _cartesian(directions) + faces = [] + for face in ConvexHull(U).simplices: + v = U[face] + n = np.cross(v[1] - v[0], v[2] - v[1]) + if np.dot(n, v.mean(axis=0) - U.mean(axis=0)) < 0: + face, v, n = face[::-1], v[::-1], -n + # MATLAB keeps acos(dot(centroid, n)) < pi/2; a dot product of + # rounding size, as on a flat bottom face, rounds to pi/2 there. + if np.dot(v.mean(axis=0), n) > 1e-12: + faces.append(np.roll(face, -int(np.argmin(face)))) + faces = np.array(faces, dtype=int) + if max_aperture is not None: + sides = np.stack([np.sum(U[faces[:, a]] * U[faces[:, b]], axis=1) + for a, b in ((0, 1), (1, 2), (2, 0))], axis=1) + faces = faces[np.all(np.arccos(np.clip(sides, -1, 1)) + <= np.radians(max_aperture), axis=1)] + return faces[np.lexsort((faces[:, 1], faces[:, 0]))] + + +def inverse_matrices(directions, groups): + """ + Inverse of the matrix of loudspeaker vectors of each group (invertLsMtx). + + Parameters + ---------- + directions : array_like + Loudspeaker azimuths (2D) or [azimuth, elevation] (3D) in degrees. + groups : numpy.ndarray + Pairs or triplets from loudspeaker_pairs or loudspeaker_triplets. + + Returns + ------- + numpy.ndarray + (groups, dim, dim): the inverse of the matrix whose rows are the + unit vectors of the group, so that the gains of a source u are + u @ inverse. + """ + U = _cartesian(directions) + return np.linalg.inv(U[np.asarray(groups)]) + + +def _spread_directions(direction, spread, num_src=8): + # getSpreadSrcDirs with its defaults: num_src sources, one ring in 3D. + if np.ndim(direction) == 0: + d = np.radians(np.linspace(direction - spread / 2, direction + spread / 2, + num_src + 1)) + return np.column_stack([np.cos(d), np.sin(d)]) + u = _cartesian(np.atleast_2d(direction))[0] + u_x = np.array([[0, -u[2], u[1]], [u[2], 0, -u[0]], [-u[1], u[0], 0]]) + theta = 2 * np.pi / num_src + R = (np.eye(3) * np.cos(theta) + np.sin(theta) * u_x + + (1 - np.cos(theta)) * np.outer(u, u)) + base = np.zeros((num_src, 3)) + if direction[1] > 89 or direction[1] < -89: + base[0] = [1, 0, 0] + else: + c = np.cross(u, [0, 0, 1]) + base[0] = c / np.sqrt(np.sum(c**2)) + for n in range(1, num_src): + base[n] = R @ base[n - 1] + ring = u + base * np.tan(np.radians(spread / 2)) + ring = ring / np.sqrt(np.sum(ring**2, axis=1))[:, None] + return np.vstack([ring, u]) + + +def _group_gains(u, groups, inverses, first_only): + g = np.zeros(int(groups.max()) + 1) + for group, inv in zip(groups, inverses): + g_tmp = u @ inv + if np.min(g_tmp) > -0.001: + g[group] = g_tmp / np.sqrt(np.sum(g_tmp**2)) + if first_only: + break + return g + + +def gains(directions, groups, inverses, spread=0.): + """ + VBAP gains of a set of source directions (vbap). + + Parameters + ---------- + directions : array_like + Source azimuths (2D) or (sources, 2) [azimuth, elevation] (3D) in + degrees. + groups : numpy.ndarray + Pairs or triplets from loudspeaker_pairs or loudspeaker_triplets. + inverses : numpy.ndarray + From inverse_matrices. + spread : float, optional + Spread in degrees for MDAP: the gains of 9 directions across the + spread (8 on a ring and the source in 3D) are summed. The default is + 0, plain VBAP. + + Returns + ------- + numpy.ndarray + (sources, loudspeakers) gains, non-negative, of norm 1 per source. + A source outside every group (below a hemisphere layout, say) gets + all gains 0, as in the MATLAB code. + """ + groups = np.asarray(groups) + three_d = groups.shape[1] == 3 + directions = np.asarray(directions, dtype=float) + directions = directions.reshape(-1, 2) if three_d else directions.reshape(-1) + out = np.zeros((len(directions), int(groups.max()) + 1)) + for n, d in enumerate(directions): + if spread: + g = sum(_group_gains(u, groups, inverses, three_d) + for u in _spread_directions(d, spread)) + else: + g = _group_gains(_cartesian(np.atleast_2d(d) if three_d else [d])[0], + groups, inverses, three_d) + with np.errstate(invalid='ignore'): + out[n] = np.fmax(0, g / np.sqrt(np.sum(g**2))) + return out diff --git a/tests/golden/make_vbap_polarch.m b/tests/golden/make_vbap_polarch.m new file mode 100644 index 0000000..e6abb89 --- /dev/null +++ b/tests/golden/make_vbap_polarch.m @@ -0,0 +1,20 @@ +% Golden data for tests/test_vbap.py: polarch Vector-Base-Amplitude-Panning +% (Archontis Politis, BSD 3-Clause), the copy that Iceberg installs in +% Toolboxes/polarch/vbap. Run from tests/golden with that folder on the path. +% Indices are 1-based. +ls2 = [0 30 -30 110 -110]; % 5.0, 2D +ls3 = [0 0; 90 0; 180 0; -90 0; 45 45; 135 45; -135 45; -45 45; 0 90]; % hemisphere +src2 = (-180:7.5:180)'; +[a, e] = meshgrid(-180:20:160, -10:20:90); src3 = [a(:) e(:)]; +pairs = findLsPairs(ls2); +inv2 = invertLsMtx(ls2, pairs); +g2 = vbap(src2, pairs, inv2); +g2s = vbap(src2, pairs, inv2, 20); +tri = findLsTriplets(ls3); +inv3 = invertLsMtx(ls3, tri); +g3 = vbap(src3, tri, inv3); +g3s = vbap(src3, tri, inv3, 20); +save('vbap_polarch.mat', 'ls2', 'ls3', 'src2', 'src3', 'pairs', 'inv2', 'g2', ... + 'g2s', 'tri', 'inv3', 'g3', 'g3s'); +fprintf('%d pairs, %d triplets\n', size(pairs, 1), size(tri, 1)); +disp(tri) diff --git a/tests/golden/vbap_polarch.mat b/tests/golden/vbap_polarch.mat new file mode 100644 index 0000000000000000000000000000000000000000..8a568a5180c3cbe940ca8bffad17c9682911e470 GIT binary patch literal 4942 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 B%ozXx literal 0 HcmV?d00001 diff --git a/tests/test_vbap.py b/tests/test_vbap.py new file mode 100644 index 0000000..00e5d13 --- /dev/null +++ b/tests/test_vbap.py @@ -0,0 +1,90 @@ +""" +Tests for pytta.vbap, vector base amplitude panning (Pulkki 1997), a port of +the VBAP library of Archontis Politis. Each test pins one rule. + +The golden data come from golden/make_vbap_polarch.m (MATLAB R2026b with +polarch Vector-Base-Amplitude-Panning, the copy that Iceberg installs); +their loudspeaker indices are 1-based. +""" +import os +import unittest + +import numpy as np +import scipy.io as sio + +from pytta import vbap + +GOLDEN = os.path.join(os.path.dirname(__file__), 'golden') + + +def unit(azi, elev=0.): + a, e = np.radians(azi), np.radians(elev) + return np.array([np.cos(a) * np.cos(e), np.sin(a) * np.cos(e), np.sin(e)]) + + +class TestVBAP(unittest.TestCase): + + def test_stereo_pair_follows_the_tangent_law(self): + # Loudspeakers at +-30 deg: (g1 - g2) / (g1 + g2) = tan(theta) / + # tan(30 deg), with g1^2 + g2^2 = 1 (Pulkki 1997, eq. 4 and 6). + ls = [30, -30] + groups = vbap.loudspeaker_pairs(ls) + inv = vbap.inverse_matrices(ls, groups) + theta = np.linspace(-29, 29, 59) + g = vbap.gains(theta, groups, inv) + law = (g[:, 0] - g[:, 1]) / (g[:, 0] + g[:, 1]) + err = np.max(np.abs(law - np.tan(np.radians(theta)) / np.tan(np.radians(30)))) + print(f'\n max |tangent law error| = {err:.1e}, ' + f'max |norm - 1| = {np.max(np.abs(np.linalg.norm(g, axis=1) - 1)):.1e}') + self.assertLess(err, 1e-12) + np.testing.assert_allclose(np.linalg.norm(g, axis=1), 1, atol=1e-12) + + def test_3d_gains_point_at_the_source(self): + # VBAP definition: the gains of the active triplet weight the + # loudspeaker vectors into a vector along the source direction, all + # gains are non-negative and their norm is 1. A source on a + # loudspeaker gets that loudspeaker alone. + ls = np.array([[0, 0], [90, 0], [180, 0], [-90, 0], [45, 45], + [135, 45], [-135, 45], [-45, 45], [0, 90]]) + groups = vbap.loudspeaker_triplets(ls) + inv = vbap.inverse_matrices(ls, groups) + rng = np.random.default_rng(1) + src = np.column_stack([rng.uniform(-180, 180, 200), rng.uniform(1, 89, 200)]) + g = vbap.gains(src, groups, inv) + L = np.array([unit(*d) for d in ls]) + p = g @ L + u = np.array([unit(*s) for s in src]) + cross = np.max(np.linalg.norm(np.cross(p, u), axis=1)) + print(f'\n max |p x u| = {cross:.1e} over 200 sources') + self.assertLess(cross, 1e-12) + self.assertTrue(np.all(g >= 0)) + np.testing.assert_allclose(np.linalg.norm(g, axis=1), 1, atol=1e-12) + on = vbap.gains(ls[[4]], groups, inv)[0] + self.assertAlmostEqual(on[4], 1, places=12) + self.assertAlmostEqual(np.sum(np.delete(on, 4)**2), 0, places=12) + + def test_matches_polarch(self): + # Same layouts and sources: pairs and triplets (1-based there), the + # inverse matrices (stacked by columns there) and the gains, with and + # without a spread of 20 deg (MDAP). + d = sio.loadmat(os.path.join(GOLDEN, 'vbap_polarch.mat'), squeeze_me=True) + pairs = vbap.loudspeaker_pairs(d['ls2']) + tri = vbap.loudspeaker_triplets(d['ls3']) + np.testing.assert_array_equal(pairs + 1, d['pairs']) + np.testing.assert_array_equal(tri + 1, d['tri']) + inv2 = vbap.inverse_matrices(d['ls2'], pairs) + inv3 = vbap.inverse_matrices(d['ls3'], tri) + diffs = { + 'inv2': np.abs(inv2.transpose(0, 2, 1).reshape(len(pairs), -1) - d['inv2']).max(), + 'inv3': np.abs(inv3.transpose(0, 2, 1).reshape(len(tri), -1) - d['inv3']).max(), + 'g2': np.abs(vbap.gains(d['src2'], pairs, inv2) - d['g2']).max(), + 'g2s': np.abs(vbap.gains(d['src2'], pairs, inv2, 20) - d['g2s']).max(), + 'g3': np.abs(vbap.gains(d['src3'], tri, inv3) - d['g3']).max(), + 'g3s': np.abs(vbap.gains(d['src3'], tri, inv3, 20) - d['g3s']).max()} + print('\n ' + ', '.join(f'{k} {v:.1e}' for k, v in diffs.items())) + for v in diffs.values(): + self.assertLess(v, 1e-12) + + +if __name__ == '__main__': + unittest.main() From 7cffe365991d6227eda8af4f853dbf3a581f33b3 Mon Sep 17 00:00:00 2001 From: Sergio Aguirre Date: Sat, 31 Oct 2026 23:33:33 +0100 Subject: [PATCH 2/2] [FEATURE] add pytta.ambisonics with higher-order Ambisonics pytta.ambisonics encodes, decodes and rotates higher-order Ambisonics in N3D and ACN: real_sh, encode, decode (also in frequency bands), decoder with the sampling, mode-matching, energy-preserving, all-round (ALLRAD) and constant angular spread (CSAD) decoders and max-rE weights, layout_order, rotate and sh_rotation, the B-format functions, the conversions to SN3D, FuMa and SID, and bformat_fuma of Iceberg. The t-designs of degree 1 to 21 are in pytta/_tdesigns.py. pytta.vbap gains energy_gains (VBIP) and spread rings, which CSAD uses. It is a port of the MATLAB libraries Higher-Order-Ambisonics and Spherical-Harmonic-Transform of Archontis Politis, whose BSD 3-Clause licence is reproduced in the module header. Tests check the orthonormality of the harmonics by exact quadrature, that the first order is FuMa B-format, that a rotation moves the encoded direction, and parity with the MATLAB library on golden data from tests/golden/make_ambisonics_polarch.m (within 3.4e-15). Co-Authored-By: Claude Fable 5.1 --- docs/pyttadoc/ambisonics.rst | 7 + docs/pyttadoc/index.rst | 4 + pytta/__init__.py | 5 + pytta/_tdesigns.py | 1903 ++++++++++++++++++++++++ pytta/ambisonics.py | 512 +++++++ pytta/vbap.py | 65 +- tests/golden/ambisonics_polarch.mat | Bin 0 -> 85660 bytes tests/golden/make_ambisonics_polarch.m | 44 + tests/test_ambisonics.py | 106 ++ 9 files changed, 2638 insertions(+), 8 deletions(-) create mode 100644 docs/pyttadoc/ambisonics.rst create mode 100644 pytta/_tdesigns.py create mode 100644 pytta/ambisonics.py create mode 100644 tests/golden/ambisonics_polarch.mat create mode 100644 tests/golden/make_ambisonics_polarch.m create mode 100644 tests/test_ambisonics.py diff --git a/docs/pyttadoc/ambisonics.rst b/docs/pyttadoc/ambisonics.rst new file mode 100644 index 0000000..4585292 --- /dev/null +++ b/docs/pyttadoc/ambisonics.rst @@ -0,0 +1,7 @@ +.. currentmodule:: pytta + +Ambisonics +========== + +.. automodule:: pytta.ambisonics + :members: diff --git a/docs/pyttadoc/index.rst b/docs/pyttadoc/index.rst index 57877a0..ba9cc57 100644 --- a/docs/pyttadoc/index.rst +++ b/docs/pyttadoc/index.rst @@ -60,6 +60,9 @@ Modules: * pytta.vbap: vector base amplitude panning for 2D and 3D loudspeaker layouts; + * pytta.ambisonics: + higher-order Ambisonics encoding, decoding and rotation; + .. toctree:: classes/index utils/index @@ -68,4 +71,5 @@ Modules: generate rooms vbap + ambisonics properties diff --git a/pytta/__init__.py b/pytta/__init__.py index 908e6ab..7e39efd 100644 --- a/pytta/__init__.py +++ b/pytta/__init__.py @@ -75,6 +75,9 @@ * pytta.vbap: vector base amplitude panning for 2D and 3D loudspeaker layouts; + * pytta.ambisonics: + higher-order Ambisonics encoding, decoding and rotation; + You can find usage examples in the toolbox's examples folder. @@ -112,6 +115,7 @@ from . import rooms from . import iso3741 from . import vbap +from . import ambisonics from .functions import read_wav, write_wav, merge, split, \ list_devices, print_devices, get_device_from_user,\ @@ -135,6 +139,7 @@ 'utils', 'iso3741', 'vbap', + 'ambisonics', 'rooms', # Functions diff --git a/pytta/_tdesigns.py b/pytta/_tdesigns.py new file mode 100644 index 0000000..578a145 --- /dev/null +++ b/pytta/_tdesigns.py @@ -0,0 +1,1903 @@ +""" +Spherical t-designs of degree 1 to 21 on the unit sphere, as unit vectors. + +A t-design of degree t is a set of points whose mean of any polynomial of +degree t or less on the sphere equals the mean over the sphere. The designs +are those of Hardin and Sloane, as stored in t_designs_1_21.mat of the +Spherical-Harmonic-Transform library of Archontis Politis +(https://github.com/polarch/Spherical-Harmonic-Transform), BSD 3-Clause +licence, reproduced in pytta/ambisonics.py. + +Hardin, R. H., & Sloane, N. J. A. (1996). McLaren's improved snub cube and +other new spherical designs in three dimensions. Discrete & Computational +Geometry, 15(4), 429-441. +""" + +T_DESIGNS = { + 1: [ + (1.0, 0.0, 0.0), + (-1.0, 0.0, 0.0), + ], + 2: [ + (0.5773502691896257, 0.5773502691896257, 0.5773502691896257), + (0.5773502691896257, -0.5773502691896257, -0.5773502691896257), + (-0.5773502691896257, 0.5773502691896257, -0.5773502691896257), + (-0.5773502691896257, -0.5773502691896257, 0.5773502691896257), + ], + 3: [ + (1.0, 0.0, 0.0), + (-1.0, 0.0, 0.0), + (0.0, 1.0, 0.0), + (0.0, -1.0, 0.0), + (0.0, 0.0, 1.0), + (0.0, 0.0, -1.0), + ], + 4: [ + (0.850650808352, 0.0, -0.525731112119), + (0.525731112119, -0.850650808352, 0.0), + (0.0, -0.525731112119, 0.850650808352), + (0.850650808352, 0.0, 0.525731112119), + (-0.525731112119, -0.850650808352, 0.0), + (0.0, 0.525731112119, -0.850650808352), + (-0.850650808352, 0.0, -0.525731112119), + (-0.525731112119, 0.850650808352, 0.0), + (0.0, 0.525731112119, 0.850650808352), + (-0.850650808352, 0.0, 0.525731112119), + (0.525731112119, 0.850650808352, 0.0), + (0.0, -0.525731112119, -0.850650808352), + ], + 5: [ + (0.850650808352, 0.0, -0.525731112119), + (0.525731112119, -0.850650808352, 0.0), + (0.0, -0.525731112119, 0.850650808352), + (0.850650808352, 0.0, 0.525731112119), + (-0.525731112119, -0.850650808352, 0.0), + (0.0, 0.525731112119, -0.850650808352), + (-0.850650808352, 0.0, -0.525731112119), + (-0.525731112119, 0.850650808352, 0.0), + (0.0, 0.525731112119, 0.850650808352), + (-0.850650808352, 0.0, 0.525731112119), + (0.525731112119, 0.850650808352, 0.0), + (0.0, -0.525731112119, -0.850650808352), + ], + 6: [ + (0.8662468181078206, 0.42251865376111153, 0.26663540151670473), + (0.8662468181078206, -0.42251865376111153, -0.26663540151670473), + (0.8662468181078206, 0.26663540151670473, -0.42251865376111153), + (0.8662468181078206, -0.26663540151670473, 0.42251865376111153), + (-0.8662468181078206, 0.42251865376111153, -0.26663540151670473), + (-0.8662468181078206, -0.42251865376111153, 0.26663540151670473), + (-0.8662468181078206, 0.26663540151670473, 0.42251865376111153), + (-0.8662468181078206, -0.26663540151670473, -0.42251865376111153), + (0.26663540151670473, 0.8662468181078206, 0.42251865376111153), + (-0.26663540151670473, 0.8662468181078206, -0.42251865376111153), + (-0.42251865376111153, 0.8662468181078206, 0.26663540151670473), + (0.42251865376111153, 0.8662468181078206, -0.26663540151670473), + (-0.26663540151670473, -0.8662468181078206, 0.42251865376111153), + (0.26663540151670473, -0.8662468181078206, -0.42251865376111153), + (0.42251865376111153, -0.8662468181078206, 0.26663540151670473), + (-0.42251865376111153, -0.8662468181078206, -0.26663540151670473), + (0.42251865376111153, 0.26663540151670473, 0.8662468181078206), + (-0.42251865376111153, -0.26663540151670473, 0.8662468181078206), + (0.26663540151670473, -0.42251865376111153, 0.8662468181078206), + (-0.26663540151670473, 0.42251865376111153, 0.8662468181078206), + (0.42251865376111153, -0.26663540151670473, -0.8662468181078206), + (-0.42251865376111153, 0.26663540151670473, -0.8662468181078206), + (0.26663540151670473, 0.42251865376111153, -0.8662468181078206), + (-0.26663540151670473, -0.42251865376111153, -0.8662468181078206), + ], + 7: [ + (0.8662468181078206, 0.42251865376111153, 0.26663540151670473), + (0.8662468181078206, -0.42251865376111153, -0.26663540151670473), + (0.8662468181078206, 0.26663540151670473, -0.42251865376111153), + (0.8662468181078206, -0.26663540151670473, 0.42251865376111153), + (-0.8662468181078206, 0.42251865376111153, -0.26663540151670473), + (-0.8662468181078206, -0.42251865376111153, 0.26663540151670473), + (-0.8662468181078206, 0.26663540151670473, 0.42251865376111153), + (-0.8662468181078206, -0.26663540151670473, -0.42251865376111153), + (0.26663540151670473, 0.8662468181078206, 0.42251865376111153), + (-0.26663540151670473, 0.8662468181078206, -0.42251865376111153), + (-0.42251865376111153, 0.8662468181078206, 0.26663540151670473), + (0.42251865376111153, 0.8662468181078206, -0.26663540151670473), + (-0.26663540151670473, -0.8662468181078206, 0.42251865376111153), + (0.26663540151670473, -0.8662468181078206, -0.42251865376111153), + (0.42251865376111153, -0.8662468181078206, 0.26663540151670473), + (-0.42251865376111153, -0.8662468181078206, -0.26663540151670473), + (0.42251865376111153, 0.26663540151670473, 0.8662468181078206), + (-0.42251865376111153, -0.26663540151670473, 0.8662468181078206), + (0.26663540151670473, -0.42251865376111153, 0.8662468181078206), + (-0.26663540151670473, 0.42251865376111153, 0.8662468181078206), + (0.42251865376111153, -0.26663540151670473, -0.8662468181078206), + (-0.42251865376111153, 0.26663540151670473, -0.8662468181078206), + (0.26663540151670473, 0.42251865376111153, -0.8662468181078206), + (-0.26663540151670473, -0.42251865376111153, -0.8662468181078206), + ], + 8: [ + (0.507475446410817, -0.306200013239571, 0.805425492011663), + (-0.306200013239569, 0.805425492011663, 0.507475446410817), + (-0.507475446410817, 0.30620001323957, 0.805425492011663), + (0.805425492011663, 0.507475446410817, -0.306200013239569), + (0.306200013239569, 0.805425492011664, -0.507475446410817), + (0.805425492011663, -0.507475446410817, 0.306200013239569), + (0.306200013239569, -0.805425492011663, 0.507475446410816), + (-0.805425492011663, -0.507475446410817, -0.306200013239569), + (-0.30620001323957, -0.805425492011664, -0.507475446410816), + (-0.805425492011663, 0.507475446410818, 0.306200013239569), + (0.507475446410817, 0.30620001323957, -0.805425492011663), + (-0.507475446410817, -0.30620001323957, -0.805425492011663), + (0.626363670265271, -0.243527775409194, -0.74051520928072), + (-0.243527775409195, -0.74051520928072, 0.626363670265271), + (-0.626363670265271, 0.243527775409194, -0.74051520928072), + (-0.74051520928072, 0.62636367026527, -0.243527775409195), + (0.243527775409195, -0.740515209280719, -0.626363670265271), + (-0.74051520928072, -0.62636367026527, 0.243527775409195), + (0.243527775409195, 0.740515209280719, 0.626363670265271), + (0.74051520928072, -0.62636367026527, -0.243527775409195), + (-0.243527775409195, 0.74051520928072, -0.626363670265271), + (0.74051520928072, 0.62636367026527, 0.243527775409195), + (0.626363670265271, 0.243527775409194, 0.74051520928072), + (-0.626363670265271, -0.243527775409194, 0.74051520928072), + (-0.286248723426035, 0.957120327092458, -0.044523564585421), + (0.957120327092458, -0.04452356458542, -0.286248723426035), + (0.286248723426035, -0.957120327092458, -0.044523564585421), + (-0.04452356458542, -0.286248723426035, 0.957120327092458), + (-0.957120327092458, -0.044523564585419, 0.286248723426035), + (-0.044523564585421, 0.286248723426034, -0.957120327092458), + (-0.957120327092458, 0.04452356458542, -0.286248723426034), + (0.044523564585421, 0.286248723426034, 0.957120327092458), + (0.957120327092458, 0.04452356458542, 0.286248723426034), + (0.044523564585421, -0.286248723426034, -0.957120327092458), + (-0.286248723426034, -0.957120327092458, 0.044523564585421), + (0.286248723426035, 0.957120327092458, 0.044523564585421), + ], + 9: [ + (0.9333646931993099, 0.353542188921472, -0.06195377423185971), + (0.9333646931993099, -0.353542188921472, 0.06195377423185971), + (0.9333646931993099, -0.06195377423185971, -0.353542188921472), + (0.9333646931993099, 0.06195377423185971, 0.353542188921472), + (-0.9333646931993099, 0.353542188921472, 0.06195377423185971), + (-0.9333646931993099, -0.353542188921472, -0.06195377423185971), + (-0.9333646931993099, -0.06195377423185971, 0.353542188921472), + (-0.9333646931993099, 0.06195377423185971, -0.353542188921472), + (-0.06195377423185971, 0.9333646931993099, 0.353542188921472), + (0.06195377423185971, 0.9333646931993099, -0.353542188921472), + (-0.353542188921472, 0.9333646931993099, -0.06195377423185971), + (0.353542188921472, 0.9333646931993099, 0.06195377423185971), + (0.06195377423185971, -0.9333646931993099, 0.353542188921472), + (-0.06195377423185971, -0.9333646931993099, -0.353542188921472), + (0.353542188921472, -0.9333646931993099, -0.06195377423185971), + (-0.353542188921472, -0.9333646931993099, 0.06195377423185971), + (0.353542188921472, -0.06195377423185971, 0.9333646931993099), + (-0.353542188921472, 0.06195377423185971, 0.9333646931993099), + (-0.06195377423185971, -0.353542188921472, 0.9333646931993099), + (0.06195377423185971, 0.353542188921472, 0.9333646931993099), + (0.353542188921472, 0.06195377423185971, -0.9333646931993099), + (-0.353542188921472, -0.06195377423185971, -0.9333646931993099), + (-0.06195377423185971, 0.353542188921472, -0.9333646931993099), + (0.06195377423185971, -0.353542188921472, -0.9333646931993099), + (0.7068416977125503, 0.639740098619792, 0.3018400579657685), + (0.7068416977125503, -0.639740098619792, -0.3018400579657685), + (0.7068416977125503, 0.3018400579657685, -0.639740098619792), + (0.7068416977125503, -0.3018400579657685, 0.639740098619792), + (-0.7068416977125503, 0.639740098619792, -0.3018400579657685), + (-0.7068416977125503, -0.639740098619792, 0.3018400579657685), + (-0.7068416977125503, 0.3018400579657685, 0.639740098619792), + (-0.7068416977125503, -0.3018400579657685, -0.639740098619792), + (0.3018400579657685, 0.7068416977125503, 0.639740098619792), + (-0.3018400579657685, 0.7068416977125503, -0.639740098619792), + (-0.639740098619792, 0.7068416977125503, 0.3018400579657685), + (0.639740098619792, 0.7068416977125503, -0.3018400579657685), + (-0.3018400579657685, -0.7068416977125503, 0.639740098619792), + (0.3018400579657685, -0.7068416977125503, -0.639740098619792), + (0.639740098619792, -0.7068416977125503, 0.3018400579657685), + (-0.639740098619792, -0.7068416977125503, -0.3018400579657685), + (0.639740098619792, 0.3018400579657685, 0.7068416977125503), + (-0.639740098619792, -0.3018400579657685, 0.7068416977125503), + (0.3018400579657685, -0.639740098619792, 0.7068416977125503), + (-0.3018400579657685, 0.639740098619792, 0.7068416977125503), + (0.639740098619792, -0.3018400579657685, -0.7068416977125503), + (-0.639740098619792, 0.3018400579657685, -0.7068416977125503), + (0.3018400579657685, 0.639740098619792, -0.7068416977125503), + (-0.3018400579657685, -0.639740098619792, -0.7068416977125503), + ], + 10: [ + (-0.753828667197017, 0.54595190806126, -0.365621190026287), + (0.545951908061258, -0.36562119002629, -0.753828667197017), + (0.753828667197016, -0.545951908061261, -0.365621190026288), + (-0.365621190026289, -0.753828667197017, 0.545951908061259), + (-0.545951908061258, -0.365621190026288, 0.753828667197018), + (-0.365621190026289, 0.753828667197017, -0.545951908061259), + (-0.545951908061258, 0.365621190026289, -0.753828667197017), + (0.365621190026287, 0.753828667197017, 0.54595190806126), + (0.545951908061259, 0.365621190026289, 0.753828667197017), + (0.365621190026287, -0.753828667197018, -0.545951908061259), + (-0.753828667197017, -0.545951908061261, 0.365621190026288), + (0.753828667197016, 0.545951908061261, 0.365621190026287), + (0.70018101936373, -0.713151065847793, 0.034089549761256), + (-0.713151065847794, 0.034089549761254, 0.700181019363729), + (-0.70018101936373, 0.713151065847793, 0.034089549761256), + (0.034089549761255, 0.70018101936373, -0.713151065847793), + (0.713151065847793, 0.034089549761254, -0.70018101936373), + (0.034089549761257, -0.700181019363729, 0.713151065847794), + (0.713151065847794, -0.034089549761255, 0.700181019363728), + (-0.034089549761256, -0.700181019363729, -0.713151065847794), + (-0.713151065847794, -0.034089549761254, -0.700181019363729), + (-0.034089549761257, 0.700181019363729, 0.713151065847794), + (0.70018101936373, 0.713151065847793, -0.034089549761257), + (-0.700181019363729, -0.713151065847794, -0.034089549761257), + (0.276230218261792, 0.077050720725736, -0.957997939953259), + (0.077050720725735, -0.957997939953258, 0.276230218261793), + (-0.276230218261792, -0.077050720725734, -0.957997939953259), + (-0.957997939953259, 0.276230218261791, 0.077050720725738), + (-0.077050720725735, -0.957997939953259, -0.276230218261792), + (-0.957997939953258, -0.276230218261793, -0.077050720725736), + (-0.077050720725736, 0.957997939953258, 0.276230218261794), + (0.957997939953259, -0.27623021826179, 0.077050720725737), + (0.077050720725734, 0.957997939953259, -0.276230218261792), + (0.957997939953258, 0.276230218261793, -0.077050720725738), + (0.276230218261793, -0.077050720725736, 0.957997939953258), + (-0.276230218261791, 0.077050720725735, 0.957997939953259), + (0.451819102555243, -0.783355937521819, 0.42686411621907), + (-0.783355937521818, 0.426864116219071, 0.451819102555243), + (-0.451819102555243, 0.783355937521819, 0.42686411621907), + (0.426864116219071, 0.451819102555242, -0.783355937521819), + (0.783355937521818, 0.42686411621907, -0.451819102555244), + (0.426864116219072, -0.451819102555242, 0.783355937521818), + (0.783355937521819, -0.42686411621907, 0.451819102555242), + (-0.426864116219072, -0.451819102555241, -0.783355937521819), + (-0.783355937521818, -0.42686411621907, -0.451819102555243), + (-0.426864116219072, 0.451819102555241, 0.783355937521819), + (0.451819102555243, 0.783355937521818, -0.426864116219071), + (-0.451819102555242, -0.783355937521819, -0.426864116219071), + (-0.33858435995926, -0.933210037239527, 0.120331448866784), + (-0.933210037239526, 0.120331448866787, -0.33858435995926), + (0.338584359959261, 0.933210037239526, 0.120331448866786), + (0.120331448866785, -0.338584359959261, -0.933210037239526), + (0.933210037239526, 0.120331448866789, 0.33858435995926), + (0.120331448866785, 0.338584359959261, 0.933210037239526), + (0.933210037239526, -0.120331448866787, -0.338584359959262), + (-0.120331448866784, 0.338584359959262, -0.933210037239526), + (-0.933210037239526, -0.120331448866787, 0.338584359959261), + (-0.120331448866784, -0.338584359959262, 0.933210037239526), + (-0.338584359959262, 0.933210037239526, -0.120331448866784), + (0.338584359959261, -0.933210037239527, -0.120331448866783), + ], + 11: [ + (-0.674940520480437, 0.725629052064501, 0.133857284499464), + (0.09672433446143, -0.910327382989987, -0.402428203412229), + (0.906960315916358, 0.135127022135053, 0.398953221871704), + (-0.132758704758026, -0.307658524060733, 0.942189661842955), + (-0.226055801127587, -0.958831174708704, -0.171876563798827), + (0.275738264019853, -0.180692733507538, -0.944096682449892), + (0.830881650513589, 0.333278644528177, -0.445601871563928), + (-0.616471328612787, -0.2675443371664, 0.740528951931372), + (0.430277293287436, -0.892644471615357, -0.13434023290057), + (-0.690987198523076, 0.175109339053207, 0.701336874015319), + (0.810517041535507, -0.381449337547215, 0.444475565431127), + (-0.086734443854626, -0.706008517835924, -0.702872043114784), + (0.871320852056737, 0.46045780600396, 0.169642511361809), + (-0.600735266749549, 0.303266118552509, -0.739693720820614), + (-0.899100947083419, -0.418081246639828, 0.12971336924846), + (0.896927087079571, -0.188066327344843, -0.400191025613991), + (0.150494960966991, 0.903072153139254, 0.402258564791324), + (0.248601716402621, -0.224283612281953, 0.94228129975259), + (0.842584674708423, -0.510756382085546, -0.1708185707275), + (0.260034500418337, 0.209356489957684, -0.942630319215749), + (-0.058802461572434, 0.894595213188746, -0.442991732488095), + (0.061611769180132, -0.671290108790159, 0.738629528071408), + (0.982337536097614, 0.133784014710179, -0.130823555148513), + (-0.382277582532576, -0.605243847900137, 0.698243320392029), + (0.611839278216357, 0.651571608497249, 0.448449703569971), + (0.646865348569582, -0.298464129297652, -0.701772316597447), + (-0.169201016881282, 0.970430912746818, 0.172147783812972), + (-0.471725450862325, -0.47529570366279, -0.742676977621112), + (0.119369755955723, -0.984692604411347, 0.127009197228668), + (0.457289212231729, 0.796155990558714, -0.396260287026038), + (-0.813631436350979, 0.420942272793499, 0.40101307803722), + (0.287154555386871, 0.16417332397066, 0.943710432821951), + (0.746667577045155, 0.644035989066398, -0.166448713352744), + (-0.115779644740906, 0.314952464646105, -0.942019118105898), + (-0.867579212111466, 0.221916315040665, -0.445038717226738), + (0.655140022433912, -0.151162631680508, 0.740230646345257), + (0.176736512358047, 0.976002671721061, -0.12721238144483), + (0.455284607701078, -0.55278635410423, 0.697956426080188), + (-0.432023930219742, 0.781838026058859, 0.449538234998843), + (0.485961267092557, 0.525163287076294, -0.698602296584415), + (-0.975758639968897, 0.138431863354196, 0.1695042646494), + (0.308602378401872, -0.593188152818631, -0.743567338847214), + (0.972979693579006, -0.191167383224118, 0.129481842256537), + (-0.614624689780931, 0.68217777986423, -0.39606813475866), + (-0.653028964396532, -0.644975511259979, 0.396937981974668), + (-0.070378900922493, 0.320878001965403, 0.944502047726543), + (-0.381252925250545, 0.909662131759037, -0.164805986030565), + (-0.332341796304234, -0.009834857390798, -0.943107738283054), + (-0.477746621168896, -0.755138676192789, -0.448913962446598), + (0.343877558432071, 0.574039599276676, 0.743119615720828), + (-0.873212544495548, 0.47009394139203, -0.128497231106812), + (0.664216892966437, 0.259987346974329, 0.700872669256879), + (-0.878489109322641, -0.170673846340671, 0.446236846278739), + (-0.347082716608212, 0.626648635925969, -0.697742842975825), + (-0.433716795977713, -0.885744934523588, 0.165365207503367), + (0.661861683362982, 0.112512128799614, -0.741134355544863), + (0.482068945127674, 0.865869532174741, 0.133714192945202), + (-0.8374660393934, -0.372486946227971, -0.399880116725617), + (0.410355219266256, -0.82161905066793, 0.39566492086167), + (-0.329899568015879, 0.02926988290883, 0.943562159572669), + (-0.982429034616553, -0.080964254903198, -0.168160582094488), + (-0.090370421487683, -0.316160436207578, -0.944391743662116), + (0.571959920493404, -0.686312971502271, -0.449262010965652), + (-0.442021476996821, 0.502111749619808, 0.743303978710785), + (-0.716515724344093, -0.684793506171761, -0.13290248557738), + (-0.044218043628816, 0.709851625611568, 0.702961900983442), + (-0.110556556362806, -0.889624975730714, 0.443107944412334), + (-0.701028131184281, -0.134257385503649, -0.700381691455451), + (0.707841110014082, -0.686721956709281, 0.165450648676302), + (0.099860111408803, 0.666551337869757, -0.738740327945793), + ], + 12: [ + (-0.893804977761136, -0.426862191124497, 0.137482113446834), + (-0.426862191241092, 0.137482113445288, -0.893804977705691), + (0.893804977770157, 0.426862191128157, 0.137482113376823), + (0.1374821132964, -0.893804977739491, -0.426862191218271), + (0.426862191272731, 0.137482113377345, 0.893804977701032), + (0.137482113529033, 0.893804977707775, 0.426862191209756), + (0.426862191185983, -0.137482113474993, -0.893804977727441), + (-0.137482113324291, 0.893804977725279, -0.426862191239047), + (-0.426862191217414, -0.137482113347288, 0.893804977732073), + (-0.137482113501071, -0.893804977693655, 0.426862191248328), + (-0.893804977672548, 0.426862191328071, -0.137482113390703), + (0.893804977663553, -0.42686219133326, -0.137482113433065), + (0.983086600385574, 0.022300380107522, -0.181778516853323), + (0.022300380232394, -0.181778516808726, 0.983086600390988), + (-0.983086600396613, -0.022300380113323, -0.181778516792915), + (-0.181778516710471, 0.983086600409631, 0.022300380211455), + (-0.022300380272854, -0.181778516836686, -0.9830866003849), + (-0.18177851693601, -0.983086600368179, -0.022300380200376), + (-0.0223003801708, 0.181778516841875, 0.983086600386256), + (0.181778516710979, -0.983086600409044, 0.022300380233212), + (0.022300380212558, 0.181778516804081, -0.983086600392297), + (0.181778516934384, 0.983086600367503, -0.022300380243431), + (0.983086600391629, -0.022300380332372, 0.181778516792996), + (-0.98308660038057, 0.022300380337865, 0.181778516852128), + (-0.897951986971875, 0.376695603035365, 0.227558018419664), + (0.376695602927528, 0.227558018339206, -0.897951987037503), + (0.897951986986053, -0.376695603028569, 0.227558018374966), + (0.227558018305554, -0.897951987041904, 0.376695602937366), + (-0.376695602875261, 0.227558018455254, 0.89795198703002), + (0.227558018486567, 0.89795198699048, -0.3766956029506), + (-0.376695602982511, -0.22755801836891, -0.89795198700691), + (-0.227558018280939, 0.897951987054767, 0.376695602921573), + (0.376695602931437, -0.22755801842558, 0.897951987013974), + (-0.227558018511349, -0.897951987002348, -0.376695602907339), + (-0.897951987072194, -0.376695602830637, -0.227558018362707), + (0.897951987057819, 0.376695602823051, -0.227558018431989), + (-0.171330151245221, 0.459786194953055, -0.871345301361568), + (0.459786194843117, -0.871345301414649, -0.171330151270292), + (0.171330151191219, -0.459786194982334, -0.871345301356736), + (-0.871345301364754, -0.171330151162981, 0.459786194977662), + (-0.459786195042432, -0.871345301303738, 0.171330151299472), + (-0.871345301353407, 0.171330151362727, -0.459786194924734), + (-0.459786194855202, 0.87134530140841, -0.171330151269592), + (0.871345301392835, 0.171330151178183, 0.45978619491878), + (0.459786195054412, 0.871345301309038, 0.171330151240368), + (0.871345301325486, -0.171330151377355, -0.459786194972196), + (-0.17133015129661, -0.459786194913003, 0.871345301372597), + (0.171330151350736, 0.459786194942983, 0.871345301346135), + (-0.397191702297223, -0.548095590649226, -0.736091010091219), + (-0.548095590778902, -0.736091010056557, -0.397191702182515), + (0.397191702250221, 0.548095590625205, -0.736091010134467), + (-0.736091010174764, -0.397191702137083, -0.548095590653075), + (0.548095590610212, -0.736091010169131, 0.397191702206669), + (-0.736091010049194, 0.397191702305889, 0.548095590699385), + (0.548095590752529, 0.736091010044117, -0.397191702241962), + (0.736091010139925, 0.397191702119602, -0.548095590712531), + (-0.548095590584386, 0.736091010182625, 0.3971917022173), + (0.736091010083782, -0.39719170228798, 0.548095590665912), + (-0.39719170212526, 0.548095590740419, 0.736091010116106), + (0.397191702171386, -0.548095590716295, 0.736091010109179), + (0.379474725534956, 0.69627727809449, 0.609259291836815), + (0.696277278210441, 0.609259291787114, 0.379474725402001), + (-0.379474725495576, -0.696277278074161, 0.609259291884576), + (0.609259291925953, 0.379474725376213, 0.696277278103008), + (-0.696277278071056, 0.609259291933888, -0.379474725422102), + (0.60925929179591, -0.379474725515542, -0.696277278140864), + (-0.696277278185906, -0.609259291774849, 0.379474725466713), + (-0.609259291882878, -0.379474725353089, 0.696277278153303), + (0.696277278046548, -0.609259291946589, -0.379474725446676), + (-0.609259291838737, 0.379474725493095, -0.696277278115623), + (0.37947472533629, -0.696277278181595, -0.609259291861008), + (-0.379474725375237, 0.696277278161216, -0.609259291860039), + (-0.678701446470328, 0.729764213479081, 0.082513873284097), + (0.729764213389772, 0.082513873179234, -0.678701446579104), + (0.678701446474772, -0.72976421347722, 0.082513873263995), + (0.082513873217671, -0.678701446552547, 0.729764213410125), + (-0.729764213370974, 0.082513873368402, 0.678701446576318), + (0.082513873326892, 0.678701446534692, -0.729764213414381), + (-0.729764213431284, -0.082513873201736, -0.678701446531733), + (-0.082513873171694, 0.678701446577399, 0.72976421339221), + (0.729764213412797, -0.08251387334668, 0.67870144653399), + (-0.082513873373655, -0.678701446558336, -0.729764213387104), + (-0.678701446641541, -0.729764213324827, -0.082513873240061), + (0.678701446637016, 0.729764213321344, -0.082513873308075), + ], + 13: [ + (0.276790129286922, -0.235256466916603, 0.931687511509759), + (0.198886780634501, 0.360548603139528, 0.911289609983006), + (-0.258871339062373, 0.204230077441409, 0.944073993540935), + (-0.20028291392731, -0.228346161950354, 0.952756414153864), + (-0.883545166667525, -0.414277696639041, -0.218453492821483), + (0.397750057908559, -0.901619535998689, -0.16993264471327), + (0.876539487069282, 0.434392104192327, -0.207321073274483), + (-0.411742357517625, 0.88489597883979, -0.217778184534166), + (0.501114093867204, 0.377868932752059, 0.778524074507957), + (-0.394238847790386, 0.473687133880952, 0.787525383774109), + (-0.495364292002136, -0.406429808740612, 0.767742814213388), + (0.370186583802172, -0.559306270968252, 0.741713144300723), + (0.411742357517961, -0.884895978839253, 0.217778184535715), + (0.883545166668397, 0.414277696639157, 0.218453492817737), + (-0.39775005791059, 0.901619535997218, 0.169932644716324), + (-0.876539487069878, -0.434392104191278, 0.20732107327416), + (-0.69101430131565, -0.702815226887987, -0.168967429499392), + (0.684400344460127, -0.714441044251654, -0.145499700314004), + (0.660710489482765, 0.731357715035063, -0.169048932993191), + (-0.773611287956309, 0.615222357857778, -0.151746583284428), + (0.683629784686022, -0.21996733132084, -0.695891292258878), + (0.256574099503526, 0.681472791071418, -0.685393730999406), + (-0.644474509637892, 0.354062227985534, -0.677711254990588), + (-0.220535080416141, -0.731547754140859, -0.645137320046912), + (0.394238847792041, -0.473687133882522, -0.787525383772336), + (0.495364292000968, 0.406429808741285, -0.767742814213785), + (-0.370186583802439, 0.559306270970003, -0.74171314429927), + (-0.50111409386464, -0.377868932752239, -0.77852407450952), + (-0.488574873968534, -0.006884557978444, -0.872494811095214), + (0.055542048727444, -0.584131720249991, -0.809756268404849), + (0.526812107464791, 0.049707819617671, -0.848527039107984), + (0.004245864108125, 0.4886546223943, -0.872466980836902), + (-0.710317361514613, -0.479530914625401, 0.515266288291253), + (0.521404384476562, -0.728039165451723, 0.445080264016476), + (0.738099355388852, 0.407803273205931, 0.53749961110413), + (-0.496057991262554, 0.699670113703365, 0.514186932248264), + (-0.973220809307327, 0.194260751789571, -0.122898399685852), + (-0.376203572666605, -0.908865964003535, -0.180093118660339), + (0.914477900370762, -0.368657988534049, -0.166797653531193), + (0.28746218785413, 0.946817914340553, -0.144572914606861), + (-0.098900669929334, 0.99509705928004, 0.000707177308311), + (-0.986068201425202, -0.161328561779779, 0.04052896855503), + (0.098900669927371, -0.995097059280236, -0.000707177307522), + (0.98606820142538, 0.161328561777872, -0.040528968558297), + (0.815232440848265, 0.131832381174928, -0.563929331266187), + (-0.113644567080339, 0.787251605615581, -0.606069155978764), + (-0.76050444170531, -0.010569874890521, -0.649246695710724), + (0.179848227912241, -0.83248278540524, -0.52404868754798), + (-0.92768989180951, -0.047241289482188, -0.370350813692261), + (-0.062273759773745, -0.944434685686409, -0.322746190242513), + (0.939840260740896, -0.044569841802216, -0.33869427732427), + (-0.00824273878155, 0.93408705946015, -0.356950168239866), + (-0.287462187854123, -0.94681791433958, 0.144572914613243), + (0.973220809307003, -0.194260751794934, 0.122898399679932), + (0.376203572664097, 0.908865964003331, 0.180093118666611), + (-0.914477900372906, 0.368657988531957, 0.16679765352406), + (-0.198886780630987, -0.360548603140065, -0.91128960998356), + (0.258871339064112, -0.204230077444254, -0.944073993539843), + (0.200282913924527, 0.228346161951352, -0.952756414154209), + (-0.276790129284401, 0.235256466920766, -0.931687511509456), + (0.496057991258595, -0.69967011370777, -0.514186932246089), + (0.710317361512836, 0.479530914628761, -0.515266288290576), + (-0.521404384476695, 0.728039165452453, -0.445080264015126), + (-0.738099355384499, -0.407803273209131, -0.537499611107679), + (-0.815232440849446, -0.131832381170712, 0.563929331265466), + (0.113644567080183, -0.787251605613717, 0.606069155981215), + (0.760504441709935, 0.010569874889864, 0.649246695705317), + (-0.179848227916839, 0.832482785402468, 0.524048687550806), + (0.644474509638734, -0.354062227985804, 0.677711254989647), + (0.220535080413518, 0.73154775414237, 0.645137320046095), + (-0.683629784685343, 0.219967331325312, 0.695891292258132), + (-0.256574099500943, -0.681472791069379, 0.6853937310024), + (0.00824273878347, -0.934087059459458, 0.356950168241634), + (0.927689891812602, 0.047241289479133, 0.370350813684907), + (0.062273759768788, 0.944434685686016, 0.322746190244617), + (-0.939840260741931, 0.04456984180196, 0.338694277321433), + (-0.684400344460716, 0.714441044251305, 0.145499700312953), + (-0.660710489482671, -0.731357715034246, 0.169048932997096), + (0.773611287955743, -0.615222357858877, 0.151746583282855), + (0.691014301313431, 0.702815226889319, 0.168967429502926), + (0.823586023578098, -0.394634588904438, 0.407393670798948), + (0.494068620358303, 0.708839608416629, 0.503430837272612), + (-0.75887513050105, 0.450605887274021, 0.470173234734822), + (-0.431499072601357, -0.787935048711447, 0.439279989706176), + (0.488574873974618, 0.006884557979146, 0.872494811091801), + (-0.055542048725634, 0.584131720247444, 0.80975626840681), + (-0.52681210746758, -0.049707819615904, 0.848527039106356), + (-0.004245864106237, -0.488654622389235, 0.872466980839748), + (-0.49406862035774, -0.708839608420214, -0.503430837268118), + (0.758875130496518, -0.450605887275878, -0.470173234740358), + (0.431499072601226, 0.787935048714215, -0.43927998970134), + (-0.823586023577754, 0.394634588903444, -0.407393670800605), + (-0.05223814787874, -0.056184830047506, -0.997052877624217), + (0.052238147881538, 0.05618483004769, 0.99705287762406), + ], + 14: [ + (-0.625520988160254, -0.7673610045544, 0.14099851793647), + (-0.76724274137005, 0.141111638293461, -0.625640536852518), + (0.625492928633992, 0.767336602497947, 0.141255565185161), + (0.141259978285753, -0.625497336538309, -0.767332196977417), + (0.767217177597722, 0.141142445065633, 0.625664936367606), + (0.140994104732436, 0.625522897885037, 0.76736025871308), + (0.767367121351956, -0.141003470846495, -0.625512367805189), + (-0.141107421738627, 0.625630007825311, -0.767252102531351), + (-0.767341557579575, -0.14125061251247, 0.625487968290521), + (-0.141146661279494, -0.625655569171042, 0.767224040795719), + (-0.625631916042134, 0.767247698300479, -0.141122907715456), + (0.625659975568208, -0.76722329624468, -0.141131175405856), + (0.557188048071509, -0.044753456478336, -0.829179478291342), + (-0.044855878114542, -0.829283214592494, 0.55702540354432), + (-0.557023176560509, 0.04489683751235, -0.829282493940292), + (-0.82927698135379, 0.557030331904205, -0.044909882380585), + (0.04500608513203, -0.829178759033645, -0.557168769645708), + (-0.829184990273806, -0.557180524667655, 0.044744997884746), + (0.044745112627007, 0.829186887034329, 0.557177692721376), + (0.829285903726696, -0.557022572534015, -0.044841315410966), + (-0.044895319644503, 0.829275086591665, -0.557034326619468), + (0.829176067900875, 0.557172765298515, 0.045006199906779), + (0.55701504778817, 0.04485436035495, 0.829290252501915), + (-0.55717991929916, -0.044997741388974, 0.829171719729799), + (-0.256065565410913, 0.860770382492113, 0.439891776275906), + (0.860817193749452, 0.439942893453515, -0.255820267854343), + (0.255978113844099, -0.860846435024418, 0.439793838677361), + (0.439780410927513, -0.2559784640674, 0.860853190792787), + (-0.86089686693789, 0.439742722239698, 0.255896312466096), + (0.439905203705212, 0.256058129693811, -0.860765732339981), + (-0.860766305814323, -0.439898638597135, -0.256067480432698), + (-0.439951565414546, 0.255829619022141, 0.860809982586329), + (0.860845979002674, -0.439786977094207, 0.255991435820162), + (-0.439734049218326, -0.255909284650278, -0.860897441039197), + (-0.255822182689152, -0.860816739802146, -0.439942668220034), + (0.255909634255987, 0.860892792334745, -0.43974294673258), + (-0.214847470746312, -0.032398468989078, 0.976110087808274), + (-0.032361689068532, 0.976149872834738, -0.214672184610297), + (0.214653391258715, 0.032229687143749, 0.976158372851326), + (0.976156890684175, -0.214662164023714, -0.032216146673044), + (0.032184871976157, 0.976118589466139, 0.214840948877335), + (0.976111569264229, 0.214838964338531, 0.032410241444204), + (0.032404387317597, -0.976112740167589, -0.214834527404447), + (-0.97615046971745, 0.214667748038907, -0.032373112644706), + (-0.032227570225248, -0.976155722133346, 0.214665763138194), + (-0.976117990230955, -0.214844548351237, 0.032179017872419), + (-0.214659241112501, 0.032359572226461, -0.976152789418913), + (0.21485332060012, -0.032190790383644, -0.976115671240647), + (-0.531657953418075, -0.827333953094149, -0.181268724894593), + (-0.827232187812406, -0.181173291587112, -0.531848799813059), + (0.531693969367769, 0.827365274479422, -0.181019958909332), + (-0.181013937184585, -0.531695771783126, -0.827365433658478), + (0.82726500032424, -0.181115392520565, 0.53181748168959), + (-0.181274746488222, 0.531662962384716, 0.827329414872902), + (0.827337912964399, 0.181265235803751, -0.531652980863198), + (0.181178425057189, 0.531838819184424, -0.827237480233043), + (-0.827370725476244, 0.181023448305968, 0.53168429898609), + (0.181110258615433, -0.531806009787378, 0.82727349901848), + (-0.531843826870477, 0.827237640803175, 0.181162991414264), + (0.531807810920692, -0.827268962188108, 0.181125692390543), + (-0.660052978431453, -0.64107030142389, -0.391610692264717), + (-0.640943278162024, -0.391490626360013, -0.660247532105318), + (0.660130816198753, 0.641137993287446, -0.391368597447617), + (-0.391366127194665, -0.660129990434437, -0.641140351415881), + (0.64101419265168, -0.391488664009925, 0.660179847292265), + (-0.391613162232706, 0.660059082674287, 0.641062507518011), + (0.641074532205542, 0.391604771858404, -0.660052381872206), + (0.391493582576226, 0.660240832413506, -0.64094837390819), + (-0.641145446695277, 0.391374518513393, 0.660120066685087), + (0.391485706851, -0.660169924653076, 0.641026217806203), + (-0.660246935065265, 0.640950733115446, 0.391479427883123), + (0.660169097297863, -0.641018424977353, 0.391499861829449), + (-0.887809544786451, -0.296234001309576, 0.352192601646022), + (-0.296066792023988, 0.352356402512996, -0.887800326801429), + (0.88773949759831, 0.296173084548554, 0.352420329142482), + (0.352416311777685, -0.887743835667625, -0.296164861904896), + (0.296002975181616, 0.352256528867817, 0.887861237217634), + (0.352196618754376, 0.887807646454613, 0.296234914611202), + (0.29624330876316, -0.35220289417087, -0.887802356017778), + (-0.352357435214616, 0.887795037857324, -0.296081422255585), + (-0.296179491920463, -0.352410037210515, 0.887741445601714), + (-0.352255495317536, -0.887858848644387, 0.296011369549315), + (-0.887793137385775, 0.296073200109863, -0.352369132285204), + (0.887863184573985, -0.29601228334985, -0.352243798503465), + (-0.26223504413332, -0.963196832316083, -0.059030871962556), + (-0.963146753177879, -0.05898607663367, -0.262428989645347), + (0.262246759182379, 0.963207020345643, -0.058812186281487), + (-0.058802018270752, -0.262250202293796, -0.963206703695603), + (0.963157426232092, -0.058856981708733, 0.262418802676392), + (-0.059041039930677, 0.26223953025181, 0.9631949877243), + (0.963198910461706, 0.05903143538266, -0.262227284091577), + (0.058993521775316, 0.262416743924601, -0.963149633700058), + (-0.963209583515922, 0.058811622961071, 0.262237471059664), + (0.058849536426177, -0.26240607188442, 0.963161349671286), + (-0.262421229412009, 0.963149318669471, 0.058978710569357), + (0.262409514362814, -0.96315950669899, 0.058864347675232), + (-0.715507563967586, -0.551203770138786, 0.429212452859839), + (-0.551069607492995, 0.429343180584727, -0.715532473744488), + (0.715422202362423, 0.551129535145429, 0.429450006237378), + (0.429450819917231, -0.715428271411138, -0.551121022769127), + (0.5509918383738, 0.429319279234025, 0.715606701005125), + (0.429211638866897, 0.7155060331503, 0.551206391097486), + (0.551211869430264, -0.429219462039715, -0.715497119774448), + (-0.429341247317559, 0.715523561571074, -0.551082685436994), + (-0.551134100310698, -0.42944299777979, 0.715422892513669), + (-0.429321211466422, -0.715601323311163, 0.550997317108093), + (-0.715522029029489, 0.551074173985694, -0.429354726001132), + (0.71560739063481, -0.550999938995098, -0.429307733096245), + ], + 15: [ + (0.854403279867469, -0.505354134007206, 0.120881076242474), + (-0.50543491755569, 0.120816219805996, 0.85436466754382), + (-0.854386776665562, 0.505324765203946, 0.121120260611542), + (0.120833358636621, 0.854397789834015, -0.505374827397788), + (0.505397909754575, 0.121184507524897, -0.854334400543285), + (0.121167891781777, -0.854359592169892, 0.505359307095908), + (0.505550243990606, -0.121029099883223, 0.85426629793203), + (-0.120901230058257, -0.8542712308899, -0.505572503845152), + (-0.505512743080475, -0.120971801893292, -0.854296605243135), + (-0.121100221709937, 0.854233310670545, 0.505588950870808), + (0.854228086018077, 0.50562286374044, -0.120995440909188), + (-0.854244251915001, -0.505593339178042, -0.121004683582819), + (-0.264987898778375, 0.883813698575362, -0.385557725524417), + (0.883849543661418, -0.385514772323787, -0.264930829632268), + (0.26493557758679, -0.883717531264112, -0.385814028600849), + (-0.385585969828214, -0.26499188213212, 0.883800182323873), + (-0.883729220902574, -0.385857204128221, 0.264833687708496), + (-0.385785570894326, 0.264874341279828, -0.883748310675226), + (-0.88388533425828, 0.385579930632135, -0.264716514363662), + (0.385705565188833, 0.2647713629504, 0.883814088111154), + (0.883764779677347, 0.385791993289806, 0.26481003023928), + (0.385667159435638, -0.264653128949093, -0.883866258814251), + (-0.26465693400637, -0.883897033692944, 0.385594010730408), + (0.264708980625706, 0.88380076582133, 0.385778902883153), + (-0.973352164893031, 0.228026239253951, 0.024281624940474), + (0.228112345886926, 0.024344549147457, -0.973330416960638), + (0.973355442809202, -0.228032180463189, 0.024093705130315), + (0.02435678809852, -0.973342833359315, 0.228058053182924), + (-0.228119646411613, 0.024031592823588, 0.973336483168797), + (0.024019836271027, 0.973350319061891, -0.228061842156086), + (-0.228243300844141, -0.024101340294447, -0.97330576953791), + (-0.024325527828938, 0.973285859331229, 0.228304412400888), + (0.228251100231509, -0.024273911824481, 0.973299651930403), + (-0.024050630999231, -0.973293331950969, -0.228301680082122), + (-0.973284068418876, -0.228330589256741, -0.024150862752081), + (0.973280766897442, 0.228336792285628, -0.024225153791815), + (0.176494164360597, -0.643915961757687, 0.744460908403072), + (-0.643955474734765, 0.744429879896195, 0.176480878502065), + (-0.176394545795758, 0.643730246363376, 0.744645106161623), + (0.744482176984942, 0.176540768465681, -0.643878595094843), + (0.643722173544997, 0.744675565408302, -0.176295392935637), + (0.7446232602985, -0.176308754645389, 0.643779017410342), + (0.643979646991902, -0.744473128172097, 0.176210032886433), + (-0.744568815199564, -0.17638194509082, -0.643821938798528), + (-0.643745300418696, -0.744632317149074, -0.176393595252333), + (-0.744536607308663, 0.176149174255325, 0.643922905933989), + (0.17619570702358, 0.643971118562494, -0.744483895919737), + (-0.176295790346183, -0.643784799068587, -0.744621331143847), + (-0.538268091669357, -0.714443097207928, 0.447033021534844), + (-0.714408571342234, 0.447040121797618, -0.538308018420605), + (0.538328211219438, 0.714331668550684, 0.447138685768607), + (0.447120197809599, -0.538252620284606, -0.714400199795228), + (0.714269338389924, 0.44713116789972, 0.538417153263762), + (0.447050760030329, 0.538390645652783, 0.714339646547694), + (0.714335378036911, -0.44690804426239, -0.538514779424324), + (-0.44721697306067, 0.538430741435079, -0.714205373603506), + (-0.714197054202002, -0.447264479166707, 0.53840231560137), + (-0.446955147518652, -0.538568616287532, 0.714265316011294), + (-0.53855313587161, 0.7142734853459, -0.446960745451628), + (0.538492423492547, -0.714162749879013, -0.44721077416177), + (-0.854262559171519, -0.121196786481334, 0.505516388403308), + (-0.121135225917774, 0.505562840819898, -0.854243800693903), + (0.854330941426722, 0.121071937454801, 0.505430735592792), + (0.50560092757097, -0.854220500665318, -0.12114057240441), + (0.120980483865727, 0.505385380088754, 0.854370727562784), + (0.505347417325379, 0.854380006035553, 0.121073502837149), + (0.121020187472529, -0.505348640639146, -0.854386836057462), + (-0.505616684568734, 0.854250931602746, -0.120859894677939), + (-0.120865731027902, -0.505598482368904, 0.854260879175297), + (-0.50533076030876, -0.85441039802093, 0.120928468275617), + (-0.854368086396689, 0.120928425096259, -0.505402304061426), + (0.854300186149896, -0.120804448918389, -0.50554671106217), + (0.744463297304691, 0.643945879165515, 0.176374895243003), + (0.643874092586096, 0.176352654639277, 0.744530653565125), + (-0.744439438351756, -0.643989673955645, 0.176315689786883), + (0.176272538481235, 0.744499406704124, 0.643932159155441), + (-0.643927227994187, 0.176337348613599, -0.744488323972679), + (0.176417156092134, -0.744445359352701, -0.643955040336351), + (-0.643773276436742, -0.176537581494637, 0.74457400630557), + (-0.176186289075531, -0.744659935538757, 0.643770123526409), + (0.643827610309512, -0.176153056179147, -0.744618096074686), + (-0.176503857743247, 0.744606426388638, -0.643745025598251), + (0.744642563797528, -0.643711305756388, -0.176474380640507), + (-0.744666089545738, 0.643755051780831, -0.176215346628265), + (-0.228336071531986, 0.973285242484899, -0.024051511354805), + (0.97330645157172, -0.02400727784605, -0.228250305452786), + (0.228333198106367, -0.973279105669999, -0.024325564920999), + (-0.024087022235214, -0.228306334617004, 0.973291340212985), + (-0.973298936142401, -0.024368394387641, 0.228244084828147), + (-0.024288551979359, 0.228299553239789, -0.97328792257649), + (-0.973337174231582, 0.024064219708824, -0.228113258248362), + (0.024218299052141, 0.228063963157171, 0.973344904286279), + (0.973329707418057, 0.024311563193311, 0.228118891266323), + (0.024157446706033, -0.228056100974521, -0.97334825862943), + (-0.228026586903388, -0.973357047279715, 0.0240818225246), + (0.228030283046768, 0.973350913461235, 0.024293811512186), + (0.714188221641478, 0.538577770578359, -0.447067298186109), + (0.538527478643698, -0.447091804432198, 0.714210804423472), + (-0.714248768779612, -0.538467220657919, -0.447103733571689), + (-0.447161335031565, 0.714165750281327, 0.538529499264337), + (-0.538389584312319, -0.447080411787951, -0.714321888856505), + (-0.447010969457209, -0.714305113088417, -0.538469496444014), + (-0.538432015415055, 0.446913681352591, 0.714394237235966), + (0.447232748299163, -0.714300498031757, 0.538291433482237), + (0.538293039926151, 0.447257878415203, -0.714283552493402), + (0.446938450981038, 0.714439434305571, -0.538351479745163), + (0.714416613789271, -0.538354159540915, 0.44697170027516), + (-0.714357130519037, 0.538243224338143, 0.447200314770336), + (-0.88382262479637, 0.264636203965444, 0.385778754532718), + (0.264703577640278, 0.38583706665855, -0.88377699335113), + (0.883874390523727, -0.264731681104579, 0.385594604209984), + (0.385842662030848, -0.883779863837697, 0.264685837233447), + (-0.26482277877838, 0.385535295796556, 0.883872972510847), + (0.385530295763018, 0.883900556837248, -0.264737977388365), + (-0.264823043649479, -0.385616295049092, -0.883837557781314), + (-0.385806659371906, 0.883713819704025, 0.264958688191976), + (0.264941401779006, -0.385755452567231, 0.883741356075422), + (-0.385565266715756, -0.883834666939721, -0.264906977291953), + (-0.883791950673863, -0.264913750483099, -0.38565851828926), + (0.883739852971877, 0.26500888802554, -0.385712537437807), + ], + 16: [ + (0.938311825813856, -0.17507925577492, -0.298191501782276), + (-0.175109632245629, -0.298282531121024, 0.938277223598034), + (-0.938311652301346, 0.175147761450008, -0.298151815044902), + (-0.298182757815715, 0.938327057553728, -0.175012502421904), + (0.175097712410131, -0.298058347845738, -0.938350687316958), + (-0.298185477757762, -0.938323612741539, 0.175026336949732), + (0.175121225661409, 0.298070999742225, 0.938342280532811), + (0.298159022282375, -0.938297484887434, -0.175211378870018), + (-0.175136638135111, 0.298288500226525, -0.938270285480331), + (0.298175056505462, 0.938292628074833, 0.175210101816042), + (0.938309721676758, 0.175091137054814, 0.298191146635404), + (-0.938307020714082, -0.175144295988174, 0.298168426332282), + (0.318319389865683, -0.189552295411868, 0.928839433561922), + (-0.189466106261457, 0.928833946336168, 0.318386706242113), + (-0.318293314473071, 0.18936285961738, 0.928887007853633), + (0.928852943553566, 0.318350700348959, -0.189433473386317), + (0.189441607397533, 0.928892798895752, -0.318229548512164), + (0.928866264406345, -0.318313837307129, 0.189430102746667), + (0.18945182591494, -0.928887156552102, 0.318239934719146), + (-0.928865750332054, -0.318289122686796, -0.189474146625178), + (-0.189481041982253, -0.928834132900175, -0.318377273511944), + (-0.928863874908086, 0.318277395441538, 0.189503038080361), + (0.318275484124591, 0.18957282380822, -0.92885028970154), + (-0.318345902583112, -0.189353418017315, -0.928870911049379), + (0.415270907116288, 0.626546860524453, 0.659537038588256), + (0.626612654947257, 0.659451415891007, 0.415307609777736), + (-0.415241828112963, -0.626676394380167, 0.659432271664102), + (0.659494217922308, 0.41519684716212, 0.626641009377521), + (-0.626618996427069, 0.659521812332477, -0.415186238180433), + (0.659478785687794, -0.415192215022902, -0.626660319321504), + (-0.626602233185435, -0.65952877581014, 0.415200475969626), + (-0.659472693683341, -0.415326178073293, 0.626577953724091), + (0.626606052873236, -0.65944383659479, -0.415329605108723), + (-0.659498633823103, 0.415315781516604, -0.626557542136963), + (0.415250963158486, -0.626542854390271, -0.659553401331872), + (-0.415267233073285, 0.626674158557439, -0.659418398387537), + (0.081476869754028, 0.884767493032223, 0.458855100188022), + (0.88480215017059, 0.458780629597686, 0.081519868495058), + (-0.08148097265168, -0.88484396510395, 0.458706887363658), + (0.458778051156021, 0.08139667888042, 0.884814828336823), + (-0.884809515892886, 0.45878451702782, -0.081417980329578), + (0.458732327572868, -0.081386172952098, -0.884839500978449), + (-0.884806469025575, -0.458784799888689, 0.081449492089205), + (-0.458770768146743, -0.081567624478124, 0.884802862185155), + (0.884821176813587, -0.458741101923224, -0.081535798692882), + (-0.458810899116744, 0.081573887356361, -0.884781475706435), + (0.081470600041761, -0.884777903494754, -0.458836139396478), + (-0.081497545017818, 0.88485010959699, -0.458692090298344), + (-0.722581612146772, 0.69116944690793, -0.012673178305347), + (0.691146231887784, -0.012722477090735, -0.722602950951623), + (0.722589739174094, -0.691157232223568, -0.012874361552029), + (-0.012719991090033, -0.722649829139429, 0.691097262526357), + (-0.6911640879369, -0.012832809701898, 0.722583920760425), + (-0.012740894622282, 0.722658126679523, -0.691088200990487), + (-0.691184825451665, 0.012806932405418, -0.722564543516851), + (0.01278690708865, 0.722509435119358, 0.69124280189425), + (0.691166758903022, 0.012679269543203, 0.722584076430794), + (0.012798402734516, -0.722517774658893, -0.691233872281593), + (-0.722587198973115, -0.691163495604889, 0.01267920436853), + (0.722578352800658, 0.691170335944389, 0.012809792129789), + (0.560117573995459, 0.806868022890413, 0.187702682288658), + (0.806883478716379, 0.18757144265397, 0.560139273462648), + (-0.560134093540899, -0.806891631206385, 0.18755184014617), + (0.187652131237362, 0.560025149416763, 0.806943932168034), + (-0.806885441512999, 0.18768574188158, -0.560098157976558), + (0.187630222901067, -0.560004720839195, -0.806963203679022), + (-0.806874677594158, -0.187697516958668, 0.560109718523856), + (-0.187614808802038, -0.560215760321792, 0.806820293129301), + (0.806892320702248, -0.18757613331143, -0.560124965524367), + (-0.187636487681617, 0.56022497423671, -0.806808853900342), + (0.56009182108872, -0.806880865199227, -0.18772432267788), + (-0.560129384097476, 0.806896245083186, -0.187546054987136), + (-0.099485634221032, -0.358895129517995, -0.928060824834181), + (-0.359050794288811, -0.927994608087772, -0.099541621850345), + (0.099434389660615, 0.359143761945999, -0.927970129049474), + (-0.928019026720099, -0.09942019096838, -0.359021324816913), + (0.358990815531993, -0.928035748444477, 0.099374262124424), + (-0.928007207203491, 0.099420259668564, 0.359051856067911), + (0.359002982562248, 0.928031348467288, -0.099371398165657), + (0.928017938922059, 0.099510949379702, -0.358998991631458), + (-0.359042863742385, 0.92799619207621, 0.099555459356689), + (0.928013665632084, -0.099489549105096, 0.359015969030581), + (-0.099451875312545, 0.358926751348054, 0.928052213867059), + (0.099465503317397, -0.359120063291987, 0.927975966170987), + (0.787833199437607, 0.557450082325166, -0.261855409681697), + (0.557405388687852, -0.261977292048617, 0.787824302184578), + (-0.787861477876718, -0.557364111687839, -0.26195331393273), + (-0.261861028070608, 0.787802657602316, 0.557490604990374), + (-0.557427204478003, -0.261835304855293, -0.787856068605919), + (-0.261850091868655, -0.787804146924511, -0.557493637162722), + (-0.557398047481063, 0.261806624190095, 0.787886227950765), + (0.26192814680606, -0.787893374500188, 0.557330849971047), + (0.557399834363592, 0.261935778537884, -0.787842035292097), + (0.261909535328364, 0.787908371337394, -0.55731839524686), + (0.787858967733566, -0.557444321449493, 0.261790136264747), + (-0.787856023927293, 0.557369329488324, 0.261958615256708), + (-0.507282732168614, -0.717049946047353, -0.478020506377115), + (-0.71706431400176, -0.477906271006066, -0.507370048109131), + (0.507331753192767, 0.71711626280308, -0.477868975583995), + (-0.477891616916408, -0.50725750267016, -0.717153699332196), + (0.717108744361459, -0.47798851765986, 0.507229756368514), + (-0.477913676926975, 0.507235340412842, 0.717154674280526), + (0.717103637758922, 0.477942092943937, -0.507280719627002), + (0.477949791330649, 0.507362311781387, -0.7170407809538), + (-0.717073605236621, 0.477889924387354, 0.507372313830785), + (0.477966885504482, -0.507396895429057, 0.717004914118516), + (-0.507289494490155, 0.717039874321013, 0.478028437871252), + (0.507342973335893, -0.717147616692481, 0.47781000751239), + (-0.469705390085658, -0.33624876406351, 0.816280353304085), + (-0.336180458859188, 0.816354017519737, -0.4696262526314), + (0.469729267279509, 0.336087427571651, 0.816333054879763), + (0.816299320102214, -0.469688480988201, -0.336226338688182), + (0.336166188592078, 0.816261044646191, 0.469798042397566), + (0.816308187841148, 0.469684487990511, 0.336210386818421), + (0.336161196424763, -0.816254520485116, -0.469812949806501), + (-0.81631474754769, 0.469749196906201, -0.336104038866138), + (-0.336166711539314, -0.816355082377068, 0.469634242288587), + (-0.816302029136435, -0.469752338316787, 0.33613053695499), + (-0.469725914764869, 0.336254309274991, -0.816266258332602), + (0.469715709020586, -0.336082571137018, -0.816342855715183), + (0.220975783117544, 0.56198189964132, -0.797085972622227), + (0.56189854338611, -0.797188442616427, 0.220818056099052), + (-0.22090980871236, -0.561819935638318, -0.79721842448229), + (-0.7971433029262, 0.220906560624346, 0.561927794358875), + (-0.561911046458035, -0.797113560704263, -0.221056434445611), + (-0.797166608166814, -0.22092145416411, -0.561888876837612), + (-0.561903189214556, 0.797117195899141, 0.221063298519679), + (0.797149071206196, -0.221019917708182, 0.56187503437274), + (0.56187154518738, 0.797190222992272, -0.220880318440273), + (0.797151311779493, 0.220966501329483, -0.561892864715723), + (0.220989674227739, -0.56195922843892, 0.79709810529009), + (-0.220934514736207, 0.561821479644177, 0.797210489901321), + (-0.025586321091663, 0.991400659992677, -0.128335776535923), + (0.991391023154192, -0.128410509654448, -0.025584765380375), + (0.0255553186148, -0.991378867053065, -0.128510185009118), + (-0.128427355734578, -0.025687167640031, 0.991386193023514), + (-0.99138841235829, -0.128432289728374, 0.02557660643704), + (-0.128471046150121, 0.025696657527584, -0.991380286314492), + (-0.991388770492313, 0.128433611757029, -0.025556077805202), + (0.128434643068809, 0.02546732329508, 0.991390920829907), + (0.991386054149539, 0.128448345934336, 0.025587380962899), + (0.128392989158359, -0.02544975216483, -0.991396767419448), + (-0.025589705051665, -0.991398220731893, 0.128353943940207), + (0.025571746935955, 0.991376476866512, 0.128525354986419), + ], + 17: [ + (-0.053895316433783, -0.14060350667641, -0.988597971258691), + (-0.140602010826056, -0.988598302765153, -0.053893137981829), + (0.05389273741214, 0.140602992486377, -0.988598184986247), + (-0.988598098647216, -0.053895299659618, -0.140602617421263), + (0.140604478516356, -0.988597884351918, 0.053894375180116), + (-0.98859806586327, 0.053892813420617, 0.140603800919506), + (0.14060181241573, 0.988598276619905, -0.053894135205652), + (0.988598002509875, 0.053895652551635, -0.140603158106489), + (-0.140604846635692, 0.988597850035353, 0.053894044272357), + (0.988598167360928, -0.0538924507278, 0.140603226297146), + (-0.053892835910884, 0.140602751584219, 0.988598213878838), + (0.053896097450443, -0.140603793852246, 0.988597887836084), + (-0.712137820619482, 0.484725955627139, 0.50783902211694), + (0.484727142303201, 0.507838589157962, -0.71213732164283), + (0.712137878427749, -0.484726895412166, 0.507838044038163), + (0.507839760738435, -0.712137969376798, 0.484724963236905), + (-0.484727642870466, 0.507838390853387, 0.712137122338588), + (0.507839607814555, 0.712136191976364, -0.48472773472555), + (-0.484726768067281, -0.507839236872381, -0.712137114474401), + (-0.507840112632748, 0.712136257912531, 0.48472710896699), + (0.48472854912246, -0.507840095411427, 0.712135289926112), + (-0.507838861015403, -0.712137466171353, -0.484726645149226), + (-0.712136671868904, -0.484727632936327, -0.507839032024349), + (0.712137765857364, 0.484726401555971, -0.507838673275561), + (-0.703005448039525, 0.261790111709517, 0.66124827216248), + (0.26179085361446, 0.661247136036676, -0.7030062404041), + (0.703006433944545, -0.261790569085573, 0.661247042919986), + (0.661249487589413, -0.70300443378585, 0.261789765346499), + (-0.261791711051733, 0.661247232073399, 0.703005830772316), + (0.661247423359215, 0.703005908959219, -0.261791017930756), + (-0.261791042135151, -0.661248438610085, -0.703004944999334), + (-0.661249044674904, 0.703004424763402, 0.261790908321135), + (0.26179224626734, -0.661248916662998, 0.703004046934519), + (-0.661247254530942, -0.703006260525483, -0.261790500280794), + (-0.70300546948428, -0.261791326803081, -0.66124776830313), + (0.703005158463527, 0.261791025358218, -0.661248218308045), + (0.062800447246381, 0.786218819998244, -0.614748786827777), + (0.786220043108977, -0.614747449388309, 0.062798226760693), + (-0.062799502252198, -0.786219239565021, -0.614748346768559), + (-0.61474770709614, 0.062799571514381, 0.786219734194995), + (-0.786218534519124, -0.614749234835089, -0.062799635733612), + (-0.61474933069654, -0.062799956628617, -0.786218433932708), + (-0.786219538571286, 0.614747943528109, 0.062799706183351), + (0.614749395150454, -0.062799770458141, 0.786218398406292), + (0.786217798458002, 0.614750179051967, -0.062799607828601), + (0.614747800214019, 0.062800058129605, -0.786219622517107), + (0.062800526363459, -0.786218909415802, 0.614748664386918), + (-0.062801412397757, 0.786218712810953, 0.614748825315458), + (0.829543607739232, 0.321465368220585, 0.456637076783941), + (0.321463595502047, 0.456637380632479, 0.829544127443504), + (-0.82954344503853, -0.321464459743646, 0.456638011903666), + (0.456635000556537, 0.829545159774775, 0.321464312422039), + (-0.32146420025779, 0.456637459573867, -0.829543849634571), + (0.456637068558535, -0.829544000114897, -0.321464367374743), + (-0.321462954195433, -0.456636421102337, 0.829544904150941), + (-0.456636713034899, -0.829544190971737, 0.321464379883261), + (0.321462955106589, -0.456636688799517, -0.82954475643955), + (-0.456637112396323, 0.829544098578271, -0.321464051017078), + (0.829544701976578, -0.321462298506758, -0.45663724997129), + (-0.829544861446795, 0.321463589390512, -0.456636051515194), + (-0.249500423448462, 0.954025094362385, -0.166089307379737), + (0.954025470855406, -0.166087567010738, -0.249500142371853), + (0.249500943484422, -0.954025029612664, -0.166088898102612), + (-0.166086877408662, -0.249500137449683, 0.954025592196158), + (-0.954024855383494, -0.166089937003122, 0.249500918108135), + (-0.166090151998567, 0.249500107118379, -0.954025030047436), + (-0.954025593688894, 0.166087862658579, -0.249499475879328), + (0.166089692874499, 0.249499687822531, 0.954025219633797), + (0.954024931419817, 0.166090647913648, 0.249500154118264), + (0.166087956122076, -0.249500002352048, -0.954025439732882), + (-0.249499759982538, -0.954025225930409, 0.166089548307795), + (0.249498374708179, 0.954025720257113, 0.166088789826025), + (0.860787215766444, 0.418630333044569, -0.289471956203095), + (0.418631425510959, -0.289473932939102, 0.860786019707239), + (-0.860786771736426, -0.418630687137034, -0.289472764506019), + (-0.289474446503673, 0.860786651964262, 0.418629770347917), + (-0.418629889262302, -0.289472838226515, -0.860787134978979), + (-0.289472399693171, -0.860787556030986, -0.418629326729602), + (-0.418629257388446, 0.289472594933548, 0.86078752409688), + (0.289473185156189, -0.860787817544824, 0.418628245872098), + (0.418628942424652, 0.289472756316209, -0.860787623002977), + (0.289473542762772, 0.860786603163078, -0.418630495610795), + (0.860788012261758, -0.418628676927689, 0.289471982755196), + (-0.860787839361109, 0.418628479474405, 0.289472782452827), + (-0.16910412959425, -0.878917391692094, 0.445991044680649), + (-0.878918175066698, 0.445989574417742, -0.169103935637546), + (0.169102333488308, 0.878918206143462, 0.445990120651083), + (0.445989469034572, -0.169103982598388, -0.878918219506016), + (0.878916540171611, 0.445992443170219, 0.169104867014003), + (0.445990224108014, 0.169104198005711, 0.87891779491425), + (0.878918285318947, -0.445988968934561, -0.169104959479873), + (-0.445991404870479, 0.169104651147508, -0.87891710857278), + (-0.878917501870672, -0.445990557689425, 0.169104841318319), + (-0.4459886381463, -0.169104239878744, 0.878918591622365), + (-0.16910368279187, 0.878918081785917, -0.445989854117772), + (0.169104218306708, -0.878917936541782, -0.445989937303537), + (0.699159749436449, 0.682605593469953, 0.2126622875159), + (0.682603600110598, 0.212662432840561, 0.699161651389995), + (-0.699161274801242, -0.682604056820824, 0.21266220498729), + (0.212660843412531, 0.699162101347243, 0.68260363441662), + (-0.682604762820295, 0.212661985195386, -0.699160652373835), + (0.212662594223091, -0.699161699678606, -0.682603500372528), + (-0.682602562402764, -0.212662368159073, 0.69916268419457), + (-0.212661230546804, -0.699160950060591, 0.682604693019826), + (0.682603395227417, -0.21266176997579, -0.699162053042617), + (-0.212661876797938, 0.699162212981133, -0.682603198129121), + (0.699162077611852, -0.682602648532129, -0.212664085934605), + (-0.69916198280867, 0.682603270588311, -0.212662400948523), + (-0.893254372981228, -0.172342415041176, -0.415204428116666), + (-0.17234169138316, -0.415205105182347, -0.893254197886418), + (0.893254479512939, 0.172341865725213, -0.415204426937409), + (-0.415203760144359, -0.893254961141014, -0.172340975855865), + (0.172343621116966, -0.415205895670259, 0.893253458129858), + (-0.41520633444977, 0.89325370222595, 0.172341298859036), + (0.172340599563611, 0.415204277853847, -0.893254793098767), + (0.415204013461881, 0.893254652987798, -0.172341962739188), + (-0.17234119462921, 0.415204328749048, 0.893254654632054), + (0.415206142771325, -0.893254015914866, 0.172340134782712), + (-0.89325441464337, 0.172340274858685, 0.415205226823752), + (0.893254705389659, -0.172340550786628, 0.415204486793911), + (-0.030119107290242, 0.538031004327585, -0.842386774444073), + (0.538032715913301, -0.84238566918646, -0.030119444819523), + (0.030118087641353, -0.538031590262412, -0.842386436664628), + (-0.842386183209587, -0.030119347292783, 0.538031916577671), + (-0.538030304105545, -0.842387233636645, 0.030118772718924), + (-0.842387312723823, 0.030117901022641, -0.538030229076328), + (-0.538031723308682, 0.842386324999934, -0.030118834084264), + (0.842387103098144, 0.030119789658303, 0.53803045155907), + (0.538029173032331, 0.842387968746045, 0.030118417588877), + (0.842386330532407, -0.030117441179441, -0.538031792619125), + (-0.030117059116644, -0.538030739137179, 0.842387017049566), + (0.030118346812524, 0.538030710181824, 0.84238698950454), + (0.951905881051384, -0.301774121097739, 0.052986540323701), + (-0.301774405343499, 0.052986798530194, 0.951905776566724), + (-0.951905698855431, 0.3017745999, 0.05298708655653), + (0.052987612958423, 0.951905238066977, -0.301775960960479), + (0.301774562398047, 0.052986834212903, -0.951905724790833), + (0.052986766829206, -0.951905252173644, 0.301776065030379), + (0.301777293645336, -0.052987994727859, 0.951904794322844), + (-0.052986574701187, -0.951905591847301, -0.301775027315507), + (-0.301776941841401, -0.052986526316734, -0.951904987591586), + (-0.052988794896112, 0.951905240176443, 0.301775746772478), + (0.95190486181224, 0.301777462059556, -0.052985823114433), + (-0.951905018594348, -0.301776824324304, -0.052986638651951), + (0.553606146300219, 0.45440115669048, 0.697882385203248), + (0.454399619298559, 0.697882738116233, 0.55360696330584), + (-0.553605814018882, -0.454401128197097, 0.697882667342941), + (0.697880969772289, 0.55360789125326, 0.454401204632875), + (-0.454400796454347, 0.697882268834231, -0.553606588678677), + (0.697882520653254, -0.553605103541103, -0.454402219074583), + (-0.454400180900896, -0.697882141032518, 0.553607255032936), + (-0.697881635390884, -0.553607048847703, 0.454401208680482), + (0.454401271818775, -0.69788172578063, -0.553606883077631), + (-0.69788232191633, 0.553606343141444, -0.454401014072625), + (0.553606029480292, -0.454400306845994, -0.697883031217504), + (-0.553606960810672, 0.454400842716203, -0.697881943512493), + ], + 18: [ + (-0.866376343641697, 0.223696804580225, 0.446488265017841), + (0.223696806212017, 0.446488265347841, -0.866376343050305), + (0.866376343115579, -0.223696806225293, 0.44648826521453), + (0.44648826367979, -0.866376344067145, 0.223696805603153), + (-0.223696804286002, 0.446488265023544, 0.866376343714725), + (0.446488262849567, 0.866376344947941, -0.22369680384892), + (-0.2236968055886, -0.446488263582537, -0.866376344121022), + (-0.446488264810465, 0.866376343829741, 0.223696804265844), + (0.223696803801399, -0.446488262808774, 0.866376344981234), + (-0.446488265014924, -0.866376343219064, -0.223696806222901), + (-0.8663763449212, -0.223696804074408, -0.446488262788483), + (0.866376344172558, 0.223696805482214, -0.446488263535836), + (-0.806844783933568, -0.461758079243128, -0.368484695601989), + (-0.461758081774945, -0.368484698390835, -0.806844781210945), + (0.80684478133506, 0.461758081613586, -0.368484698321273), + (-0.368484697968357, -0.806844781706494, -0.461758081246195), + (0.461758078945765, -0.368484695716793, 0.806844784051319), + (-0.368484697702554, 0.806844783105505, 0.461758079013772), + (0.461758081217295, 0.368484698443883, -0.806844781505862), + (0.368484695481328, 0.806844784182969, -0.461758078903629), + (-0.461758078967836, 0.36848469793606, 0.806844783025151), + (0.36848469846061, -0.806844781213308, 0.461758081715136), + (-0.806844782774103, 0.461758079314706, 0.368484698051091), + (0.806844781709987, -0.461758081098712, 0.368484698145526), + (-0.134842418858112, -0.040021669507572, 0.990058477084218), + (-0.040021669975618, 0.990058477016276, -0.134842419218046), + (0.134842418981357, 0.040021669788942, 0.990058477056058), + (0.990058476924117, -0.134842420436143, -0.040021668151402), + (0.040021669677461, 0.990058477116921, 0.13484241856757), + (0.990058477286021, 0.134842417855397, 0.040021667893743), + (0.040021668037836, -0.990058476927568, -0.134842420444507), + (-0.990058477115239, 0.134842418635191, -0.040021669491235), + (-0.040021667837798, -0.990058477270892, 0.134842417983082), + (-0.990058477042031, -0.134842419087429, 0.040021669778575), + (-0.134842418122745, 0.040021667670891, -0.990058477258617), + (0.134842420378113, -0.040021667867212, -0.990058476943508), + (0.049794077313207, -0.279738156561879, -0.958784185115654), + (-0.279738157129975, -0.958784185068512, 0.049794075029415), + (-0.049794075085005, 0.279738157111834, -0.958784185070918), + (-0.958784184460955, 0.049794077429761, -0.279738158785068), + (0.279738156684233, -0.958784185083851, -0.049794077238191), + (-0.958784184306963, -0.049794076856858, 0.279738159414846), + (0.279738159012938, 0.958784184390379, 0.049794077508567), + (0.958784185034086, -0.049794077337113, -0.279738156837192), + (-0.279738159575992, 0.95878418424315, -0.049794077180261), + (0.958784185016722, 0.049794074909289, 0.279738157328865), + (0.049794077031905, 0.279738159517178, 0.958784184268015), + (-0.049794077785621, -0.279738158888691, 0.958784184412241), + (0.205470768670777, -0.192901743072287, 0.959463746444603), + (-0.192901744385898, 0.959463746331714, 0.205470767964668), + (-0.205470768086678, 0.19290174463045, 0.959463746256418), + (0.959463745738735, 0.205470770340502, -0.192901744804646), + (0.19290174290288, 0.959463746447685, -0.205470768815433), + (0.95946374536634, -0.205470771694041, 0.192901745215149), + (0.192901744892626, -0.959463745685675, 0.205470770505673), + (-0.959463746372533, -0.205470769064203, -0.192901743011692), + (-0.192901745122065, -0.959463745348892, -0.205470771862908), + (-0.959463746220563, 0.205470768260652, 0.192901744623478), + (0.205470771726444, 0.192901745460598, -0.959463745310053), + (-0.205470770652743, -0.192901744949698, -0.959463745642705), + (-0.278905392074019, 0.772004854137857, -0.571156972696319), + (0.772004854268172, -0.571156972466399, -0.278905392184152), + (0.278905392160238, -0.772004854249339, -0.571156972503532), + (-0.571156971675365, -0.278905392078835, 0.772004854891456), + (-0.772004854266013, -0.571156972533567, 0.278905392052577), + (-0.571156970567139, 0.278905391582234, -0.77200485589077), + (-0.772004855078365, 0.571156971421921, -0.27890539208049), + (0.57115697255647, 0.278905391952095, 0.77200485428537), + (0.772004855995114, 0.571156970376128, 0.278905391684575), + (0.571156972406906, -0.278905392034109, -0.772004854366394), + (-0.278905391724262, -0.772004855939801, 0.571156970431511), + (0.278905392185009, 0.772004854970249, 0.571156971517017), + (0.912363859945553, -0.393198149041577, -0.113962286110494), + (-0.393198146993911, -0.113962287143254, 0.912363860699027), + (-0.912363860783175, 0.393198146824102, -0.113962287055462), + (-0.113962288369946, 0.912363860162702, -0.393198147882843), + (0.393198149035756, -0.113962285681562, -0.912363860001638), + (-0.113962285259825, -0.912363861029922, 0.393198146771995), + (0.393198147677495, 0.113962288066404, 0.912363860289116), + (0.113962286031302, -0.912363859929225, -0.393198149102416), + (-0.39319814691232, 0.113962285090844, -0.912363860990554), + (0.113962287370876, 0.912363860762456, 0.393198146780759), + (0.9123638610767, 0.393198146641003, 0.113962285337288), + (-0.912363860199082, -0.39319814784926, 0.113962288194565), + (0.848662336981788, -0.012909984472825, -0.528777429633226), + (-0.01290998542466, -0.528777432035493, 0.848662335470524), + (-0.848662335552813, 0.012909985214183, -0.528777431908562), + (-0.52877743352546, 0.848662334565693, -0.012909983878239), + (0.012909984639682, -0.528777429281808, -0.848662337198209), + (-0.528777430386149, -0.848662336521067, 0.012909983920382), + (0.012909983944448, 0.52877743344648, 0.848662334613896), + (0.528777429496827, -0.848662337067122, -0.012909984449961), + (-0.012909983871647, 0.528777430419671, -0.848662336500922), + (0.528777432240356, 0.848662335344326, 0.012909985329594), + (0.848662336343559, 0.012909983743557, 0.528777430675359), + (-0.848662334668199, -0.012909983655303, 0.528777433366386), + (-0.69585113208617, 0.211164782101034, 0.686440555892948), + (0.211164781099711, 0.68644055554441, -0.695851132733858), + (0.695851132741401, -0.211164781335084, 0.686440555464357), + (0.686440553889191, -0.695851134384757, 0.211164781040182), + (-0.211164781930503, 0.686440555960218, 0.695851132071559), + (0.686440553598939, 0.695851134525998, -0.21116478151828), + (-0.21116478087036, -0.686440553737906, -0.69585113458553), + (-0.686440555776475, 0.695851132224679, 0.211164782023223), + (0.211164781498505, -0.686440553499572, 0.695851134630023), + (-0.686440555292332, -0.695851132981083, -0.211164781104467), + (-0.695851134744882, -0.211164781531153, -0.686440553373094), + (0.695851134495813, 0.211164781101486, -0.686440553757753), + (-0.261718169263029, -0.581630098396244, 0.770201290908541), + (-0.581630098290833, 0.770201291506502, -0.261718167737572), + (0.261718167857864, 0.581630098126426, 0.770201291589781), + (0.770201292726794, -0.261718168321791, -0.581630096412025), + (0.581630098450626, 0.770201290888376, 0.261718169201518), + (0.770201293263127, 0.261718168077775, 0.581630095811608), + (0.581630096213803, -0.770201292881278, -0.261718168307686), + (-0.770201291051568, 0.26171816913029, -0.581630098266577), + (-0.581630095716607, -0.770201293304276, 0.261718168167806), + (-0.770201291705965, -0.261718167641045, 0.581630098070137), + (-0.261718168076348, 0.581630095637746, -0.770201293394907), + (0.261718168494542, -0.581630096129926, -0.770201292881124), + (0.506136437086844, 0.700992881596967, 0.502428987025446), + (0.700992883568509, 0.502428985302136, 0.506136436066968), + (-0.506136436123196, -0.700992883503112, 0.502428985336736), + (0.502428986281426, 0.506136435764488, 0.700992883085013), + (-0.700992881635171, 0.502428986938925, -0.50613643711982), + (0.502428986199081, -0.506136436342322, -0.700992882726821), + (-0.700992883178434, -0.502428986124795, 0.506136435790584), + (-0.502428987099143, -0.506136437034413, 0.700992881582003), + (0.700992882671006, -0.502428986197914, -0.506136436420782), + (-0.502428985277898, 0.506136435955935, -0.700992883666051), + (0.506136436300189, -0.700992882789867, -0.502428986153563), + (-0.506136435852246, 0.700992882991532, -0.502428986323445), + (-0.440748149182578, 0.602242024157979, 0.665616716534547), + (0.602242022260099, 0.66561671834234, -0.440748149045733), + (0.440748149100016, -0.602242022337998, 0.665616718235914), + (0.665616715634027, -0.440748149390786, 0.602242025000887), + (-0.602242023804998, 0.665616716814167, 0.440748149242614), + (0.665616716586012, 0.440748149783209, -0.602242023661529), + (-0.602242024940208, -0.665616715760932, -0.440748149282046), + (-0.665616716462371, 0.440748149424008, 0.602242024061062), + (0.602242023852026, -0.665616716460744, 0.440748149712092), + (-0.665616718266293, -0.44074814917988, -0.602242022245974), + (-0.440748149655782, -0.602242023883194, -0.66561671646983), + (0.44074814928254, 0.602242025306933, -0.665616715428797), + (-0.89025783677553, -0.293518547758229, 0.348264046639405), + (-0.293518546899673, 0.348264043649922, -0.890257838228066), + (0.890257838178446, 0.293518546762444, 0.348264043892422), + (0.34826404446276, -0.890257837322353, -0.293518548682307), + (0.293518547908785, 0.348264046686625, 0.89025783670742), + (0.348264047178787, 0.890257836270502, 0.293518548650024), + (0.293518548932545, -0.348264044336184, -0.890257837289365), + (-0.348264046901224, 0.890257836626627, -0.29351854789921), + (-0.2935185489462, -0.348264047080228, 0.890257836211408), + (-0.348264043786589, -0.890257838192766, 0.293518546844585), + (-0.890257836357219, 0.293518548692058, -0.348264046921688), + (0.890257837186443, -0.293518548811097, -0.348264044701638), + (0.661971946522154, 0.031389655564508, 0.748871037990662), + (0.03138965429721, 0.748871040172752, 0.661971944113708), + (-0.661971944196008, -0.031389654112142, 0.748871040107759), + (0.748871039164329, 0.661971945218693, 0.031389655052549), + (-0.031389655768972, 0.748871037783183, -0.661971946747175), + (0.748871037422933, -0.661971947171443, -0.031389655416215), + (-0.031389655026768, -0.748871039044161, 0.661971945355858), + (-0.748871037767735, -0.661971946761125, 0.031389655843332), + (0.03138965553856, -0.748871037222178, -0.661971947392751), + (-0.748871040238931, 0.661971944045087, -0.031389654165497), + (0.66197194707148, -0.031389655223358, -0.748871037519379), + (-0.661971945551351, 0.031389654961479, -0.74887103887409), + (-0.125732546862956, -0.877697090664539, -0.462427446956124), + (-0.877697091831705, -0.462427445382079, -0.125732544504481), + (0.125732544403638, 0.877697091976424, -0.46242744513482), + (-0.462427446167756, -0.125732547895101, -0.877697090932044), + (0.877697090790478, -0.462427446687307, 0.125732546972493), + (-0.462427443232932, 0.125732547131528, 0.877697092587683), + (0.87769709111192, 0.462427445862905, -0.12573254776065), + (0.462427446678181, 0.125732547366796, -0.877697090738801), + (-0.87769709250851, 0.462427443357225, 0.125732547227075), + (0.462427444949274, -0.125732544734265, 0.877697092026818), + (-0.125732546895942, 0.877697092616795, 0.462427443241732), + (0.125732547889573, -0.877697091021935, 0.462427445998644), + ], + 19: [ + (0.553035945587524, -0.472050222255944, 0.686527370580538), + (-0.472050227459673, 0.686527365766638, 0.553035947121696), + (-0.55303594558747, 0.472050222505474, 0.686527370409006), + (0.686527372366403, 0.553035941501725, -0.472050224445432), + (0.472050228567412, 0.686527364805305, -0.553035947369552), + (0.68652737203169, -0.553035941518164, 0.472050224912964), + (0.472050228340927, -0.686527365268236, 0.553035946988198), + (-0.686527371732145, -0.553035942965273, -0.47205022365323), + (-0.472050227580466, -0.686527365608527, -0.553035947214868), + (-0.686527371021655, 0.553035942983048, 0.472050224665708), + (0.553035946644886, 0.472050221691609, -0.686527370116806), + (-0.553035947212832, -0.472050222465287, -0.68652736912732), + (0.534151654424436, 0.792082393152326, 0.29544456761586), + (0.792082397489039, 0.295444568376044, 0.534151647573148), + (-0.53415165460592, -0.792082392760173, 0.295444568339099), + (0.295444567949351, 0.534151645341887, 0.792082399152876), + (-0.792082397600766, 0.29544456929757, -0.534151646897765), + (0.295444567829592, -0.534151645364488, -0.792082399182305), + (-0.792082397865911, -0.295444567933543, 0.534151647259045), + (-0.295444567962042, -0.53415164560035, 0.792082398973845), + (0.792082397128777, -0.29544456908432, -0.534151647715618), + (-0.295444567489444, 0.53415164476261, -0.792082399715064), + (0.534151654464793, -0.792082393125927, -0.29544456761367), + (-0.534151654460867, 0.792082392713663, -0.295444568726043), + (-0.987783901989363, -0.008366313346394, -0.155605166275604), + (-0.008366316491905, -0.155605166254194, -0.987783901966094), + (0.987783902042018, 0.008366312354305, -0.155605165994688), + (-0.155605167507252, -0.98778390181532, -0.008366310987655), + (0.008366315777747, -0.155605166766477, 0.987783901891443), + (-0.155605168424393, 0.987783901667492, 0.008366311383278), + (0.008366317026602, 0.155605166706053, -0.987783901890384), + (0.155605166835858, 0.987783901919836, -0.008366311135093), + (-0.008366315838957, 0.155605165685948, 0.98778390206114), + (0.155605167761508, -0.987783901773982, 0.008366311139443), + (-0.98778390211314, 0.008366313179595, 0.155605165498836), + (0.987783902165643, -0.008366312162939, 0.155605165220208), + (0.950764981387945, 0.202727494112491, -0.234408859255043), + (0.202727496789996, -0.234408860732757, 0.950764980452705), + (-0.950764980986237, -0.202727494847485, -0.234408860248721), + (-0.23440885021567, 0.950764983233011, 0.202727495911382), + (-0.20272749729896, -0.234408861592541, -0.950764980132203), + (-0.23440885011577, -0.950764983322899, -0.20272749560533), + (-0.202727496759051, 0.234408860491485, 0.950764980518789), + (0.234408850569327, -0.950764983253747, 0.20272749540521), + (0.202727497565203, 0.234408861679376, -0.950764980054025), + (0.234408850341461, 0.950764983305224, -0.202727495427267), + (0.950764981380539, -0.202727493695606, 0.234408859645621), + (-0.950764980970666, 0.202727494426432, 0.234408860676023), + (0.512072989115983, -0.124051607170076, -0.849936734455185), + (-0.12405160965336, -0.849936734716267, 0.512072988081055), + (-0.51207298893537, 0.124051606674421, -0.849936734636344), + (-0.849936734725645, 0.512072989351902, -0.124051604343177), + (0.124051609706947, -0.849936734419284, -0.512072988561004), + (-0.849936734185619, -0.512072990133951, 0.124051604814925), + (0.124051609905272, 0.849936734159209, 0.512072988944631), + (0.849936734486865, -0.512072989718667, -0.124051604465195), + (-0.124051609776913, 0.849936734911909, -0.512072987726399), + (0.849936733865973, 0.512072990727649, 0.124051604554246), + (0.512072989657044, 0.124051606837459, 0.849936734177751), + (-0.512072989396574, -0.124051606970032, 0.84993673431533), + (0.391883697914976, 0.850423194793585, -0.351009340424947), + (0.850423195330397, -0.351009335923244, 0.39188370078221), + (-0.391883697466306, -0.850423195007668, -0.351009340407185), + (-0.351009335872243, 0.391883705326061, 0.850423193257595), + (-0.850423194593128, -0.351009337444654, -0.391883701019427), + (-0.35100933799945, -0.391883705264188, -0.850423192408108), + (-0.850423194468673, 0.351009337760337, 0.391883701006749), + (0.351009335527498, -0.391883705866852, 0.850423193150685), + (0.850423195361416, 0.351009336170377, -0.391883700493539), + (0.351009336873483, 0.39188370616407, -0.850423192458173), + (0.391883698323181, -0.850423194786778, 0.3510093399857), + (-0.391883698167036, 0.850423194811902, 0.351009340099156), + (-0.637143378120116, -0.628499374133282, 0.446135464216598), + (-0.628499375204954, 0.446135468086576, -0.637143374353178), + (0.63714337757707, 0.628499374826823, 0.446135464015109), + (0.446135466991108, -0.63714337381196, -0.628499376531226), + (0.628499375911897, 0.446135468292267, 0.637143373511799), + (0.446135467311664, 0.637143373480954, 0.628499376639239), + (0.62849937527089, -0.446135468666392, -0.637143373882143), + (-0.446135467006437, 0.637143373286424, -0.628499377053108), + (-0.628499376195147, -0.446135467887251, 0.637143373515989), + (-0.446135467633094, -0.637143373382135, 0.628499376511253), + (-0.637143377816856, 0.628499373935058, -0.446135464928946), + (0.637143377542956, -0.62849937478419, -0.446135464123887), + (-0.420378708184596, 0.903565957647232, -0.082766550526719), + (0.903565960547129, -0.082766548074817, -0.420378702434272), + (0.42037870768752, -0.903565957904322, -0.082766550244743), + (-0.08276654593922, -0.420378701283585, 0.9035659612781), + (-0.903565960760554, -0.082766547146253, 0.420378702158358), + (-0.082766545039106, 0.420378701254078, -0.903565961374279), + (-0.903565960509685, 0.082766547722836, -0.420378702584056), + (0.082766546052241, 0.420378700439882, 0.903565961660275), + (0.90356596090935, 0.082766547862683, 0.420378701697478), + (0.082766545679396, -0.420378701270528, -0.903565961307975), + (-0.420378707505505, -0.903565957945495, 0.082766550719722), + (0.420378706956033, 0.903565958233438, 0.082766550367062), + (0.491848298473796, 0.355367007972287, 0.794858189196825), + (0.355367012626596, 0.794858187708896, 0.491848297515583), + (-0.491848298344631, -0.355367008156744, 0.794858189194284), + (0.794858192062911, 0.491848294626225, 0.355367006886901), + (-0.355367012548889, 0.794858187634159, -0.491848297692508), + (0.794858192091183, -0.491848294618182, -0.355367006834796), + (-0.355367012605403, -0.79485818761909, 0.491848297676028), + (-0.79485819260841, -0.491848293926967, 0.355367006634583), + (0.35536701250799, -0.794858187986535, -0.491848297152596), + (-0.794858192358054, 0.491848294578517, -0.355367006292778), + (0.491848297979809, -0.355367007868558, -0.794858189548874), + (-0.491848297571808, 0.355367007417215, -0.794858190003127), + (0.060667255805915, 0.97798263888706, 0.199673338501868), + (0.977982638810576, 0.199673341482313, 0.060667247229371), + (-0.06066725576913, -0.977982638936182, 0.199673338272451), + (0.19967333790937, 0.060667250976018, 0.977982639307643), + (-0.977982639072168, 0.199673340242081, -0.060667247094362), + (0.199673337373138, -0.060667251086811, -0.977982639410252), + (-0.977982638978921, -0.199673340702871, 0.060667247080943), + (-0.199673337990036, -0.060667251306886, 0.977982639270649), + (0.977982638897865, -0.199673341052594, -0.060667247236562), + (-0.199673337084201, 0.060667250789575, -0.977982639487682), + (0.06066725570001, -0.977982638898456, -0.199673338478232), + (-0.060667256074209, 0.977982638961939, -0.199673338053604), + (-0.708312961873346, 0.702414591990534, 0.070046334671986), + (0.702414584158394, 0.070046328146925, -0.70831297028554), + (0.70831296180624, -0.702414591950002, 0.070046335757007), + (0.070046325730793, -0.7083129711293, 0.702414583548491), + (-0.702414584241602, 0.070046328819927, 0.70831297013647), + (0.070046325138075, 0.708312971393231, -0.70241458334145), + (-0.702414584340882, -0.070046327329757, -0.708312970185382), + (-0.070046326094986, 0.708312970542407, 0.702414584103993), + (0.702414584126282, -0.070046328999645, 0.708312970233058), + (-0.07004632593766, -0.708312970292399, -0.70241458437179), + (-0.70831296129047, -0.702414592488956, -0.070046335567964), + (0.708312961059513, 0.702414592640383, -0.070046336384914), + (-0.608778246497891, -0.729529462544733, -0.311730348009535), + (-0.729529461162802, -0.311730341531525, -0.608778251471052), + (0.608778246679673, 0.729529462023489, -0.31173034887438), + (-0.311730343069402, -0.608778253416134, -0.729529458882528), + (0.729529460955067, -0.311730341992774, 0.608778251483804), + (-0.311730342453046, 0.608778253837742, 0.729529458794075), + (0.729529461285603, 0.311730341286902, -0.608778251449154), + (0.311730342676067, 0.608778254584565, -0.729529458075568), + (-0.729529460737167, 0.311730342625706, 0.608778251420826), + (0.311730342500045, -0.608778254449614, 0.729529458263397), + (-0.608778247292532, 0.72952946202083, 0.31173034768375), + (0.608778247330452, -0.729529461617846, 0.311730348552781), + (0.230102774190651, -0.807756554170623, 0.542754145543051), + (-0.807756552084345, 0.542754149424728, 0.230102772358463), + (-0.230102773683601, 0.807756554197333, 0.542754145718266), + (0.542754144206019, 0.230102772513564, -0.807756555546758), + (0.807756552132751, 0.542754149180793, -0.230102772763921), + (0.54275414387689, -0.230102773432955, 0.807756555506005), + (0.807756552229309, -0.542754148882616, 0.230102773128283), + (-0.542754145084005, -0.230102772500065, -0.80775655496066), + (-0.807756552237738, -0.542754149346909, -0.230102772003543), + (-0.542754144288786, 0.230102773227955, 0.807756555287639), + (0.230102774097675, 0.807756554025896, -0.542754145797859), + (-0.230102773562357, -0.807756553761761, -0.542754146417909), + (-0.496383809474105, -0.862518230775131, -0.098312843883766), + (-0.862518224287333, -0.098312838975785, -0.49638382171939), + (0.496383809596231, 0.862518230686221, -0.098312844047173), + (-0.098312839350041, -0.496383823019562, -0.862518223496418), + (0.862518224300261, -0.098312838333147, 0.496383821824206), + (-0.098312838299782, 0.496383823078636, 0.862518223582133), + (0.862518224470515, 0.098312838524506, -0.496383821490472), + (0.09831283917121, 0.496383824314041, -0.862518222771822), + (-0.862518224078588, 0.098312839378387, 0.496383822002367), + (0.098312838470056, -0.49638382381015, 0.862518223141735), + (-0.496383810069422, 0.862518230414379, 0.098312844042943), + (0.496383810403814, -0.862518230215463, 0.098312844099726), + (0.278692551327958, 0.919313188465131, 0.277837584477674), + (0.919313191744972, 0.277837581526559, 0.278692543450923), + (-0.278692551566547, -0.91931318841363, 0.277837584408758), + (0.277837583051005, 0.278692544908351, 0.919313190842426), + (-0.919313192180326, 0.277837580345951, -0.278692543191822), + (0.277837582008532, -0.278692545046071, -0.919313191115735), + (-0.919313192196504, -0.277837580255645, 0.278692543228489), + (-0.277837582825575, -0.278692545265575, 0.919313190802263), + (0.919313191814052, -0.277837581086655, -0.278692543661607), + (-0.277837581528602, 0.278692544535811, -0.919313191415468), + (0.278692551299389, -0.91931318860489, -0.277837584043894), + (-0.278692551719555, 0.919313188501633, -0.277837583964092), + (0.711723818982073, -0.147355178359107, -0.686830151423428), + (-0.14735518004562, -0.686830151696651, 0.711723818369232), + (-0.711723818994987, 0.147355179083635, -0.686830151254603), + (-0.686830156031755, 0.711723816221312, -0.147355170213896), + (0.147355179878181, -0.68683015150873, -0.711723818585246), + (-0.686830155899656, -0.711723816480405, 0.147355169578202), + (0.147355179832049, 0.686830151151262, 0.711723818939762), + (0.686830156307707, -0.711723816117428, -0.147355169429431), + (-0.147355180410728, 0.686830151596083, -0.711723818390689), + (0.686830155813336, 0.711723816667769, 0.147355169075579), + (0.711723819167954, 0.147355177853636, 0.686830151339256), + (-0.711723818958232, -0.147355177932743, 0.686830151539607), + (0.910866815770901, -0.407547474081887, 0.065013077890936), + (-0.407547470055014, 0.06501307469253, 0.910866817800923), + (-0.91086681602966, 0.40754747351676, 0.065013077808199), + (0.065013071417123, 0.910866817243773, -0.407547471822745), + (0.407547469547224, 0.065013074424327, -0.910866818047266), + (0.065013071503944, -0.910866817193855, 0.407547471920462), + (0.407547469994702, -0.065013074730498, 0.910866817825199), + (-0.065013071237167, -0.910866817002829, -0.407547472389962), + (-0.407547469492954, -0.065013074760909, -0.910866818047525), + (-0.065013070894069, 0.910866817046896, 0.407547472346204), + (0.910866815571027, 0.407547474607393, -0.065013077397032), + (-0.910866815826998, -0.407547474069762, -0.065013077180997), + ], + 20: [ + (-0.251581299355938, 0.965702462813156, -0.064230858090044), + (0.965702462812973, -0.064230858090163, -0.251581299356609), + (0.25158129935621, -0.965702462813076, -0.064230858090184), + (-0.064230858090037, -0.251581299356469, 0.965702462813018), + (-0.965702462812988, -0.064230858090212, 0.25158129935654), + (-0.064230858090283, 0.251581299356213, -0.965702462813068), + (-0.965702462813129, 0.06423085809035, -0.251581299355962), + (0.064230858090209, 0.251581299356322, 0.965702462813045), + (0.96570246281309, 0.064230858089911, 0.251581299356226), + (0.0642308580902, -0.2515812993563, -0.965702462813051), + (-0.2515812993566, -0.965702462812992, 0.064230858089919), + (0.251581299356516, 0.965702462812981, 0.064230858090402), + (-0.774265533845772, 0.381515182343397, -0.504934697500583), + (0.381515182343197, -0.504934697500657, -0.774265533845823), + (0.774265533845583, -0.381515182343386, -0.504934697500883), + (-0.504934697500797, -0.774265533845681, 0.3815151823433), + (-0.381515182343153, -0.504934697500805, 0.774265533845748), + (-0.504934697500622, 0.774265533845887, -0.381515182343114), + (-0.381515182343272, 0.504934697500883, -0.774265533845639), + (0.504934697500808, 0.774265533845615, 0.381515182343419), + (0.38151518234349, 0.504934697500621, 0.774265533845703), + (0.50493469750058, -0.774265533845806, -0.381515182343333), + (-0.774265533845719, -0.381515182343321, 0.504934697500723), + (0.774265533845894, 0.38151518234298, 0.504934697500711), + (0.621892089865857, 0.451716799694261, -0.639689113113747), + (0.451716799694191, -0.639689113113918, 0.621892089865731), + (-0.621892089865648, -0.451716799694225, -0.639689113113976), + (-0.639689113113901, 0.621892089865499, 0.451716799694535), + (-0.451716799694008, -0.6396891131138, -0.621892089865986), + (-0.639689113113879, -0.621892089865655, -0.451716799694351), + (-0.451716799694347, 0.639689113113675, 0.621892089865869), + (0.639689113113788, -0.621892089865995, 0.451716799694013), + (0.451716799694587, 0.639689113113955, -0.621892089865406), + (0.639689113114061, 0.6218920898659, -0.451716799693757), + (0.621892089865889, -0.451716799694281, 0.639689113113701), + (-0.621892089865898, 0.451716799693713, 0.639689113114094), + (0.281811042675091, 0.858047847696197, -0.429344182783814), + (0.858047847696408, -0.429344182783659, 0.281811042674688), + (-0.281811042675114, -0.858047847696306, -0.429344182783581), + (-0.429344182783315, 0.281811042674947, 0.858047847696495), + (-0.858047847696386, -0.429344182783329, -0.281811042675257), + (-0.429344182783979, -0.281811042674793, -0.858047847696213), + (-0.858047847696136, 0.429344182783948, 0.281811042675075), + (0.429344182783574, -0.281811042675002, 0.858047847696347), + (0.85804784769643, 0.429344182783432, -0.281811042674964), + (0.429344182783407, 0.2818110426754, -0.8580478476963), + (0.28181104267478, -0.858047847696515, 0.429344182783383), + (-0.281811042675193, 0.858047847696227, 0.429344182783688), + (-0.649612004107369, -0.615311084069471, 0.44653836782617), + (-0.615311084069575, 0.446538367826544, -0.649612004107014), + (0.649612004107338, 0.615311084069274, 0.446538367826487), + (0.44653836782629, -0.649612004107234, -0.615311084069526), + (0.615311084069631, 0.446538367826189, 0.649612004107205), + (0.4465383678263, 0.649612004107223, 0.615311084069531), + (0.615311084069337, -0.44653836782627, -0.649612004107428), + (-0.446538367826248, 0.649612004107346, -0.615311084069439), + (-0.615311084069373, -0.446538367826536, 0.649612004107211), + (-0.446538367826286, -0.649612004107303, 0.615311084069457), + (-0.649612004107121, 0.615311084069723, -0.446538367826183), + (0.649612004107125, -0.615311084069551, -0.446538367826415), + (0.993363116319503, -0.113468728148246, -0.018829946054775), + (-0.113468728148035, -0.018829946054639, 0.993363116319529), + (-0.993363116319523, 0.113468728148204, -0.018829946053964), + (-0.018829946053903, 0.993363116319554, -0.113468728147943), + (0.113468728148066, -0.018829946054323, -0.993363116319532), + (-0.018829946054743, -0.993363116319533, 0.113468728147986), + (0.113468728148219, 0.018829946054485, 0.993363116319511), + (0.018829946054344, -0.99336311631951, -0.113468728148254), + (-0.113468728148178, 0.018829946054246, -0.993363116319521), + (0.018829946054485, 0.993363116319503, 0.113468728148287), + (0.99336311631954, 0.113468728147985, 0.018829946054382), + (-0.993363116319531, -0.113468728148037, 0.018829946054542), + (0.246398885891569, -0.720801569649804, 0.647867799957501), + (-0.720801569649392, 0.647867799957886, 0.246398885891762), + (-0.246398885891682, 0.720801569649632, 0.647867799957649), + (0.647867799957437, 0.246398885891663, -0.720801569649829), + (0.720801569649864, 0.647867799957577, -0.246398885891192), + (0.647867799957658, -0.246398885891679, 0.720801569649625), + (0.720801569649656, -0.647867799957734, 0.246398885891389), + (-0.647867799957904, -0.246398885891433, -0.720801569649489), + (-0.720801569649865, -0.647867799957373, -0.246398885891727), + (-0.647867799957474, 0.246398885891166, 0.720801569649966), + (0.246398885891794, 0.720801569649507, -0.647867799957745), + (-0.246398885891456, -0.720801569649666, -0.647867799957697), + (-0.793544204802179, -0.387628773401269, -0.469075184865183), + (-0.387628773401353, -0.469075184864794, -0.793544204802368), + (0.793544204802171, 0.387628773401536, -0.469075184864975), + (-0.469075184865097, -0.793544204802034, -0.387628773401668), + (0.38762877340168, -0.469075184864988, 0.793544204802093), + (-0.46907518486511, 0.793544204802104, 0.387628773401512), + (0.387628773401425, 0.469075184865298, -0.793544204802035), + (0.469075184865068, 0.793544204802337, -0.387628773401084), + (-0.387628773401491, 0.469075184864931, 0.793544204802219), + (0.469075184864784, -0.793544204802296, 0.387628773401512), + (-0.793544204802265, 0.387628773401224, 0.469075184865075), + (0.793544204802185, -0.387628773401823, 0.469075184864715), + (0.164945057653003, -0.958376909717154, 0.233038251960587), + (-0.958376909716935, 0.233038251961126, 0.164945057653512), + (-0.164945057653238, 0.958376909717001, 0.233038251961048), + (0.233038251960668, 0.164945057653504, -0.958376909717048), + (0.958376909717102, 0.233038251960514, -0.164945057653409), + (0.233038251960742, -0.164945057653288, 0.958376909717067), + (0.958376909717099, -0.233038251960827, 0.164945057652982), + (-0.233038251961122, -0.164945057653226, -0.958376909716986), + (-0.958376909717093, -0.233038251960632, -0.164945057653293), + (-0.233038251960434, 0.164945057653261, 0.958376909717147), + (0.164945057653494, 0.958376909716965, -0.233038251961015), + (-0.164945057653458, -0.958376909717031, -0.233038251960769), + (0.560484250466976, 0.813252649483695, -0.156452974040834), + (0.81325264948369, -0.156452974041446, 0.560484250466813), + (-0.56048425046724, -0.813252649483431, -0.156452974041263), + (-0.15645297404103, 0.560484250467047, 0.813252649483609), + (-0.81325264948382, -0.156452974040726, -0.560484250466826), + (-0.156452974041097, -0.560484250466778, -0.813252649483781), + (-0.81325264948363, 0.156452974040967, 0.560484250467035), + (0.156452974041285, -0.560484250467053, 0.813252649483555), + (0.813252649483481, 0.156452974041151, -0.560484250467199), + (0.156452974040881, 0.560484250466996, -0.813252649483672), + (0.560484250466836, -0.813252649483737, 0.156452974041122), + (-0.56048425046674, 0.813252649483823, 0.156452974041018), + (0.366630058651312, 0.922018832550933, -0.124353015704282), + (0.92201883255088, -0.124353015704762, 0.366630058651284), + (-0.366630058651761, -0.922018832550708, -0.124353015704629), + (-0.124353015704377, 0.366630058651577, 0.922018832550815), + (-0.922018832550933, -0.124353015704203, -0.366630058651341), + (-0.124353015704534, -0.366630058651111, -0.922018832550979), + (-0.922018832550883, 0.124353015704478, 0.366630058651372), + (0.12435301570463, -0.366630058651537, 0.922018832550797), + (0.922018832550745, 0.124353015704463, -0.366630058651723), + (0.124353015704299, 0.366630058651563, -0.922018832550831), + (0.366630058651286, -0.922018832550923, 0.124353015704438), + (-0.366630058651229, 0.922018832550938, 0.124353015704492), + (-0.804671953651735, -0.070836250755727, 0.589478814365005), + (-0.070836250756058, 0.589478814365003, -0.804671953651707), + (0.804671953651921, 0.070836250755383, 0.589478814364792), + (0.589478814364726, -0.804671953651941, -0.070836250755714), + (0.070836250755939, 0.589478814364776, 0.804671953651884), + (0.589478814365018, 0.804671953651715, 0.070836250755846), + (0.070836250755601, -0.589478814364811, -0.804671953651888), + (-0.589478814364784, 0.804671953651884, -0.070836250755875), + (-0.070836250755551, -0.589478814364944, 0.804671953651795), + (-0.589478814364978, -0.804671953651759, 0.07083625075567), + (-0.804671953651836, 0.070836250756193, -0.589478814364811), + (0.804671953651731, -0.070836250755764, -0.589478814365006), + (-0.830597137771463, -0.481356221636722, 0.280008183125909), + (-0.481356221636763, 0.280008183126324, -0.830597137771299), + (0.830597137771467, 0.481356221636628, 0.280008183126056), + (0.280008183125864, -0.830597137771343, -0.481356221636956), + (0.481356221637075, 0.280008183125899, 0.830597137771262), + (0.280008183126004, 0.830597137771351, 0.481356221636859), + (0.481356221636653, -0.280008183125859, -0.83059713777152), + (-0.280008183126012, 0.83059713777152, -0.481356221636564), + (-0.481356221636741, -0.280008183126112, 0.830597137771384), + (-0.280008183126053, -0.830597137771314, 0.481356221636894), + (-0.830597137771366, 0.48135622163684, -0.280008183125994), + (0.830597137771194, -0.481356221637029, -0.280008183126178), + (0.622576105404642, 0.027441908430236, -0.782077959439399), + (0.027441908430276, -0.782077959439431, 0.622576105404601), + (-0.622576105404963, -0.027441908430045, -0.78207795943915), + (-0.782077959439118, 0.622576105404988, 0.027441908430397), + (-0.027441908430201, -0.782077959439296, -0.622576105404774), + (-0.782077959439408, -0.622576105404628, -0.027441908430289), + (-0.027441908430238, 0.782077959439221, 0.622576105404866), + (0.782077959439263, -0.62257610540482, 0.027441908430083), + (0.027441908430419, 0.782077959439269, -0.622576105404798), + (0.782077959439451, 0.622576105404591, -0.027441908429928), + (0.622576105404788, -0.02744190843038, 0.782077959439278), + (-0.622576105404572, 0.027441908429868, 0.782077959439468), + (-0.93186959347387, 0.318712863282032, -0.173323891998229), + (0.318712863281944, -0.173323891998258, -0.931869593473894), + (0.931869593473744, -0.318712863282051, -0.173323891998871), + (-0.173323891998841, -0.931869593473836, 0.318712863281799), + (-0.318712863281924, -0.173323891998617, 0.931869593473834), + (-0.173323891998245, 0.931869593473975, -0.318712863281714), + (-0.318712863281997, 0.173323891998515, -0.931869593473828), + (0.173323891998501, 0.931869593473801, 0.318712863282084), + (0.318712863282089, 0.173323891998539, 0.931869593473793), + (0.173323891998443, -0.931869593473824, -0.31871286328205), + (-0.931869593473865, -0.318712863281928, 0.173323891998448), + (0.931869593473897, 0.318712863281802, 0.173323891998503), + (0.883848176852703, 0.201423804475213, 0.422185801827685), + (0.201423804475703, 0.422185801827661, 0.883848176852602), + (-0.883848176852534, -0.201423804475554, 0.422185801827875), + (0.42218580182791, 0.883848176852484, 0.201423804475701), + (-0.201423804475472, 0.422185801827744, -0.883848176852615), + (0.422185801827623, -0.883848176852647, -0.201423804475586), + (-0.201423804475397, -0.422185801827833, 0.88384817685259), + (-0.42218580182793, -0.883848176852523, 0.201423804475489), + (0.201423804475479, -0.422185801827682, -0.883848176852643), + (-0.422185801827514, 0.883848176852769, -0.20142380447528), + (0.883848176852476, -0.201423804475614, -0.422185801827967), + (-0.88384817685271, 0.201423804475563, -0.422185801827502), + (0.204275039956405, 0.718770569884226, 0.664560438123663), + (0.718770569884334, 0.664560438123474, 0.204275039956637), + (-0.20427503995626, -0.718770569884265, 0.664560438123664), + (0.66456043812381, 0.204275039956156, 0.71877056988416), + (-0.718770569884325, 0.664560438123579, -0.204275039956328), + (0.664560438123492, -0.204275039956373, -0.718770569884393), + (-0.718770569884361, -0.664560438123554, 0.20427503995628), + (-0.664560438123554, -0.204275039956662, 0.718770569884254), + (0.71877056988409, -0.664560438123802, -0.204275039956432), + (-0.664560438123505, 0.204275039956682, -0.718770569884293), + (0.204275039956376, -0.718770569884165, -0.664560438123738), + (-0.204275039956367, 0.718770569884538, -0.664560438123337), + (-0.898847927472069, 0.43770082336828, 0.022144807560617), + (0.437700823367923, 0.022144807560963, -0.898847927472234), + (0.898847927472182, -0.437700823368065, 0.02214480756027), + (0.022144807560315, -0.898847927472293, 0.437700823367834), + (-0.437700823367766, 0.022144807560623, 0.898847927472319), + (0.022144807560559, 0.89884792747216, -0.437700823368094), + (-0.437700823368255, -0.022144807560327, -0.898847927472088), + (-0.022144807560661, 0.898847927472103, 0.437700823368207), + (0.43770082336803, -0.022144807560607, 0.898847927472191), + (-0.022144807560733, -0.898847927472195, -0.437700823368015), + (-0.898847927472245, -0.437700823367908, -0.022144807560796), + (0.898847927472313, 0.437700823367778, -0.022144807560634), + ], + 21: [ + (0.892653535762723, 0.412534053657361, -0.181618610454253), + (0.412534053425032, -0.181618610641782, 0.892653535831938), + (-0.892653535806407, -0.412534053627853, -0.181618610306575), + (-0.181618610613849, 0.892653535740475, 0.41253405363524), + (-0.412534053477435, -0.181618610422654, -0.892653535852304), + (-0.181618610451384, -0.892653535762812, -0.412534053658432), + (-0.41253405331709, 0.181618610611827, 0.892653535887918), + (0.181618610400136, -0.8926535358123, 0.412534053573911), + (0.412534053327996, 0.1816186104204, -0.892653535921825), + (0.181618610580789, 0.892653535810904, -0.412534053497399), + (0.892653535867644, -0.412534053472558, 0.181618610358339), + (-0.892653535855064, 0.41253405353516, 0.181618610277971), + (-0.292093742593433, -0.29576702799317, 0.909507070170347), + (-0.295767028026887, 0.90950707008926, -0.292093742811776), + (0.292093742447864, 0.295767028039713, 0.909507070201962), + (0.909507070147612, -0.292093742926721, -0.295767027733934), + (0.295767028145396, 0.909507070084441, 0.292093742706783), + (0.909507070188854, 0.292093742689207, 0.295767027841675), + (0.295767027907311, -0.909507070148419, -0.292093742748651), + (-0.909507070101221, 0.292093743159272, -0.295767027646927), + (-0.295767027835333, -0.909507070047293, 0.292093743136414), + (-0.909507070218591, -0.292093742721776, 0.295767027718069), + (-0.292093742540896, 0.295767027793147, -0.909507070252266), + (0.292093742861938, -0.295767027747614, -0.909507070163969), + (-0.575225718038192, 0.024120572825078, 0.817639022597403), + (0.024120572786144, 0.817639022511238, -0.575225718162301), + (0.575225718116478, -0.024120572979213, 0.817639022537781), + (0.817639022556003, -0.57522571810348, 0.024120572671469), + (-0.024120573041503, 0.817639022440757, 0.575225718251777), + (0.817639022458379, 0.575225718229118, -0.024120572984526), + (-0.024120572818239, -0.81763902258126, -0.575225718061424), + (-0.817639022543578, 0.575225718123882, 0.024120572606111), + (0.02412057271295, -0.817639022527296, 0.575225718142546), + (-0.817639022600495, -0.575225718035174, -0.024120572792228), + (-0.575225717925469, -0.024120572711052, -0.81763902268007), + (0.57522571790823, 0.024120572594155, -0.817639022695646), + (-0.1288331617248, 0.05224764072024, 0.990288947973853), + (0.052247640694409, 0.990288947958895, -0.128833161850251), + (0.128833161840325, -0.052247640320038, 0.990288947979938), + (0.990288947949717, -0.128833161924796, 0.052247640684558), + (-0.05224764038851, 0.990288947967581, 0.128833161907538), + (0.99028894797773, 0.128833161878001, -0.052247640268992), + (-0.052247640390409, -0.99028894796219, -0.128833161948209), + (-0.990288947960626, 0.128833161896649, 0.052247640547187), + (0.052247640527808, -0.990288947953251, 0.1288331619612), + (-0.990288947970868, -0.128833161936205, -0.052247640255526), + (-0.128833161790478, -0.052247640337643, -0.990288947985494), + (0.128833161857416, 0.052247640551545, -0.9902889479655), + (0.71800638603475, 0.657446876255993, -0.228539787596286), + (0.657446876286737, -0.228539787831922, 0.718006385931596), + (-0.718006386109442, -0.657446876171434, -0.228539787604877), + (-0.228539787737219, 0.718006385947422, 0.657446876302374), + (-0.657446876241021, -0.2285397877138, -0.718006386011054), + (-0.228539787678997, -0.718006386031359, -0.657446876230945), + (-0.657446876361185, 0.228539787860549, 0.718006385854315), + (0.228539787703065, -0.718006385857385, 0.657446876412577), + (0.657446876304454, 0.228539787874017, -0.718006385901975), + (0.228539787784967, 0.718006385813853, -0.657446876431648), + (0.71800638588076, -0.657446876363485, 0.228539787770851), + (-0.718006385891018, 0.657446876371558, 0.228539787715401), + (0.863176473117803, 0.468181816653138, 0.189029528940001), + (0.468181816438486, 0.189029529197492, 0.86317647317784), + (-0.863176473194446, -0.46818181657642, 0.189029528780033), + (0.189029529125527, 0.863176473064389, 0.468181816676708), + (-0.468181816392671, 0.189029528897443, -0.863176473268398), + (0.189029528792174, -0.863176473143688, -0.4681818166651), + (-0.468181816411213, -0.189029529128138, 0.863176473207821), + (-0.189029528897852, -0.86317647308972, 0.468181816721931), + (0.468181816508867, -0.189029528930555, -0.863176473198123), + (-0.189029529001823, 0.863176473106659, -0.468181816648722), + (0.863176473135229, -0.468181816648642, -0.189029528871561), + (-0.863176473123334, 0.468181816698762, -0.189029528801744), + (0.772632856847133, -0.51705945069559, 0.368358511462152), + (-0.517059450567132, 0.368358511585515, 0.772632856874286), + (-0.772632856806081, 0.517059450647391, 0.368358511615915), + (0.368358511648001, 0.772632856806054, -0.517059450624573), + (0.517059450494007, 0.368358511816588, -0.772632856813056), + (0.368358511720496, -0.772632856802476, 0.517059450578273), + (0.517059450583445, -0.368358511487117, 0.77263285691028), + (-0.36835851156733, -0.772632856859467, -0.517059450602229), + (-0.517059450502369, -0.368358511665956, -0.772632856879275), + (-0.368358511469803, 0.772632856855651, 0.517059450677412), + (0.772632856934749, 0.517059450691919, -0.368358511283531), + (-0.772632856927485, -0.517059450633778, -0.368358511380378), + (-0.847819231914648, -0.066325775900167, -0.526121128113002), + (-0.066325775913631, -0.526121128257686, -0.847819231823809), + (0.847819231883018, 0.066325775819852, -0.526121128174097), + (-0.526121128348762, -0.847819231766957, -0.06632577591791), + (0.06632577584612, -0.526121128407098, 0.847819231736372), + (-0.52612112845924, 0.84781923170908, 0.066325775781366), + (0.066325775945785, 0.52612112834438, -0.847819231767496), + (0.526121128449532, 0.847819231700692, -0.066325775965613), + (-0.066325775877211, 0.526121128306388, 0.847819231796436), + (0.526121128504669, -0.847819231665213, 0.06632577598176), + (-0.847819231821725, 0.066325775941005, 0.526121128257594), + (0.847819231850264, -0.066325775996655, 0.52612112820459), + (0.00980574322923, 0.942983815842593, 0.332694109443892), + (0.942983815808923, 0.332694109539748, 0.00980574321495), + (-0.00980574337969, -0.942983815787291, 0.332694109596207), + (0.332694109226554, 0.009805743204272, 0.942983815919532), + (-0.94298381577404, 0.332694109635647, -0.009805743315804), + (0.332694109397996, -0.00980574329891, -0.942983815858062), + (-0.942983815776114, -0.332694109630098, 0.009805743304667), + (-0.332694109319027, -0.009805743188507, 0.94298381588707), + (0.942983815775082, -0.332694109635199, -0.009805743230763), + (-0.332694109455765, 0.009805743389762, -0.942983815836735), + (0.00980574330114, -0.942983815752524, -0.332694109697065), + (-0.009805743287713, 0.942983815791379, -0.332694109587331), + (0.785599248371152, -0.405156945312269, -0.467634120465896), + (-0.405156944932125, -0.467634120649859, 0.785599248457698), + (-0.78559924820179, 0.405156945434051, -0.467634120644904), + (-0.467634120611242, 0.785599248334623, -0.405156945215339), + (0.405156945136423, -0.467634120868201, -0.785599248222366), + (-0.467634120811804, -0.785599248145609, 0.405156945350347), + (0.405156944841985, 0.467634120861332, 0.785599248378305), + (0.467634120786726, -0.785599248249857, -0.405156945177156), + (-0.405156944999643, 0.467634120871098, -0.785599248291182), + (0.467634120893713, 0.78559924823424, 0.405156945083953), + (0.785599248313341, 0.405156945117104, 0.467634120732106), + (-0.7855992482811, -0.40515694519737, 0.467634120716727), + (-0.737331999131492, 0.620851501013764, -0.26624225199189), + (0.620851500949186, -0.266242252154895, -0.73733199912701), + (0.737331999060061, -0.620851501088737, -0.266242252014883), + (-0.266242251948631, -0.737331999103255, 0.62085150106585), + (-0.620851501079221, -0.2662422522338, 0.737331998989025), + (-0.266242252011624, 0.737331998996222, -0.620851501165951), + (-0.620851501072124, 0.26624225222256, -0.73733199899906), + (0.266242252113864, 0.737331998832974, 0.620851501315983), + (0.620851501187387, 0.266242252328374, 0.737331998863797), + (0.26624225193225, -0.73733199893899, -0.620851501267959), + (-0.737331998947943, -0.620851501183297, 0.266242252104879), + (0.737331998835007, 0.620851501305786, 0.26624225213201), + (0.726871469165659, -0.027488282350428, -0.686223186468061), + (-0.027488282182755, -0.686223186448325, 0.726871469190633), + (-0.726871469172931, 0.027488282371885, -0.686223186459499), + (-0.686223186449712, 0.726871469185406, -0.027488282286341), + (0.027488282351607, -0.68622318649112, -0.726871469143845), + (-0.686223186545622, -0.726871469089794, 0.027488282420281), + (0.027488282266836, 0.686223186470335, 0.726871469166674), + (0.686223186661183, -0.726871468983422, -0.027488282348185), + (-0.027488282251029, 0.686223186523092, -0.726871469117465), + (0.686223186609112, 0.726871469033498, 0.027488282323948), + (0.726871469070107, 0.02748828233555, 0.686223186569869), + (-0.726871469080183, -0.027488282309716, 0.686223186560232), + (0.665363385720515, 0.580860267739271, 0.468927408352716), + (0.580860267577087, 0.468927408488638, 0.665363385766308), + (-0.66536338567738, -0.580860267719575, 0.468927408438318), + (0.468927408340783, 0.665363385821863, 0.580860267632813), + (-0.580860267528453, 0.468927408678832, -0.665363385674723), + (0.468927408372614, -0.665363385698803, -0.580860267748078), + (-0.580860267640877, -0.468927408552762, 0.665363385665427), + (-0.468927408468336, -0.665363385847947, 0.580860267499961), + (0.580860267386752, -0.468927408654519, -0.665363385815563), + (-0.468927408375699, 0.665363385651356, -0.580860267799938), + (0.665363385651819, -0.580860267791212, -0.46892740838585), + (-0.665363385751734, 0.580860267548017, -0.468927408545326), + (-0.580125367305304, -0.779099597924434, 0.237609710918707), + (-0.779099598053518, 0.237609710909934, -0.580125367135539), + (0.580125367186808, 0.779099597977732, 0.237609711033258), + (0.237609710695932, -0.58012536727611, -0.779099598014114), + (0.779099598064732, 0.23760971114732, 0.58012536702325), + (0.237609710819285, 0.580125367047426, 0.779099598146774), + (0.779099598170224, -0.237609710849642, -0.580125367003499), + (-0.237609710811802, 0.580125367157256, -0.779099598067276), + (-0.779099598074961, -0.237609711045128, 0.580125367051369), + (-0.237609710609253, -0.580125367022359, 0.779099598229495), + (-0.580125367090094, 0.779099598151966, -0.237609710698086), + (0.580125367218411, -0.779099597966716, -0.237609710992215), + (0.9586680253602, 0.101113605900539, -0.265954236389956), + (0.101113605889893, -0.265954236477199, 0.95866802533712), + (-0.95866802532641, -0.101113606095432, -0.26595423643766), + (-0.265954236634179, 0.958668025294555, 0.101113605880558), + (-0.101113606003171, -0.265954236656317, -0.958668025275482), + (-0.265954236715455, -0.958668025246162, -0.101113606125602), + (-0.101113605825438, 0.265954236414664, 0.958668025361267), + (0.265954236286739, -0.958668025393583, 0.101113605855522), + (0.101113605802444, 0.265954236260664, -0.958668025406415), + (0.265954236515854, 0.958668025322577, -0.101113605926106), + (0.9586680254495, -0.101113605909101, 0.265954236064808), + (-0.9586680254786, 0.101113605789497, 0.265954236005386), + (-0.784431814417085, 0.284319025007229, 0.551207239202516), + (0.284319024822848, 0.551207239320709, -0.784431814400862), + (0.784431814443422, -0.284319024888131, 0.551207239226467), + (0.551207239434677, -0.784431814291888, 0.284319024902556), + (-0.284319024640161, 0.551207239347504, 0.784431814448249), + (0.551207239408357, 0.784431814400998, -0.284319024652546), + (-0.28431902471494, -0.551207239160137, -0.784431814552804), + (-0.551207239417649, 0.784431814426743, 0.284319024563503), + (0.284319024477106, -0.551207239394067, 0.784431814474629), + (-0.551207239227164, -0.784431814510832, -0.284319024700797), + (-0.7844318146549, -0.284319024757729, -0.551207238992772), + (0.784431814542139, 0.284319024689884, -0.55120723918824), + (0.166663878535118, 0.97946877886665, 0.113419851953285), + (0.979468778892362, 0.113419852011248, 0.166663878344564), + (-0.166663878322335, -0.979468778877222, 0.113419852174659), + (0.113419851852603, 0.166663878465092, 0.979468778890224), + (-0.979468778908051, 0.113419852233229, -0.166663878101297), + (0.113419852023532, -0.166663878213165, -0.979468778913298), + (-0.979468778891418, -0.113419852088755, 0.166663878297368), + (-0.113419851942299, -0.166663878383785, 0.979468778893673), + (0.979468778887792, -0.113419852252651, -0.166663878207142), + (-0.113419851887333, 0.166663878420061, -0.979468778893865), + (0.166663878513312, -0.97946877885884, -0.113419852052775), + (-0.166663878525992, 0.979468778852403, -0.113419852089727), + (0.90354263539087, 0.099002690679599, 0.416904273507865), + (0.09900269051118, 0.416904273753692, 0.903542635295897), + (-0.903542635383533, -0.099002690647923, 0.416904273531288), + (0.41690427395825, 0.903542635193768, 0.09900269058185), + (-0.099002690414933, 0.416904273699732, -0.903542635331341), + (0.416904273843964, -0.903542635237517, -0.099002690663845), + (-0.099002690464192, -0.416904273937254, 0.903542635216348), + (-0.416904274206036, -0.903542635110147, 0.099002690301575), + (0.099002690128044, -0.41690427406438, -0.903542635194523), + (-0.416904274113744, 0.903542635131386, -0.099002690496392), + (0.903542635279275, -0.099002690467102, -0.416904273800183), + (-0.903542635234399, 0.099002690245829, -0.416904273949988), + (0.278762404536092, 0.349312185537063, -0.894579520698175), + (0.349312185586056, -0.894579520608515, 0.278762404762431), + (-0.278762404540525, -0.349312185503473, -0.89457952070991), + (-0.894579520734144, 0.278762404727917, 0.349312185291866), + (-0.349312185466701, -0.894579520677723, -0.278762404689896), + (-0.894579520788864, -0.278762404658677, -0.349312185206984), + (-0.349312185551041, 0.894579520682798, 0.278762404567923), + (0.894579520785219, -0.278762404680469, 0.349312185198929), + (0.349312185549623, 0.89457952067923, -0.278762404581149), + (0.894579520781805, 0.278762404555908, -0.349312185307075), + (0.27876240443795, -0.3493121855065, 0.894579520740692), + (-0.278762404443259, 0.349312185428787, 0.894579520769382), + (0.555896230179415, -0.676833211736671, 0.48257246581476), + (-0.676833211681567, 0.482572466040116, 0.555896230050876), + (-0.555896230314892, 0.676833211522987, 0.482572465958401), + (0.482572465910283, 0.555896230164672, -0.676833211680673), + (0.676833211457692, 0.482572466092895, -0.555896230277639), + (0.482572465902981, -0.555896230367909, 0.676833211518957), + (0.676833211635592, -0.482572466071981, 0.555896230079191), + (-0.482572466150586, -0.555896230230084, -0.676833211455616), + (-0.676833211438286, -0.482572466327737, -0.5558962300974), + (-0.482572465972373, 0.55589623026777, 0.676833211551727), + (0.555896230192691, 0.676833211589453, -0.482572466005949), + (-0.555896230194338, -0.676833211455537, -0.482572466191875), + ], +} diff --git a/pytta/ambisonics.py b/pytta/ambisonics.py new file mode 100644 index 0000000..7dbc653 --- /dev/null +++ b/pytta/ambisonics.py @@ -0,0 +1,512 @@ +""" +PyTTa Ambisonics: +----------------- + +Higher-order Ambisonics (HOA): real spherical harmonics, encoding, decoding +with five decoders, rotation and the conversions between the usual channel +conventions. + +Available functions: + + >>> Y = pytta.ambisonics.real_sh(order, directions) + >>> hoa = pytta.ambisonics.encode(signals, directions, order) + >>> D, order = pytta.ambisonics.decoder(ls_directions, 'allrad') + >>> ls = pytta.ambisonics.decode(hoa, D) + >>> hoa_rot = pytta.ambisonics.rotate(hoa, yaw, pitch, roll) + +Signals are (samples, channels) arrays. The HOA channels follow ACN ordering, +q = n^2 + n + m (0-based), with orthonormal real spherical harmonics (N3D) +and no Condon-Shortley phase. Directions are (points, 2) arrays of +[azimuth, elevation] in degrees, 0 deg in front and 90 deg to the left. +Conversions to SN3D, FuMa and SID are included, and bformat_fuma gives the +first-order FuMa B-format of Iceberg. + +Origin of the code +------------------ + +This module is a port to Python of the MATLAB library Higher-Order-Ambisonics +by Archontis Politis (2015), https://github.com/polarch/Higher-Order-Ambisonics +(getRSH, encodeHOA_N3D, decodeHOA_N3D, encodeBformat, decodeBformat, +rotateHOA_N3D, rotateBformat, convert_N3D_SN3D, convert_N3D_FuMa, +convert_ACN_SID, getMaxREweights, getLayoutAmbisonicOrder, ambiDecoder, +allrad, csad), with the functions it uses from his Spherical-Harmonic- +Transform library, https://github.com/polarch/Spherical-Harmonic-Transform +(getSHrotMtx, euler2rotationMatrix, getTdesign and its t-designs, in +pytta/_tdesigns.py). Both are under the BSD 3-Clause licence reproduced +below. bformat_fuma follows native_bformat_fuma of Iceberg +(https://github.com/aguirreSL/Iceberg, BSD 3-Clause, Sergio Aguirre). + +References +---------- + +Zotter, F., & Frank, M. (2012). All-round ambisonic panning and decoding. +Journal of the Audio Engineering Society, 60(10), 807-820. + +Zotter, F., Pomberger, H., & Noisternig, M. (2012). Energy-preserving +ambisonic decoding. Acta Acustica united with Acustica, 98(1), 37-47. + +Epain, N., Jin, C. T., & Zotter, F. (2014). Ambisonic decoding with +constant angular spread. Acta Acustica united with Acustica, 100(5), 928-936. + +Ivanic, J., & Ruedenberg, K. (1996). Rotation matrices for real spherical +harmonics. Direct determination by recursion. Journal of Physical Chemistry, +100(15), 6342-6347 (with the corrections of 1998). + +Licence of the ported code (both libraries) +------------------------------------------- + + Copyright (c) 2015, Archontis Politis + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +""" + +import warnings +from math import factorial + +import numpy as np +from scipy import signal as ss +from scipy.optimize import brentq +from scipy.spatial import ConvexHull +from scipy.special import eval_legendre, lpmv + +from . import vbap +from ._tdesigns import T_DESIGNS + + +def _dirs(directions): + return np.atleast_2d(np.asarray(directions, dtype=float)) + + +def _unit(directions): + d = np.radians(_dirs(directions)) + return np.column_stack([np.cos(d[:, 0]) * np.cos(d[:, 1]), + np.sin(d[:, 0]) * np.cos(d[:, 1]), np.sin(d[:, 1])]) + + +def _directions(vectors): + v = np.asarray(vectors, dtype=float) + return np.degrees(np.column_stack([np.arctan2(v[:, 1], v[:, 0]), + np.arctan2(v[:, 2], np.hypot(v[:, 0], v[:, 1]))])) + + +def _order(channels): + order = int(round(np.sqrt(channels))) - 1 + if (order + 1)**2 != channels: + raise ValueError('HOA signals need (order + 1)^2 channels.') + return order + + +def real_sh(order, directions): + """ + Orthonormal real spherical harmonics, ACN ordering, no Condon-Shortley + phase (getRSH). + + Returns + ------- + numpy.ndarray + ((order + 1)^2, points). + """ + d = np.radians(_dirs(directions)) + azi, x = d[:, 0], np.sin(d[:, 1]) + Y = np.zeros(((order + 1)**2, len(d))) + Y[0] = 1 / np.sqrt(4 * np.pi) + for n in range(1, order + 1): + for m in range(-n, n + 1): + a = abs(m) + norm = np.sqrt((2 * n + 1) * factorial(n - a) / (4 * np.pi * factorial(n + a))) + P = (-1)**a * lpmv(a, n, x) + trig = (np.sqrt(2) * np.cos(m * azi) if m > 0 else + np.sqrt(2) * np.sin(a * azi) if m < 0 else 1.) + Y[n * n + n + m] = norm * P * trig + return Y + + +def t_design(degree): + """ + Spherical t-design of degree 1 to 21 (getTdesign). + + Returns + ------- + (numpy.ndarray, numpy.ndarray) + Unit vectors (points, 3) and their [azimuth, elevation] in degrees. + """ + if not 1 <= degree <= 21: + raise ValueError('t-designs are available for degrees 1 to 21.') + vecs = np.array(T_DESIGNS[degree]) + return vecs, _directions(vecs) + + +def encode(signals, directions, order): + """ + Encode source signals from their directions to HOA, N3D (encodeHOA_N3D). + + signals: (samples, sources); returns (samples, (order + 1)^2). + """ + s = np.asarray(signals, dtype=float) + s = s[:, None] if s.ndim == 1 else s + return s @ real_sh(order, directions).T + + +def _filterbank(sig, cutoffs, fs): + # applyFilterbank: FIR of order 100 (fir1, Hamming), zero phase by + # dropping the 50 samples of group delay. + n_band, half = len(cutoffs) + 1, 50 + pad = np.vstack([sig, np.zeros((half, sig.shape[1]))]) + bands = [] + for b in range(n_band): + if b == 0: + h = ss.firwin(101, cutoffs[0], fs=fs) + elif b == n_band - 1: + h = ss.firwin(101, cutoffs[-1], fs=fs, pass_zero=False) + else: + h = ss.firwin(101, [cutoffs[b - 1], cutoffs[b]], fs=fs, pass_zero=False) + bands.append(ss.lfilter(h, 1, pad, axis=0)[half:]) + return bands + + +def _decode(sig, D, cutoffs, fs, channels): + D = np.asarray(D, dtype=float) + D = D[:, :, None] if D.ndim == 2 else D + n_band = 1 if cutoffs is None else len(cutoffs) + 1 + if D.shape[2] != n_band: + raise ValueError('One decoding matrix is needed for each frequency band.') + if n_band == 1: + return sig @ D[:, :channels, 0].T + return sum(b @ D[:, :channels, k].T + for k, b in enumerate(_filterbank(sig, cutoffs, fs))) + + +def decode(hoa, D, cutoffs=None, fs=None): + """ + Decode HOA signals with a decoding matrix (decodeHOA_N3D). + + D: (loudspeakers, (order + 1)^2), or (loudspeakers, (order + 1)^2, + bands) with cutoffs (bands - 1 frequencies in Hz) and fs for a decoding + in frequency bands, split by FIR filters of order 100. + """ + hoa = np.asarray(hoa, dtype=float) + return _decode(hoa, D, cutoffs, fs, hoa.shape[1]) + + +def encode_bformat(signals, directions): + """First-order FuMa B-format (W/sqrt(2), X, Y, Z) of sources (encodeBformat).""" + s = np.asarray(signals, dtype=float) + s = s[:, None] if s.ndim == 1 else s + u = _unit(directions) + return s @ np.column_stack([np.ones(len(u)) / np.sqrt(2), u]) + + +def decode_bformat(bformat, D, cutoffs=None, fs=None): + """Decode FuMa B-format with the first 4 columns of D (decodeBformat).""" + return _decode(fuma_to_n3d(bformat), D, cutoffs, fs, 4) + + +def n3d_to_sn3d(sig): + """N3D to SN3D normalization (convert_N3D_SN3D, 'n2sn').""" + sig = np.asarray(sig, dtype=float) + n = np.floor(np.sqrt(np.arange(sig.shape[1]))) + return sig / np.sqrt(2 * n + 1) + + +def sn3d_to_n3d(sig): + """SN3D to N3D normalization (convert_N3D_SN3D, 'sn2n').""" + sig = np.asarray(sig, dtype=float) + n = np.floor(np.sqrt(np.arange(sig.shape[1]))) + return sig * np.sqrt(2 * n + 1) + + +_FUMA2ACN = np.array([1, 3, 4, 2, 9, 7, 5, 6, 8, 16, 14, 12, 10, 11, 13, 15]) - 1 +_NORM_FUMA2N3D = np.array([np.sqrt(2), np.sqrt(3), np.sqrt(3), np.sqrt(3), + np.sqrt(15) / 2, np.sqrt(15) / 2, np.sqrt(5), np.sqrt(15) / 2, + np.sqrt(15) / 2, np.sqrt(35 / 8), np.sqrt(35) / 3, + np.sqrt(224 / 45), np.sqrt(7), np.sqrt(224 / 45), + np.sqrt(35) / 3, np.sqrt(35 / 8)]) + + +def _fuma_order(sig): + sig = np.asarray(sig, dtype=float) + order = _order(sig.shape[1]) + if order > 3: + warnings.warn('FuMa is defined up to third order; keeping the first 16 channels.') + sig, order = sig[:, :16], 3 + return sig, (order + 1)**2 + + +def fuma_to_n3d(sig): + """FuMa (up to third order) to N3D, ACN (convert_N3D_FuMa, 'fuma2n').""" + sig, nsh = _fuma_order(sig) + return sig[:, _FUMA2ACN[:nsh]] * _NORM_FUMA2N3D[:nsh] + + +def n3d_to_fuma(sig): + """N3D, ACN to FuMa (up to third order) (convert_N3D_FuMa, 'n2fuma').""" + sig, nsh = _fuma_order(sig) + acn2fuma = np.argsort(_FUMA2ACN) + norm = (1 / _NORM_FUMA2N3D)[acn2fuma] + return sig[:, acn2fuma[:nsh]] * norm[:nsh] + + +def _sid_index(order): + idx = [0] + for n in range(1, order + 1): + ms = [m for k in range(1, n + 1) for m in (n - k + 1, k - n - 1)] + [0] + idx += [n * n + n + m for m in ms] + return np.array(idx) + + +def acn_to_sid(sig): + """ACN to SID channel ordering (convert_ACN_SID, 'acn2sid').""" + sig = np.asarray(sig) + return sig[:, _sid_index(_order(sig.shape[1]))] + + +def sid_to_acn(sig): + """SID to ACN channel ordering (convert_ACN_SID, 'sid2acn').""" + sig = np.asarray(sig) + return sig[:, np.argsort(_sid_index(_order(sig.shape[1])))] + + +def bformat_fuma(sig, convention): + """ + First-order FuMa B-format (W, X, Y, Z) from full-sphere Ambisonics of + any order, as Iceberg's native_bformat_fuma. + + convention: 'fuma', 'ambix' (ACN, SN3D) or 'n3d' (ACN, N3D). The first + four channels hold the first order in all three, so the conversion of + those four is exact. + + Returns + ------- + (numpy.ndarray, int) + (samples, 4) FuMa B-format and the order of the input. + """ + sig = np.asarray(sig, dtype=float) + if sig.ndim != 2 or sig.shape[1] < 4: + raise ValueError('Ambisonics B-format needs (order + 1)^2 channels, 4 or more.') + order = _order(sig.shape[1]) + c = convention.lower() + if c == 'fuma': + return sig[:, :4], order + if c == 'ambix': + return np.column_stack([sig[:, 0] / np.sqrt(2), sig[:, 3], sig[:, 1], sig[:, 2]]), order + if c == 'n3d': + return np.column_stack([sig[:, 0] / np.sqrt(2), sig[:, [3, 1, 2]] / np.sqrt(3)]), order + raise ValueError("convention must be 'fuma', 'ambix' or 'n3d'.") + + +def max_re_weights(order): + """ + Max-rE weights a_n = P_n(cos(137.9 deg / (order + 1.51))), repeated per + channel (getMaxREweights). + """ + x = np.cos(np.radians(137.9) / (order + 1.51)) + return np.concatenate([np.full(2 * n + 1, eval_legendre(n, x)) + for n in range(order + 1)]) + + +def layout_order(ls_directions): + """ + Ambisonic order a 3D loudspeaker layout supports, from the mean energy + spread of VBAP at the centres of its triangles (getLayoutAmbisonicOrder). + """ + ls = _dirs(ls_directions) + U = _unit(ls) + centres = _directions(np.array([U[f].sum(axis=0) for f in ConvexHull(U).simplices])) + groups = vbap.loudspeaker_triplets(ls) + g = vbap.gains(centres, groups, vbap.inverse_matrices(ls, groups)) + rE = np.linalg.norm(g**2 @ U, axis=1) + spread = 2 * np.degrees(np.arccos(np.clip(rE, -1, 1))) + order = int(np.floor(2 * 137.9 / np.mean(spread) - 1.52 + 0.5)) + return max(order, 1) + + +def _fzero(f, x0): + # MATLAB fzero from a starting point: widen [x0 - dx, x0 + dx] by + # sqrt(2) until the sign changes, then solve on that bracket. + dx = x0 / 50 if x0 else 1 / 50 + a = b = x0 + fa = fb = f(x0) + while (fa > 0) == (fb > 0): + dx *= np.sqrt(2) + a, fa = x0 - dx, f(x0 - dx) + if (fa > 0) != (fb > 0): + break + b, fb = x0 + dx, f(x0 + dx) + return brentq(f, min(a, b), max(a, b), xtol=1e-14, rtol=4 * np.finfo(float).eps) + + +def _energy_spread(G, U): + E = np.sum(G**2, axis=1) + rE = np.linalg.norm((G**2 @ U) / E[:, None], axis=1) + return np.degrees(2 * np.arccos(np.clip(2 * rE - 1, -1, 1))) + + +def _csad(ls, order): + U = _unit(ls) + _, tdirs = t_design(4 * order) + azi, ele = np.arange(-180, 181, 5.), np.arange(-90, 91, 5.) + grid = np.column_stack([np.tile(azi, len(ele)), np.repeat(ele, len(azi))]) + groups = vbap.loudspeaker_triplets(ls) + inv = vbap.inverse_matrices(ls, groups) + max_spread = np.max(_energy_spread(vbap.energy_gains(grid, groups, inv), U)) + G = np.zeros((len(ls), len(tdirs))) + for k, d in enumerate(tdirs): + def excess(x): + g = vbap.energy_gains(d, groups, inv, x, 32, 4) + return _energy_spread(g, U)[0] - max_spread + G[:, k] = vbap.energy_gains(d, groups, inv, _fzero(excess, max_spread), 32, 4)[0] + Y = real_sh(order, tdirs) + return G @ Y.T @ np.linalg.inv(Y @ Y.T) + + +def decoder(ls_directions, method='sad', max_re=True, order=None): + """ + Ambisonic decoding matrix for a loudspeaker layout (ambiDecoder). + + Parameters + ---------- + ls_directions : array_like + (loudspeakers, 2) [azimuth, elevation] in degrees. + method : str + 'sad' sampling (4 pi / L Y^T), 'mmd' mode matching (pseudo-inverse), + 'epad' energy preserving, 'allrad' all-round (VBAP of a 21-design, + 3D layouts) or 'csad' constant angular spread (3D layouts). + max_re : bool + Apply the max-rE weights (not for 'csad'). Default True. + order : int, optional + Decoding order; by default the order the layout supports. + + Returns + ------- + (numpy.ndarray, int) + D, (loudspeakers, (order + 1)^2), and the order. + """ + ls = _dirs(ls_directions) + if order is None: + order = layout_order(ls) + L = len(ls) + Y = real_sh(order, ls) + m = method.lower() + if m == 'sad': + D = 4 * np.pi / L * Y.T + elif m == 'mmd': + D = np.linalg.pinv(Y) + elif m == 'epad': + U, _, Vh = np.linalg.svd(Y) + D = 4 * np.pi / L * Vh.T[:, :(order + 1)**2] @ U.T + elif m == 'allrad': + _, tdirs = t_design(21) + groups = vbap.loudspeaker_triplets(ls) + G = vbap.gains(tdirs, groups, vbap.inverse_matrices(ls, groups)).T + D = 4 * np.pi / len(tdirs) * G @ real_sh(order, tdirs).T + elif m == 'csad': + D = _csad(ls, order) + else: + raise ValueError("method must be 'sad', 'mmd', 'epad', 'allrad' or 'csad'.") + if max_re and m != 'csad': + D = D * max_re_weights(order) + return D, order + + +def _euler_zyx(alpha, beta, gamma): + # euler2rotationMatrix(alpha, beta, gamma, 'zyx') = Rx(gamma) Ry(beta) Rz(alpha) + c, s = np.cos, np.sin + Rz = np.array([[c(alpha), s(alpha), 0], [-s(alpha), c(alpha), 0], [0, 0, 1]]) + Ry = np.array([[c(beta), 0, -s(beta)], [0, 1, 0], [s(beta), 0, c(beta)]]) + Rx = np.array([[1, 0, 0], [0, c(gamma), s(gamma)], [0, -s(gamma), c(gamma)]]) + return Rx @ Ry @ Rz + + +def sh_rotation(R, order): + """ + Rotation matrix of real spherical harmonics up to order, from a 3 x 3 + rotation matrix R, by the recursion of Ivanic and Ruedenberg + (getSHrotMtx, real basis). + """ + out = np.zeros(((order + 1)**2,) * 2) + out[0, 0] = 1 + R1 = np.array([[R[1, 1], R[1, 2], R[1, 0]], + [R[2, 1], R[2, 2], R[2, 0]], + [R[0, 1], R[0, 2], R[0, 0]]]) + if order == 0: + return out + out[1:4, 1:4] = R1 + prev = R1 + + def P(i, l, a, b): + if b == -l: + return R1[i + 1, 2] * prev[a + l - 1, 0] + R1[i + 1, 0] * prev[a + l - 1, 2 * l - 2] + if b == l: + return R1[i + 1, 2] * prev[a + l - 1, 2 * l - 2] - R1[i + 1, 0] * prev[a + l - 1, 0] + return R1[i + 1, 1] * prev[a + l - 1, b + l - 1] + + for l in range(2, order + 1): + Rl = np.zeros((2 * l + 1, 2 * l + 1)) + for m in range(-l, l + 1): + for n in range(-l, l + 1): + d = 1 if m == 0 else 0 + denom = (2 * l) * (2 * l - 1) if abs(n) == l else (l * l - n * n) + u = np.sqrt((l * l - m * m) / denom) + v = np.sqrt((1 + d) * (l + abs(m) - 1) * (l + abs(m)) / denom) * (1 - 2 * d) * 0.5 + w = np.sqrt((l - abs(m) - 1) * (l - abs(m)) / denom) * (1 - d) * (-0.5) + if u: + u *= P(0, l, m, n) + if v: + if m == 0: + v *= P(1, l, 1, n) + P(-1, l, -1, n) + elif m > 0: + dm = 1 if m == 1 else 0 + v *= P(1, l, m - 1, n) * np.sqrt(1 + dm) - P(-1, l, -m + 1, n) * (1 - dm) + else: + dm = 1 if m == -1 else 0 + v *= P(1, l, m + 1, n) * (1 - dm) + P(-1, l, -m - 1, n) * np.sqrt(1 + dm) + if w: + w *= (P(1, l, m + 1, n) + P(-1, l, -m - 1, n) if m > 0 else + P(1, l, m - 1, n) - P(-1, l, -m + 1, n)) + Rl[m + l, n + l] = u + v + w + out[l * l:(l + 1)**2, l * l:(l + 1)**2] = Rl + prev = Rl + return out + + +def rotate(hoa, yaw, pitch, roll): + """ + Rotate a HOA sound scene, N3D, by yaw, pitch and roll in degrees + (rotateHOA_N3D). + """ + hoa = np.asarray(hoa, dtype=float) + R = _euler_zyx(-np.radians(yaw), -np.radians(pitch), np.radians(roll)) + return hoa @ sh_rotation(R, _order(hoa.shape[1])).T + + +def rotate_bformat(bformat, yaw, pitch, roll): + """Rotate first-order FuMa B-format by yaw, pitch and roll in degrees (rotateBformat).""" + R = np.eye(4) + R[1:, 1:] = _euler_zyx(-np.radians(yaw), -np.radians(pitch), np.radians(roll)) + return np.asarray(bformat, dtype=float) @ R.T diff --git a/pytta/vbap.py b/pytta/vbap.py index 6448614..0cf21a0 100644 --- a/pytta/vbap.py +++ b/pytta/vbap.py @@ -12,6 +12,7 @@ >>> groups = pytta.vbap.loudspeaker_triplets(directions) # 3D >>> inverses = pytta.vbap.inverse_matrices(directions, groups) >>> G = pytta.vbap.gains(source_directions, groups, inverses, spread=0) + >>> G = pytta.vbap.energy_gains(source_directions, groups, inverses) Directions are in degrees: azimuth for 2D layouts, [azimuth, elevation] for 3D ones, with x = cos(azi) cos(elev), y = sin(azi) cos(elev) and z = @@ -24,7 +25,7 @@ This module is a port to Python of the MATLAB library Vector-Base-Amplitude-Panning by Archontis Politis and Ville Pulkki (2015), https://github.com/polarch/Vector-Base-Amplitude-Panning (findLsPairs.m, -findLsTriplets.m, invertLsMtx.m, vbap.m, getSpreadSrcDirs.m), under the BSD +findLsTriplets.m, invertLsMtx.m, vbap.m, vbip.m, getSpreadSrcDirs.m), under the BSD 3-Clause licence reproduced below. The gains follow the MATLAB code operation by operation. @@ -177,8 +178,9 @@ def inverse_matrices(directions, groups): return np.linalg.inv(U[np.asarray(groups)]) -def _spread_directions(direction, spread, num_src=8): - # getSpreadSrcDirs with its defaults: num_src sources, one ring in 3D. +def _spread_directions(direction, spread, num_src=8, num_rings=1): + # getSpreadSrcDirs: num_src sources across the spread in 2D; in 3D + # num_rings concentric rings of num_src sources and the source itself. if np.ndim(direction) == 0: d = np.radians(np.linspace(direction - spread / 2, direction + spread / 2, num_src + 1)) @@ -196,17 +198,20 @@ def _spread_directions(direction, spread, num_src=8): base[0] = c / np.sqrt(np.sum(c**2)) for n in range(1, num_src): base[n] = R @ base[n - 1] - ring = u + base * np.tan(np.radians(spread / 2)) - ring = ring / np.sqrt(np.sum(ring**2, axis=1))[:, None] - return np.vstack([ring, u]) + ring = np.radians(spread / 2) / num_rings + rings = np.vstack([u + base * np.tan(ring * (r + 1)) for r in range(num_rings)]) + rings = rings / np.sqrt(np.sum(rings**2, axis=1))[:, None] + return np.vstack([rings, u]) -def _group_gains(u, groups, inverses, first_only): +def _group_gains(u, groups, inverses, first_only, energy=False): + # One direction: the gains of the first (3D) or last (2D) group that + # holds it, normalized in amplitude (VBAP) or in energy (VBIP). g = np.zeros(int(groups.max()) + 1) for group, inv in zip(groups, inverses): g_tmp = u @ inv if np.min(g_tmp) > -0.001: - g[group] = g_tmp / np.sqrt(np.sum(g_tmp**2)) + g[group] = g_tmp / (np.sum(g_tmp) if energy else np.sqrt(np.sum(g_tmp**2))) if first_only: break return g @@ -252,3 +257,47 @@ def gains(directions, groups, inverses, spread=0.): with np.errstate(invalid='ignore'): out[n] = np.fmax(0, g / np.sqrt(np.sum(g**2))) return out + + +def energy_gains(directions, groups, inverses, spread=0., num_src=8, + num_rings=1): + """ + Vector base intensity panning gains (vbip). + + The energies of the loudspeakers of a group, not their amplitudes, + weight the loudspeaker vectors into the source direction; the gains are + their square roots, normalized in energy. + + Parameters + ---------- + directions, groups, inverses + As in gains. + spread : float, optional + Spread in degrees: the energies of num_src directions across it (in + 3D num_rings rings of num_src and the source) are summed. + num_src, num_rings : int, optional + Spread directions, 8 and 1 by default. + + Returns + ------- + numpy.ndarray + (sources, loudspeakers) gains, of norm 1 per source. + """ + groups = np.asarray(groups) + three_d = groups.shape[1] == 3 + directions = np.asarray(directions, dtype=float) + directions = directions.reshape(-1, 2) if three_d else directions.reshape(-1) + out = np.zeros((len(directions), int(groups.max()) + 1)) + for n, d in enumerate(directions): + if spread: + e = sum(_group_gains(u, groups, inverses, three_d, energy=True) + for u in _spread_directions(d, spread, num_src, num_rings)) + else: + e = _group_gains(_cartesian(np.atleast_2d(d) if three_d else [d])[0], + groups, inverses, three_d, energy=True) + with np.errstate(invalid='ignore'): + e = e / np.sum(e) + if three_d: + e[e < 0] = 0 + out[n] = np.fmax(0, np.sqrt(e)) + return out diff --git a/tests/golden/ambisonics_polarch.mat b/tests/golden/ambisonics_polarch.mat new file mode 100644 index 0000000000000000000000000000000000000000..7cb2a8b3c32d9cf5690456b55b1c66f6adf2b61f GIT binary patch literal 85660 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 z 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 Xy%8TA{teQ5GSHFTLPR8ALR$JCm);BJ literal 0 HcmV?d00001 diff --git a/tests/golden/make_ambisonics_polarch.m b/tests/golden/make_ambisonics_polarch.m new file mode 100644 index 0000000..b12a1e9 --- /dev/null +++ b/tests/golden/make_ambisonics_polarch.m @@ -0,0 +1,44 @@ +% Golden data for tests/test_ambisonics.py: polarch Higher-Order-Ambisonics +% and Vector-Base-Amplitude-Panning (Archontis Politis, BSD 3-Clause), the +% copies that Iceberg installs. Three functions they take from polarch +% Spherical-Harmonic-Transform are replaced by minimal versions: getTdesign +% (the t-designs of pytta/_tdesigns.py), sphDelaunay (the faces of the +% convex hull) and euler2rotationMatrix ('zyx'). rotateHOA_N3D is not here; +% the test checks the rotation by a rule. Run from tests/golden with those +% on the path. +rng(1, 'twister'); +dirs = [rand(20,1)*360-180, asind(rand(20,1)*2-1)]; +Y4 = getRSH(4, dirs); +sig3 = rand(8, 16) - 0.5; % third order, N3D ACN +conv.n2sn = convert_N3D_SN3D(sig3, 'n2sn'); +conv.sn2n = convert_N3D_SN3D(sig3, 'sn2n'); +conv.n2fuma = convert_N3D_FuMa(sig3, 'n2fuma'); +conv.fuma2n = convert_N3D_FuMa(sig3, 'fuma2n'); +conv.acn2sid = convert_ACN_SID(sig3, 'acn2sid'); +conv.sid2acn = convert_ACN_SID(sig3, 'sid2acn'); +maxre = {getMaxREweights(1), getMaxREweights(3), getMaxREweights(5)}; +ls_hemi = [0 0; 90 0; 180 0; -90 0; 45 45; 135 45; -135 45; -45 45; 0 90]; +ls_full = [(0:90:270)' -30*ones(4,1); (0:45:315)' zeros(8,1); (30:60:330)' 45*ones(6,1); 0 90]; +order_hemi = getLayoutAmbisonicOrder(ls_hemi); +order_full = getLayoutAmbisonicOrder(ls_full); +D.sad = ambiDecoder(ls_full, 'sad', 1, 3); +D.mmd = ambiDecoder(ls_full, 'mmd', 1, 3); +D.epad = ambiDecoder(ls_full, 'epad', 0, 3); +D.allrad = ambiDecoder(ls_full, 'allrad', 1, 3); +D.csad = ambiDecoder(ls_full, 'csad', 1, 2); +[D.auto, D.auto_order] = ambiDecoder(ls_full, 'sad'); +src = [30 10; -100 40]; +s2 = rand(16, 2) - 0.5; +enc3 = encodeHOA_N3D(3, s2, src); +rng(2, 'twister'); +hoa2 = rand(400, 9) - 0.5; % numpy: RandomState(2).random_sample((9, 400)).T - 0.5 +Db = cat(3, ambiDecoder(ls_full,'sad',1,2), ambiDecoder(ls_full,'mmd',1,2), ambiDecoder(ls_full,'allrad',1,2)); +dec_bands = decodeHOA_N3D(hoa2, Db, [500 2000], 48000); +bf = encodeBformat(s2, src); +D1 = ambiDecoder(ls_full, 'sad', 1, 1); +bf_dec = decodeBformat(bf, D1); +rotbf = rotateBformat(bf, 30, -20, 10); +save('ambisonics_polarch.mat', 'dirs', 'Y4', 'sig3', 'conv', 'maxre', 'ls_hemi', 'ls_full', ... + 'order_hemi', 'order_full', 'D', 'src', 's2', 'enc3', 'Db', 'dec_bands', ... + 'bf', 'D1', 'bf_dec', 'rotbf'); +fprintf('orders: hemisphere %d, full %d, auto %d\n', order_hemi, order_full, D.auto_order); diff --git a/tests/test_ambisonics.py b/tests/test_ambisonics.py new file mode 100644 index 0000000..c95bad4 --- /dev/null +++ b/tests/test_ambisonics.py @@ -0,0 +1,106 @@ +""" +Tests for pytta.ambisonics, a port of the Higher-Order-Ambisonics library +of Archontis Politis. Each test pins one rule. + +The golden data come from golden/make_ambisonics_polarch.m (MATLAB R2026b +with polarch Higher-Order-Ambisonics and Vector-Base-Amplitude-Panning). +""" +import os +import unittest + +import numpy as np +import scipy.io as sio + +from pytta import ambisonics as A + +GOLDEN = os.path.join(os.path.dirname(__file__), 'golden') + + +class TestAmbisonics(unittest.TestCase): + + def test_real_sh_are_orthonormal(self): + # Gauss-Legendre in elevation (12 nodes) and 24 azimuths integrate + # exactly every product of harmonics up to order 10, so + # sum w Y Y^T = I. The 21-design does the same with equal weights, + # (4 pi / N) Y Y^T = I, within the precision of its tabulated points, + # which falls with the order (4e-10 at order 4, 2.5e-6 at order 10). + x, w = np.polynomial.legendre.leggauss(12) + el, az = np.meshgrid(np.degrees(np.arcsin(x)), np.arange(24) * 15., indexing='ij') + Y = A.real_sh(10, np.column_stack([az.ravel(), el.ravel()])) + err_gl = np.abs((Y * np.repeat(w, 24) * np.pi / 12) @ Y.T - np.eye(121)).max() + _, dirs = A.t_design(21) + Y = A.real_sh(6, dirs) + err_td = np.abs(4 * np.pi / len(dirs) * Y @ Y.T - np.eye(49)).max() + print(f'\n Gauss-Legendre, order 10: {err_gl:.1e}; 21-design, order 6: {err_td:.1e}') + self.assertLess(err_gl, 1e-13) + self.assertLess(err_td, 1e-8) + + def test_first_order_is_fuma_bformat(self): + # FuMa B-format is W = s / sqrt(2), X, Y, Z = s times the unit + # vector. The first order of an N3D encoding converts to it, scaled + # by the 1 / sqrt(4 pi) of the orthonormal harmonics, and so does + # Iceberg's bformat_fuma from a third-order AmbiX, N3D or FuMa scene. + rng = np.random.default_rng(4) + s, dirs = rng.uniform(-1, 1, (32, 3)), np.array([[30, 10], [-100, 40], [170, -60]]) + bf = A.encode_bformat(s, dirs) / np.sqrt(4 * np.pi) + hoa = A.encode(s, dirs, 3) + errs = [np.abs(A.n3d_to_fuma(hoa[:, :4]) - bf).max(), + np.abs(A.bformat_fuma(hoa, 'n3d')[0] - bf).max(), + np.abs(A.bformat_fuma(A.n3d_to_sn3d(hoa), 'ambix')[0] - bf).max(), + np.abs(A.bformat_fuma(A.n3d_to_fuma(hoa), 'fuma')[0] - bf).max()] + print('\n ' + ', '.join(f'{e:.1e}' for e in errs)) + self.assertLess(max(errs), 1e-14) + self.assertEqual(A.bformat_fuma(hoa, 'n3d')[1], 3) + + def test_rotation_moves_the_encoded_direction(self): + # Rotating a scene with one source equals encoding the source in + # the rotated direction, M u, with M = Rx(roll) Ry(-pitch) Rz(-yaw); + # a positive yaw adds to the azimuth. + s, u = np.ones((1, 1)), np.array([[25., 15.]]) + yawed = A.rotate(A.encode(s, u, 4), 40, 0, 0) + e_yaw = np.abs(yawed - A.encode(s, [[65., 15.]], 4)).max() + M = A._euler_zyx(-np.radians(30), np.radians(20), np.radians(10)) + moved = A._directions((M @ A._unit(u)[0])[None]) + e_any = np.abs(A.rotate(A.encode(s, u, 4), 30, -20, 10) + - A.encode(s, moved, 4)).max() + print(f'\n yaw only {e_yaw:.1e}, yaw pitch roll {e_any:.1e}') + self.assertLess(max(e_yaw, e_any), 1e-14) + + def test_matches_polarch(self): + d = sio.loadmat(os.path.join(GOLDEN, 'ambisonics_polarch.mat'), + squeeze_me=True, struct_as_record=False) + c, D, L, sig3 = d['conv'], d['D'], d['ls_full'], d['sig3'] + hoa2 = np.random.RandomState(2).random_sample((9, 400)).T - 0.5 + self.assertEqual(A.layout_order(d['ls_hemi']), d['order_hemi']) + self.assertEqual(A.layout_order(L), d['order_full']) + D_auto, order = A.decoder(L, 'sad') + self.assertEqual(order, D.auto_order) + diffs = { + 'real_sh': (A.real_sh(4, d['dirs']), d['Y4']), + 'n3d_to_sn3d': (A.n3d_to_sn3d(sig3), c.n2sn), + 'sn3d_to_n3d': (A.sn3d_to_n3d(sig3), c.sn2n), + 'n3d_to_fuma': (A.n3d_to_fuma(sig3), c.n2fuma), + 'fuma_to_n3d': (A.fuma_to_n3d(sig3), c.fuma2n), + 'acn_to_sid': (A.acn_to_sid(sig3), c.acn2sid), + 'sid_to_acn': (A.sid_to_acn(sig3), c.sid2acn), + 'max_re_weights': (np.concatenate([A.max_re_weights(o) for o in (1, 3, 5)]), + np.concatenate(d['maxre'])), + 'sad': (A.decoder(L, 'sad', True, 3)[0], D.sad), + 'mmd': (A.decoder(L, 'mmd', True, 3)[0], D.mmd), + 'epad': (A.decoder(L, 'epad', False, 3)[0], D.epad), + 'allrad': (A.decoder(L, 'allrad', True, 3)[0], D.allrad), + 'csad': (A.decoder(L, 'csad', True, 2)[0], D.csad), + 'sad, order of the layout': (D_auto, D.auto), + 'encode': (A.encode(d['s2'], d['src'], 3), d['enc3']), + 'decode in 3 bands': (A.decode(hoa2, d['Db'], [500, 2000], 48000), d['dec_bands']), + 'encode_bformat': (A.encode_bformat(d['s2'], d['src']), d['bf']), + 'decode_bformat': (A.decode_bformat(d['bf'], d['D1']), d['bf_dec']), + 'rotate_bformat': (A.rotate_bformat(d['bf'], 30, -20, 10), d['rotbf'])} + worst = {k: np.abs(a - b).max() for k, (a, b) in diffs.items()} + print('\n ' + ', '.join(f'{k} {v:.1e}' for k, v in worst.items())) + for k, v in worst.items(): + self.assertLess(v, 1e-13, k) + + +if __name__ == '__main__': + unittest.main()