Моделирование Псевдоэллипсоидов на Питоне

Программный код для моделирования:

—————————————————————————

import matplotlib.pyplot as plt

import numpy as np

from matplotlib.path import Path

import matplotlib.patches as patches

from scipy.optimize import fsolve

# — Параметры эллипсов

# rx — горизонтальная полуось, ry — вертикальная полуопись

rx = 2.0

ry = 1.0

# Расстояние от центра эллипса до фокуса

c = np.sqrt(rx**2 — ry**2)

# — Центры и фокусы каждого из четырех эллипсов —

# Эллипс 1 (справа вверху)

h_ell1_x, k_ell1_y = rx, 1.0

foci1_x1, foci1_y1 = h_ell1_x — c, k_ell1_y

foci1_x2, foci1_y2 = h_ell1_x + c, k_ell1_y

# Эллипс 2 (слева вверху)

h_ell2_x, k_ell2_y = -rx, 1.0

foci2_x1, foci2_y1 = h_ell2_x — c, k_ell2_y

foci2_x2, foci2_y2 = h_ell2_x + c, k_ell2_y

# Эллипс 3 (справа внизу)

h_ell3_x, k_ell3_y = rx, -1.0

foci3_x1, foci3_y1 = h_ell3_x — c, k_ell3_y

foci3_x2, foci3_y2 = h_ell3_x + c, k_ell3_y

# Эллипс 4 (слева внизу)

h_ell4_x, k_ell4_y = -rx, -1.0

foci4_x1, foci4_y1 = h_ell4_x — c, k_ell4_y

foci4_x2, foci4_y2 = h_ell4_x + c, k_ell4_y

# — Углы для построения полного эллипса —

theta = np.linspace(0, 2 * np.pi, 200)

# — Вспомогательная функция для получения координат эллипса —

def get_ellipse_coords(h, k, rx, ry, theta_vals):

    x = h + rx * np.cos(theta_vals)

    y = k + ry * np.sin(theta_vals)

    return x, y

# — Вспомогательная функция для уравнения эллипса (используется для поиска пересечений) —

def ellipse_equation(coords, h, k, rx, ry):

    «»»Возвращает значение уравнения эллипса для данных координат.»»»

x, y = coords

    return ((x — h)**2 / rx**2) + ((y — k)**2 / ry**2) — 1

# — Функция для нахождения точек пересечения двух эллипсов —

def find_ellipse_intersection(h1, k1, h2, k2, rx, ry, initial_guess):

    «»»

    Находит одну точку пересечения двух эллипсов, используя численное решение.

    Требует хорошего начального приближения (initial_guess) для сходимости.

«»»

    def equations(coords):

        eq1 = ellipse_equation(coords, h1, k1, rx, ry)

        eq2 = ellipse_equation(coords, h2, k2, rx, ry)

        return [eq1, eq2]

    solution = fsolve(equations, initial_guess)

    return solution

# — Функция для получения координат на дуге эллипса (для Псевдоэллипсоида) —

def get_ellipse_arc_coords_for_pseudospheroid(h, k, rx, ry, start_point, end_point, num_points=50):

    «»»

    Получает координаты точек на ‘внутренней’ дуге эллипса между двумя заданными точками.

«»»

    angle_start = np.arctan2(start_point[1] — k, start_point[0] — h)

    angle_end = np.arctan2(end_point[1] — k, end_point[0] — h)

    # Нормализация углов в диапазон [0, 2*pi) для корректного сравнения

angle_start = angle_start % (2 * np.pi)

    angle_end = angle_end % (2 * np.pi)

    # Выбираем кратчайший путь между углами (внутренняя дуга)

angle_diff = angle_end — angle_start

    if angle_diff > np.pi:

        angle_end -= 2 * np.pi

    elif angle_diff < -np.pi:

        angle_end += 2 * np.pi

    angles_segment = np.linspace(angle_start, angle_end, num_points)

    x_seg, y_seg = get_ellipse_coords(h, k, rx, ry, angles_segment)

    return x_seg, y_seg

# — Настройка графика —

plt.figure(figsize=(10, 10))

ax = plt.gca()

ax.set_title(‘Псевдоэллипсоид 2-го Порядка и Образующие Эллипсы‘, fontsize=16)

# — Построение контуров всех четырех эллипсов —

x_ell1, y_ell1 = get_ellipse_coords(h_ell1_x, k_ell1_y, rx, ry, theta)

x_ell2, y_ell2 = get_ellipse_coords(h_ell2_x, k_ell2_y, rx, ry, theta)

x_ell3, y_ell3 = get_ellipse_coords(h_ell3_x, k_ell3_y, rx, ry, theta)

x_ell4, y_ell4 = get_ellipse_coords(h_ell4_x, k_ell4_y, rx, ry, theta)

# Рисуем все эллипсы тонкими серыми линиями

ax.plot(x_ell1, y_ell1, color=’gray’, linestyle=’-‘, linewidth=1.5, alpha=0.7, label=’Исходные эллипсы‘)

ax.plot(x_ell2, y_ell2, color=’gray’, linestyle=’-‘, linewidth=1.5, alpha=0.7)

ax.plot(x_ell3, y_ell3, color=’gray’, linestyle=’-‘, linewidth=1.5, alpha=0.7)

ax.plot(x_ell4, y_ell4, color=’gray’, linestyle=’-‘, linewidth=1.5, alpha=0.7)

# — Нахождение точек пересечения для формирования «Псевдоэллипсоида» —

p1 = find_ellipse_intersection(h_ell1_x, k_ell1_y, h_ell2_x, k_ell2_y, rx, ry, initial_guess=[0, 1])

p2 = find_ellipse_intersection(h_ell2_x, k_ell2_y, h_ell4_x, k_ell4_y, rx, ry, initial_guess=[-rx, 0])

p3 = find_ellipse_intersection(h_ell4_x, k_ell4_y, h_ell3_x, k_ell3_y, rx, ry, initial_guess=[0, -1])

p4 = find_ellipse_intersection(h_ell3_x, k_ell3_y, h_ell1_x, k_ell1_y, rx, ry, initial_guess=[rx, 0])

# — Построение КОНТУРА Псевдоэллипсоида 2-го Порядка (без заливки) —

# Сегменты для внутренней фигуры

x_inner_seg1, y_inner_seg1 = get_ellipse_arc_coords_for_pseudospheroid(h_ell1_x, k_ell1_y, rx, ry, p4, p1) # Ellipse 1 (P4 to P1)

x_inner_seg2, y_inner_seg2 = get_ellipse_arc_coords_for_pseudospheroid(h_ell2_x, k_ell2_y, rx, ry, p1, p2) # Ellipse 2 (P1 to P2)

x_inner_seg3, y_inner_seg3 = get_ellipse_arc_coords_for_pseudospheroid(h_ell4_x, k_ell4_y, rx, ry, p2, p3) # Ellipse 4 (P2 to P3)

x_inner_seg4, y_inner_seg4 = get_ellipse_arc_coords_for_pseudospheroid(h_ell3_x, k_ell3_y, rx, ry, p3, p4) # Ellipse 3 (P3 to P4)

# Объединяем сегменты для внутренней фигуры

pseudospheroid_inner_x = np.concatenate((x_inner_seg1, x_inner_seg2, x_inner_seg3, x_inner_seg4))

pseudospheroid_inner_y = np.concatenate((y_inner_seg1, y_inner_seg2, y_inner_seg3, y_inner_seg4))

# Рисуем контур Псевдоэллипсоида 2-го порядка жирной черной линией

ax.plot(pseudospheroid_inner_x, pseudospheroid_inner_y, color=’black’, linewidth=3, label=’Псевдоэллипсоид 2-го порядка’)

# — Отображение фокусов —

foci_x_all = [foci1_x1, foci1_x2, foci2_x1, foci2_x2, foci3_x1, foci3_x2, foci4_x1, foci4_x2]

foci_y_all = [foci1_y1, foci1_y2, foci2_y1, foci2_y2, foci3_y1, foci3_y2, foci4_y1, foci4_y2]

ax.plot(foci_x_all, foci_y_all, ‘o’, color=’red’, markersize=6, zorder=5, label=’Фокусы эллипсов‘)

# — Подписи фокусов

ax.text(foci1_x1, foci1_y1 + 0.1, ‘F1′, color=’red’, fontsize=10, ha=’center’, va=’bottom’)

ax.text(foci1_x2, foci1_y2 + 0.1, ‘F2′, color=’red’, fontsize=10, ha=’center’, va=’bottom’)

ax.text(foci2_x1, foci2_y1 + 0.1, ‘F3′, color=’darkorange’, fontsize=10, ha=’center’, va=’bottom’)

ax.text(foci2_x2, foci2_y2 + 0.1, ‘F4′, color=’darkorange’, fontsize=10, ha=’center’, va=’bottom’)

ax.text(foci3_x1, foci3_y1 — 0.1, ‘F5′, color=’red’, fontsize=10, ha=’center’, va=’top’)

ax.text(foci3_x2, foci3_y2 — 0.1, ‘F6′, color=’red’, fontsize=10, ha=’center’, va=’top’)

ax.text(foci4_x1, foci4_y1 — 0.1, ‘F7′, color=’darkorange’, fontsize=10, ha=’center’, va=’top’)

ax.text(foci4_x2, foci4_y2 — 0.1, ‘F8′, color=’darkorange’, fontsize=10, ha=’center’, va=’top’)

# — Настройки осей и легенды —

ax.set_xlabel(‘X-ось’)

ax.set_ylabel(‘Y-ось‘)

ax.grid(True)

ax.set_aspect(‘equal’, adjustable=’box’)

plt.xlim([-5, 5])

plt.ylim([-3, 3])

# Размещение легенды

handles, labels = ax.get_legend_handles_labels()

unique_labels = dict(zip(labels, handles))

ax.legend(unique_labels.values(), unique_labels.keys(), loc=’upper left’, bbox_to_anchor=(1.05, 1),

          fancybox=True, shadow=True, ncol=1, fontsize=’small’)

plt.tight_layout(rect=[0, 0.03, 0.8, 0.95])

plt.show()

import matplotlib.pyplot as plt

import numpy as np

from matplotlib.path import Path

import matplotlib.patches as patches

from scipy.optimize import fsolve

from mpl_toolkits.mplot3d import Axes3D

# — Параметры эллипсов (из вашего кода) —

rx = 2.0

ry = 1.0

c = np.sqrt(rx**2 — ry**2)

# — Центры эллипсов (из вашего кода) —

h_ell1_x, k_ell1_y = rx, 1.0

h_ell2_x, k_ell2_y = -rx, 1.0

h_ell3_x, k_ell3_y = rx, -1.0

h_ell4_x, k_ell4_y = -rx, -1.0

# — Фокусы эллипсов (из вашего кода) —

# Для Эллипса 1 (справа вверху)

foci1_x1, foci1_y1 = h_ell1_x — c, k_ell1_y

foci1_x2, foci1_y2 = h_ell1_x + c, k_ell1_y

# Для Эллипса 2 (слева вверху)

foci2_x1, foci2_y1 = h_ell2_x — c, k_ell2_y

foci2_x2, foci2_y2 = h_ell2_x + c, k_ell2_y

# Для Эллипса 3 (справа внизу)

foci3_x1, foci3_y1 = h_ell3_x — c, k_ell3_y

foci3_x2, foci3_y2 = h_ell3_x + c, k_ell3_y

# Для Эллипса 4 (слева внизу)

foci4_x1, foci4_y1 = h_ell4_x — c, k_ell4_y

foci4_x2, foci4_y2 = h_ell4_x + c, k_ell4_y

# Собираем все фокусы в список для удобства

all_foci = [

    (foci1_x1, foci1_y1), (foci1_x2, foci1_y2),

    (foci2_x1, foci2_y1), (foci2_x2, foci2_y2),

    (foci3_x1, foci3_y1), (foci3_x2, foci3_y2),

    (foci4_x1, foci4_y1), (foci4_x2, foci4_y2)

]

# — Вспомогательная функция для получения координат эллипса —

def get_ellipse_coords(h, k, rx, ry, theta_vals):

    x = h + rx * np.cos(theta_vals)

    y = k + ry * np.sin(theta_vals)

    return x, y

# — Вспомогательная функция для уравнения эллипса —

def ellipse_equation(coords, h, k, rx, ry):

    x, y = coords

    return ((x — h)**2 / rx**2) + ((y — k)**2 / ry**2) — 1

# — Функция для нахождения точек пересечения двух эллипсов —

def find_ellipse_intersection(h1, k1, h2, k2, rx, ry, initial_guess):

    def equations(coords):

        eq1 = ellipse_equation(coords, h1, k1, rx, ry)

        eq2 = ellipse_equation(coords, h2, k2, rx, ry)

        return [float(eq1), float(eq2)]

    solution = fsolve(equations, initial_guess, xtol=1e-8, maxfev=1000)

    return solution

# — Функция для получения координат на дуге эллипса (для Псевдоэллипсоида) —

def get_ellipse_arc_coords_for_pseudospheroid(h, k, rx, ry, start_point, end_point, num_points=50):

    angle_start = np.arctan2(start_point[1] — k, start_point[0] — h)

    angle_end = np.arctan2(end_point[1] — k, end_point[0] — h)

    angle_start = angle_start % (2 * np.pi)

    angle_end = angle_end % (2 * np.pi)

    angle_diff = angle_end — angle_start

    if angle_diff > np.pi:

        angle_end -= 2 * np.pi

    elif angle_diff < -np.pi:

        angle_end += 2 * np.pi

    angles_segment = np.linspace(angle_start, angle_end, num_points)

    x_seg, y_seg = get_ellipse_coords(h, k, rx, ry, angles_segment)

    return x_seg, y_seg

# — Нахождение точек пересечения для формирования «Псевдоэллипсоида» —

p1 = find_ellipse_intersection(h_ell1_x, k_ell1_y, h_ell2_x, k_ell2_y, rx, ry, initial_guess=[0, 1])

p2 = find_ellipse_intersection(h_ell2_x, k_ell2_y, h_ell4_x, k_ell4_y, rx, ry, initial_guess=[-rx, 0])

p3 = find_ellipse_intersection(h_ell4_x, k_ell4_y, h_ell3_x, k_ell3_y, rx, ry, initial_guess=[0, -1])

p4 = find_ellipse_intersection(h_ell3_x, k_ell3_y, h_ell1_x, k_ell1_y, rx, ry, initial_guess=[rx, 0])

# — Построение КОНТУРА Псевдоэллипсоида 2-го Порядка —

x_inner_seg1, y_inner_seg1 = get_ellipse_arc_coords_for_pseudospheroid(h_ell1_x, k_ell1_y, rx, ry, p4, p1)

x_inner_seg2, y_inner_seg2 = get_ellipse_arc_coords_for_pseudospheroid(h_ell2_x, k_ell2_y, rx, ry, p1, p2)

x_inner_seg3, y_inner_seg3 = get_ellipse_arc_coords_for_pseudospheroid(h_ell4_x, k_ell4_y, rx, ry, p2, p3)

x_inner_seg4, y_inner_seg4 = get_ellipse_arc_coords_for_pseudospheroid(h_ell3_x, k_ell3_y, rx, ry, p3, p4)

# Объединяем сегменты в один массив для образующей

full_2d_contour_arr = np.vstack([

    np.column_stack((x_inner_seg1, y_inner_seg1)),

    np.column_stack((x_inner_seg2, y_inner_seg2)),

    np.column_stack((x_inner_seg3, y_inner_seg3)),

    np.column_stack((x_inner_seg4, y_inner_seg4))

])

# Удаляем дубликаты точек на стыках, если они есть

_, idx = np.unique(full_2d_contour_arr.round(decimals=6), axis=0, return_index=True)

full_2d_contour_arr = full_2d_contour_arr[np.sort(idx)]

# Замыкаем контур

if not np.allclose(full_2d_contour_arr[0], full_2d_contour_arr[-1]):

    full_2d_contour_arr = np.vstack([full_2d_contour_arr, full_2d_contour_arr[0]])

# — Функция для создания 3D-поверхности вращения —

def create_surface_of_revolution(contour_2d_points, axis_of_revolution=’x’, num_phi_points=50):

    if contour_2d_points.size == 0:

        return np.array([]), np.array([]), np.array([])

    phi_angles = np.linspace(0, 2 * np.pi, num_phi_points)

    X_surf = np.zeros((len(contour_2d_points), num_phi_points))

    Y_surf = np.zeros((len(contour_2d_points), num_phi_points))

    Z_surf = np.zeros((len(contour_2d_points), num_phi_points))

    for i, (x_2d, y_2d) in enumerate(contour_2d_points):

        radius = np.abs(y_2d) # Вращение вокруг X-оси, радиус = |y|

Y_circle = radius * np.cos(phi_angles)

        Z_circle = radius * np.sin(phi_angles)

        X_surf[i, :] = x_2d

        Y_surf[i, :] = Y_circle

        Z_surf[i, :] = Z_circle

    return X_surf, Y_surf, Z_surf

# — Функция для создания 3D-колец вокруг фокусов —

def create_foci_rings(foci_list, num_points_per_ring=50):

    rings_coords = []

    theta_ring = np.linspace(0, 2 * np.pi, num_points_per_ring)

    for fx, fy in foci_list:

        # Центр кольца в 3D будет (fx, 0, 0), так как вращаем вокруг X

        # Радиус кольца будет |fy|

        # Если fy очень близко к нулю, кольцо будет слишком маленьким или невидимым,

        # можно установить минимальный радиус или пропустить.

        if np.isclose(fy, 0, atol=1e-5): # Фокус лежит на оси вращения

continue

        radius = np.abs(fy)

        # Координаты кольца в 3D

        x_ring = np.full(num_points_per_ring, fx) # X-координата постоянна

        y_ring = radius * np.cos(theta_ring)

        z_ring = radius * np.sin(theta_ring)

        rings_coords.append(np.column_stack((x_ring, y_ring, z_ring)))

    return rings_coords

# — Создание 3D-графика

fig = plt.figure(figsize=(12, 10))

ax = fig.add_subplot(111, projection=’3d’)

ax.set_title(‘3D Псевдоэллипсоид 2-го Порядка с Кольцами Фокусов‘, fontsize=16)

# — Создание и отрисовка 3D-поверхности вращения —

X_surf, Y_surf, Z_surf = create_surface_of_revolution(full_2d_contour_arr, axis_of_revolution=’x’)

if X_surf.size > 0:

    ax.plot_surface(X_surf, Y_surf, Z_surf, color=’green’, alpha=0.6, rstride=1, cstride=1, edgecolor=’k’, linewidth=0.5)

else:

    print(«Не удалось создать 2D-контур псевдоэллипсоида. Проверьте параметры и пересечения.»)

# — Добавление 3D-колец фокусов —

foci_rings_3d = create_foci_rings(all_foci)

for ring in foci_rings_3d:

    ax.plot(ring[:, 0], ring[:, 1], ring[:, 2], color=’red’, linestyle=’—‘, linewidth=2, alpha=0.8) # Красные пунктирные кольца

# — Настройки осей —

ax.set_xlabel(‘X-ось’)

ax.set_ylabel(‘Y-ось‘)

ax.set_zlabel(‘Z-ось‘)

# Устанавливаем равный масштаб для всех осей для корректного отображения формы

if X_surf.size > 0:

    max_range = np.array([X_surf.max()-X_surf.min(), Y_surf.max()-Y_surf.min(), Z_surf.max()-Z_surf.min()]).max() / 2.0

mid_x = (X_surf.max()+X_surf.min()) * 0.5

    mid_y = (Y_surf.max()+Y_surf.min()) * 0.5

    mid_z = (Z_surf.max()+Z_surf.min()) * 0.5

    ax.set_xlim(mid_x — max_range, mid_x + max_range)

    ax.set_ylim(mid_y — max_range, mid_y + max_range)

    ax.set_zlim(mid_z — max_range, mid_z + max_range)

else:

    ax.set_xlim([-3, 3])

    ax.set_ylim([-3, 3])

    ax.set_zlim([-3, 3])

ax.set_aspect(‘auto’, adjustable=’box’)

# — Добавляем оси для наглядности —

ax.plot([ax.get_xlim()[0], ax.get_xlim()[1]], [0, 0], [0, 0], color=’gray’, linestyle=’—‘, linewidth=1) # Ось X

ax.plot([0, 0], [ax.get_ylim()[0], ax.get_ylim()[1]], [0, 0], color=’gray’, linestyle=’—‘, linewidth=1) # Ось Y

ax.plot([0, 0], [0, 0], [ax.get_zlim()[0], ax.get_zlim()[1]], color=’gray’, linestyle=’—‘, linewidth=1) # Ось Z

plt.show()

—————————————————————————

Псевдоэллипсоид 2 порядка формируется вращением замкнутой 2D-фигуры вокруг оси X. Эта 2D-фигура является комбинацией дуг четырех эллипсов.

Определение Образующей Фигуры (2D Контур)

Параметры исходных эллипсов:

Каждый из четырех эллипсов имеет одинаковые полуоси:

  • Горизонтальная полуось: rx​
  • Вертикальная полуось: ry​

Их центры расположены в точках:

  • Эллипс 1 (правый верхний): C1​=(rx​,1)
  • Эллипс 2 (левый верхний): C2​=(−rx​,1)
  • Эллипс 3 (правый нижний): C3​=(rx​,−1)
  • Эллипс 4 (левый нижний): C4​=(−rx​,−1)

Уравнение каждого эллипса i с центром (hi​,ki​) задается как: rx2​(x−hi​)2​+ry2​(y−ki​)2​=1

Точки пересечения (вершины образующей фигуры):

Образующая фигура формируется путем соединения «внутренних» дуг этих эллипсов. Точками соединения являются точки пересечения соседних эллипсов. Пусть эти точки будут Pj​=(xj​,yj​):

  • P1​: Пересечение Эллипса 1 и Эллипса 2.
  • P2​: Пересечение Эллипса 2 и Эллипса 4.
  • P3​: Пересечение Эллипса 4 и Эллипса 3.
  • P4​: Пересечение Эллипса 3 и Эллипса 1.

Эти точки находятся путем решения системы двух уравнений эллипсов. Например, для P1​ (пересечение Эллипса 1 и Эллипса 2):

​rx2​(x−rx​)2​+ry2​(y−1)2​=1

rx2​(x−(−rx​))2​+ry2​(y−1)2​=1​

Численно эти точки можно найти с помощью метода, подобного fsolve в Python, как показано в коде.

Сегменты образующей фигуры:

Образующая фигура состоит из четырех дуг эллипсов:

  1. Дуга Эллипса 1: от P4​ до P1​.
  2. Дуга Эллипса 2: от P1​ до P2​.
  3. Дуга Эллипса 4: от P2​ до P3​.
  4. Дуга Эллипса 3: от P3​ до P4​.

Каждая дуга эллипса с центром (h,k) и полуосями rx​,ry​ может быть параметризована углом θ: x(θ)=h+rx​cos(θ) y(θ)=k+ry​sin(θ)Для построения дуги между двумя точками Pstart​=(xstart​,ystart​) и Pend​=(xend​,yend​) сначала определяются их углы в параметрическом представлении относительно центра эллипса:θstart​=atan2(ystart​−k,xstart​−h) θend​=atan2(yend​−k,xend​−h) Затем генерируются точки для θ в диапазоне от θstart​ до θend​, выбирая кратчайший угловой путь.

Построение 3D псевдоэллипсоида вращения

Полученный 2D-контур, представляющий собой набор точек (x2D​,y2D​), вращается вокруг оси X на 360 градусов (от 0 до 2π).

Для каждой точки (x2D​,y2D​) на 2D-контуре, ее 3D-координаты (X,Y,Z) после вращения определяются следующим образом:

  • X=x2D​ (X-координата остается неизменной, так как вращение происходит вокруг оси X)
  • Радиус вращения R=∣y2D​∣ (абсолютное значение Y-координаты 2D-точки)
  • Y=Rcos(ϕ)
  • Z=Rsin(ϕ)

Где ϕ — это угол вращения в плоскости YZ, варьирующийся от 0 до 2π.

Таким образом, 3D-поверхность псевдоэллипсоида 2 порядка описывается набором точек (X,Y,Z), где X берется из 2D-контура, а Y и Z образуют окружность с радиусом, равным абсолютному значению Y-координаты соответствующей точки 2D-контура.

Кольца Фокусов (для справки)

Для визуализации дополнительных свойств, но не для самой формы псевдоэллипсоида, мы добавляем кольца, соответствующие фокусам исходных 2D-эллипсов.

Каждый эллипс имеет два фокуса F=(fx​,fy​). Расстояние от центра эллипса до его фокуса по главной оси определяется как c=rx2​−ry2​ ​ (при условии, что rx​>ry​).

В 3D-пространстве каждое кольцо фокуса j будет:

  • Лежать в плоскости X=fxj​.
  • Центрировано на оси X в точке (fxj​,0,0).
  • Иметь радиус RF​=∣fyj​∣.

Эти кольца демонстрируют, как фокусы исходных 2D-эллипсов «вытягиваются» в окружности при вращении.