用Manim模仿三维地球仪的球极坐标

这段代码使用Manim库创建了一个3D球极坐标可视化演示动画可视化球坐标系中的几何图形,特别展示了如何将球极坐标(r, θ, φ)转换为三维笛卡尔坐标,并在球体表面上绘制曲线。具体来说,它实现了以下功能:

1. 场景设置

  • 创建三维场景,设置初始相机视角
  • 建立三维坐标系(X, Y, Z轴)

2. 主要视觉元素

  • 透明球体:作为参考框架,半径=3
  • 参考网格:球体表面的经纬网格
  • 两条示例曲线
    • 红色螺旋路径:φ随θ变化,形成螺旋效果
    • 绿色赤道圆:固定在φ=π/2(赤道平面)

3.代码分享

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
from manim import *
import numpy as np

class ThreeDWorldDemo(ThreeDScene):
def construct(self):
# 设置相机视角
self.set_camera_orientation(phi=45 * DEGREES, theta=-35 * DEGREES)

# 创建坐标系
axes = ThreeDAxes(
x_range=[-4, 4, 1],
y_range=[-4, 4, 1],
z_range=[-4, 4, 1],
x_length=8,
y_length=8,
z_length=8,
).add_coordinates()

# 创建球体
sphere = Sphere(
radius=3,
resolution=(60, 80),
stroke_width=0.5,
stroke_color=BLUE
).set_color(BLUE).set_opacity(0.3)

# 创建示例数据 - 模拟ggb中的点集合
# 这里创建两个示例曲线:一个螺旋线和一个圆形

# 曲线1: 螺旋线
list1 = []
for i in np.linspace(0, 2*np.pi, 50):
# (r, theta, phi) - 使用弧度制
r = 3.0 # 固定半径
theta = i # 经度角
phi = i * 0.5 # 纬度角,创建螺旋效果
if phi > np.pi: # 限制phi在[0, π]范围内
phi = np.pi - (phi - np.pi)
list1.append((r, theta, phi))

# 曲线2: 赤道上的圆
list2 = []
for i in np.linspace(0, 2*np.pi, 50):
r = 2.8 # 稍微小于球体半径
theta = i # 经度角变化
phi = np.pi/2 # 固定phi在π/2,表示赤道
list2.append((r, theta, phi))

# 定义球极坐标转三维坐标的函数
def polar_to_vector(r, theta, phi):
"""将球坐标(r, theta, phi)转换为笛卡尔坐标(x, y, z)"""
x = r * np.sin(phi) * np.cos(theta)
y = r * np.sin(phi) * np.sin(theta)
z = r * np.cos(phi)
return np.array([x, y, z])

# 转换坐标点
list_point1 = [polar_to_vector(r, theta, phi) for r, theta, phi in list1]
list_point2 = [polar_to_vector(r, theta, phi) for r, theta, phi in list2]

# 创建路径
path1 = VMobject()
path1.set_points_smoothly([*list_point1, list_point1[0]]) # 闭合曲线
path1.set_stroke(color=RED, width=3)
path1.set_fill(color=RED, opacity=0.2)

path2 = VMobject()
path2.set_points_smoothly([*list_point2, list_point2[0]]) # 闭合曲线
path2.set_stroke(color=GREEN, width=3)
path2.set_fill(color=GREEN, opacity=0.2)

# 添加标签
title = Text("球极坐标可视化", font_size=36, color=WHITE).to_edge(UP)
label1 = Text("螺旋路径", font_size=24, color=RED).next_to(path1, UP, buff=0.5)
label2 = Text("赤道圆", font_size=24, color=GREEN).next_to(path2, DOWN, buff=0.5)

# 添加参考网格
sphere_grid = Sphere(
radius=3,
resolution=(30, 30),
stroke_width=0.2,
stroke_color=BLUE,
fill_opacity=0
)

# 添加坐标轴标签
axes_labels = axes.get_axis_labels(
Tex("X").scale(0.7),
Tex("Y").scale(0.7),
Tex("Z").scale(0.7)
)

# 动画序列
self.begin_ambient_camera_rotation(rate=0.1) # 缓慢旋转相机

# 显示标题
self.add_fixed_in_frame_mobjects(title)
self.play(Write(title))
self.wait(0.5)

# 显示坐标系
self.play(Create(axes), Write(axes_labels))
self.wait(0.5)

# 显示球体和网格
self.play(
Create(sphere_grid),
Create(sphere)
)
self.wait(1)

# 显示第一条路径
self.play(Create(path1))
self.add_fixed_in_frame_mobjects(label1)
self.play(Write(label1))
self.wait(1)

# 显示第二条路径
self.play(Create(path2))
self.add_fixed_in_frame_mobjects(label2)
self.play(Write(label2))
self.wait(2)

# 停止相机旋转,然后围绕场景旋转查看
self.stop_ambient_camera_rotation()
self.move_camera(
phi=60 * DEGREES,
theta=-45 * DEGREES,
run_time=3
)
self.wait(2)

# 展示坐标转换点
# 从每条路径中选择几个点进行高亮显示
highlight_points1 = []
highlight_points2 = []

for i in range(0, len(list_point1), 10): # 每10个点取一个
point = Dot3D(point=list_point1[i], color=YELLOW, radius=0.05)
highlight_points1.append(point)

for i in range(0, len(list_point2), 10):
point = Dot3D(point=list_point2[i], color=PURPLE, radius=0.05)
highlight_points2.append(point)

highlight_group1 = VGroup(*highlight_points1)
highlight_group2 = VGroup(*highlight_points2)

self.play(
LaggedStart(
*[Create(point) for point in highlight_points1],
lag_ratio=0.1
),
LaggedStart(
*[Create(point) for point in highlight_points2],
lag_ratio=0.1
)
)
self.wait(2)

# 清理高亮点
self.play(
FadeOut(highlight_group1),
FadeOut(highlight_group2),
FadeOut(label1),
FadeOut(label2)
)
self.wait(1)

# 最终视图,缓慢旋转展示
self.begin_ambient_camera_rotation(rate=0.05)
self.wait(8)
self.stop_ambient_camera_rotation()

# 淡出所有元素
self.play(
FadeOut(title),
FadeOut(sphere_grid),
FadeOut(sphere),
FadeOut(path1),
FadeOut(path2),
FadeOut(axes),
FadeOut(axes_labels)
)

来看渲染的视频效果:

渲染的视频文件比较大,十六M左右,所以加载的时间也长一些,网站的服务器资源也有限,大家可以本地跑一下代码,不过因为电脑配置的因素,渲染的市场也会不同,希望能够帮助到大家。