triple_integral_manim.py
1from manim import *2import numpy as np3 4class TripleIntegralScene(ThreeDScene):5 def construct(self):6 # 设置更好的相机角度和更美观的外观7 self.set_camera_orientation(phi=70 * DEGREES, theta=20 * DEGREES, zoom=0.8)8 9 # 创建更美观的坐标轴10 axes = ThreeDAxes(11 x_range=[-2.5, 2.5, 1],12 y_range=[-2.5, 2.5, 1],13 z_range=[0, 4.5, 1],14 x_length=5,15 y_length=5,16 z_length=5,17 axis_config={"color": BLUE_B}18 )19 labels = axes.get_axis_labels(20 x_label=MathTex("x", color=BLUE_B),21 y_label=MathTex("y", color=BLUE_B),22 z_label=MathTex("z", color=BLUE_B)23 )24 25 # 调整布局 - 顶部标题和说明更靠上26 problem_title = Text("三重积分计算(先二后一)", font_size=36, color=YELLOW).to_edge(UP, buff=0.1)27 28 # 将中文和数学公式分开显示,调整位置 - 修正Ω为立体区域29 calc_text = Text("计算:", font_size=24, color=WHITE)30 calc_formula = MathTex(r"\iiint_{\Omega} (x^2 + y^2) \, dV", font_size=24, color=WHITE)31 calc_group = VGroup(calc_text, calc_formula).arrange(RIGHT, buff=0.1)32 33 # 全部使用Text显示中文内容,避免中文在LaTeX中的问题34 domain_text = Text("其中 Ω 为旋转抛物面 z = x² + y²", font_size=24, color=WHITE)35 domain_formula = Text("与平面 z = 4 围成的立体区域", font_size=24, color=WHITE)36 domain_group = VGroup(domain_text, domain_formula).arrange(DOWN, buff=0.1, aligned_edge=LEFT)37 38 # 将描述分组放在左上方区域39 problem_desc = VGroup(calc_group, domain_group).arrange(DOWN, buff=0.2).next_to(problem_title, DOWN, buff=0.1).to_edge(LEFT, buff=0.5)40 41 # 显示坐标轴42 self.play(Create(axes), Create(labels), run_time=1.5)43 44 # 添加题目和方程45 self.add_fixed_in_frame_mobjects(problem_title, problem_desc)46 self.play(Write(problem_title), Write(problem_desc), run_time=2)47 48 # 旋转抛物面 z = r^2, 0 ≤ r ≤ 2, 0 ≤ z ≤ 449 def paraboloid(u, v):50 r = u51 theta = v52 x = r * np.cos(theta)53 y = r * np.sin(theta)54 z = r**255 return np.array([x, y, z])56 57 # 创建更美观的表面58 surface = Surface(59 paraboloid,60 u_range=[0, 2],61 v_range=[0, TAU],62 fill_opacity=0.3,63 resolution=(30, 30),64 stroke_width=0.5,65 stroke_color=BLUE_E,66 checkerboard_colors=[BLUE_D, "#3070B0"]67 )68 69 # 显示旋转抛物面70 self.play(Create(surface, run_time=2))71 72 # 旋转相机视角73 self.move_camera(phi=65 * DEGREES, theta=50 * DEGREES)74 self.wait(0.5)75 76 # 创建所有场景元素,但暂不显示77 78 # 定义z_tracker 79 self.z_tracker = ValueTracker(0.1)80 81 # 创建一个随z值变化的半透明截面平面82 section_plane = always_redraw(83 lambda: Surface(84 lambda u, v: np.array([u, v, self.z_tracker.get_value()]),85 u_range=[-2.5, 2.5],86 v_range=[-2.5, 2.5],87 resolution=(2, 2),88 fill_opacity=0.2,89 stroke_width=0.5,90 stroke_color=YELLOW,91 fill_color=YELLOW92 )93 )94 95 # 动态截面 - 使用填充和更突出的边缘96 cross_section = always_redraw(97 lambda: ParametricFunction(98 lambda t: np.array([99 np.sqrt(self.z_tracker.get_value()) * np.cos(t),100 np.sqrt(self.z_tracker.get_value()) * np.sin(t),101 self.z_tracker.get_value()102 ]),103 t_range=[0, TAU],104 color=RED_B,105 stroke_width=4106 )107 )108 109 # 在截面内绘制积分区域 - 使用更美观的填充110 integral_region = always_redraw(111 lambda: Surface(112 lambda u, v: np.array([113 u * np.cos(v),114 u * np.sin(v),115 self.z_tracker.get_value()116 ]),117 u_range=[0, np.sqrt(self.z_tracker.get_value())],118 v_range=[0, TAU],119 fill_opacity=0.4,120 resolution=(20, 20),121 stroke_width=0,122 checkerboard_colors=[RED_D, RED_A]123 )124 )125 126 # 创建左下角的2D截面变化示意图 - 坐标轴更长127 section_frame = Rectangle(height=3.5, width=3.5, color=WHITE)128 section_frame.to_corner(DL, buff=0.2)129 section_axes = Axes(130 x_range=[-2.0, 2.0, 1],131 y_range=[-2.0, 2.0, 1],132 x_length=3.0, # 增加坐标轴长度133 y_length=3.0, # 增加坐标轴长度134 axis_config={"color": GREY_B, "include_ticks": True}, # 包含刻度135 ).move_to(section_frame.get_center())136 137 # 添加坐标轴标签138 section_x_label = MathTex("x", font_size=16, color=GREY_B).next_to(section_axes.get_x_axis(), RIGHT)139 section_y_label = MathTex("y", font_size=16, color=GREY_B).next_to(section_axes.get_y_axis(), UP)140 141 # 调整标题位置,确保有足够空间放置截面方程142 section_title = Text("截面示意图", font_size=20, color=YELLOW)143 section_title.next_to(section_frame, UP, buff=0.1)144 section_title.align_to(section_frame, LEFT)145 146 # 截面方程(截面方程)放在标题上方,确保在框内147 section_text = Text("截面方程:z = x² + y² = ", font_size=24, color=YELLOW)148 149 # 将z值单独设置为动态更新的元素150 z_value_label = always_redraw(151 lambda: Text(152 f"{round(self.z_tracker.get_value(), 1)}",153 font_size=24,154 color=YELLOW155 )156 )157 158 # 将文字和动态z值组合在一起159 cross_eq = VGroup(section_text, z_value_label).arrange(RIGHT, buff=0.05)160 cross_eq.next_to(section_title, UP, buff=0.1)161 cross_eq.align_to(section_frame, LEFT)162 163 # 固定z值标签在文字后面164 z_value_label.add_updater(lambda m: m.next_to(section_text, RIGHT, buff=0.05))165 166 # 在小图右侧添加截面范围D(z)的显示167 range_text_title = Text("截面范围D(z):", font_size=20, color=YELLOW)168 range_text_theta = MathTex(r"0 \leq \theta \leq 2\pi", font_size=20, color=WHITE)169 range_text_r = MathTex(r"0 \leq r \leq \sqrt{z}", font_size=20, color=WHITE)170 171 # 将范围说明组合并垂直排列172 range_group = VGroup(range_text_title, range_text_theta, range_text_r).arrange(DOWN, buff=0.1, aligned_edge=LEFT)173 range_group.next_to(section_frame, RIGHT, buff=0.3)174 range_group.align_to(section_frame, UP)175 176 # 创建动态更新的2D截面圆177 section_circle = always_redraw(178 lambda: Circle(179 radius=np.sqrt(self.z_tracker.get_value()) * 0.75, # 缩放以适应框架,但比例更大180 color=RED,181 fill_opacity=0.3,182 fill_color=RED_A183 ).move_to(section_axes.get_center())184 )185 186 # 提示文字 - 放在右侧上方位置187 explanation = Text("先二后一计算法", font_size=28, color=YELLOW).to_edge(RIGHT, buff=0.5).shift(UP * 2)188 189 # 截面变化提示 - 放在右侧说明文字下方190 section_desc = Text("截面随z值变化而变化", font_size=24, color=WHITE).next_to(explanation, DOWN, buff=0.3)191 192 # 积分公式 - 放在"先二后一计算法"下方,但先不显示193 integral_steps = VGroup(194 MathTex(195 r"\begin{aligned}"196 r"\iiint_{\Omega} (x^2 + y^2) \, dV &= \int_{0}^{4} \left( \iint_{D(z)} (x^2 + y^2) \, dxdy \right) dz \\"197 r"&= \int_{0}^{4} \left( \int_{0}^{2\pi} \int_{0}^{\sqrt{z}} r^2 \cdot r \, drd\theta \right) dz \\"198 r"&= \int_{0}^{4} \left( 2\pi \cdot \frac{r^4}{4} \Big|_{0}^{\sqrt{z}} \right) dz \\"199 r"&= \int_{0}^{4} \frac{\pi}{2} \cdot z^2 \, dz \\"200 r"&= \frac{\pi}{2} \cdot \frac{z^3}{3} \Big|_{0}^{4} \\"201 r"&= \frac{\pi}{2} \cdot \frac{64}{3} = \frac{32\pi}{3}"202 r"\end{aligned}",203 font_size=24,204 color=WHITE205 )206 ).next_to(section_desc, DOWN, buff=0.3).to_edge(RIGHT, buff=0.5)207 208 # 最终结果 - 放在中央底部,先不显示209 final_result = MathTex(210 r"\iiint_{\Omega} (x^2 + y^2) \, dV = \frac{32\pi}{3}",211 font_size=36,212 color=YELLOW213 ).to_edge(DOWN, buff=0.3)214 215 # 按照逻辑顺序播放动画216 # 1. 方法介绍217 self.add_fixed_in_frame_mobjects(explanation)218 self.play(Write(explanation), run_time=1)219 self.wait(0.2)220 221 # 2. 截面平面和截面描述222 self.add_fixed_in_frame_mobjects(section_desc)223 self.play(224 Create(section_plane),225 Write(section_desc),226 run_time=1.5227 )228 self.wait(0.2)229 230 # 3. 截面圆和积分区域231 self.play(232 Create(cross_section),233 Create(integral_region),234 run_time=1.5235 )236 self.wait(0.2)237 238 # 4. 2D截面示意图和相关说明 - 修复闪现问题239 # 先只添加框架,不添加动态更新的元素240 self.add_fixed_in_frame_mobjects(section_frame)241 self.play(FadeIn(section_frame), run_time=0.5)242 243 # 添加坐标轴和标签244 self.add_fixed_in_frame_mobjects(section_axes, section_x_label, section_y_label)245 self.play(246 FadeIn(section_axes),247 FadeIn(section_x_label),248 FadeIn(section_y_label),249 run_time=0.5250 )251 252 # 添加标题253 self.add_fixed_in_frame_mobjects(section_title)254 self.play(FadeIn(section_title), run_time=0.5)255 256 # 添加截面方程257 self.add_fixed_in_frame_mobjects(cross_eq)258 self.play(FadeIn(cross_eq), run_time=0.5)259 260 # 添加范围说明261 self.add_fixed_in_frame_mobjects(range_group)262 self.play(FadeIn(range_group), run_time=0.5)263 264 # 最后添加动态更新的圆 - 避免闪现265 self.add_fixed_in_frame_mobjects(section_circle)266 self.play(FadeIn(section_circle), run_time=0.5)267 self.wait(0.3)268 269 # 5. 动态变化z值,显示截面变化270 self.play(271 self.z_tracker.animate.set_value(4),272 run_time=5273 )274 self.wait(0.5)275 276 # 6. 旋转相机角度,准备显示积分计算277 self.move_camera(phi=70 * DEGREES, theta=80 * DEGREES)278 self.wait(0.3)279 280 # 7. 积分计算步骤281 self.add_fixed_in_frame_mobjects(integral_steps)282 self.play(Write(integral_steps), run_time=2.5)283 self.wait(1)284 285 # 8. 旋转展示最终结果286 self.begin_ambient_camera_rotation(rate=0.15)287 self.wait(4)288 self.stop_ambient_camera_rotation()289 290 # 9. 显示最终结果291 self.add_fixed_in_frame_mobjects(final_result)292 self.play(293 FadeOut(integral_steps),294 FadeIn(final_result),295 run_time=1.5296 )297 self.wait(2)298 299if __name__ == "__main__":300 # 使用更好的渲染设置301 config.frame_rate = 30302 config.pixel_width = 1280303 config.pixel_height = 720304 scene = TripleIntegralScene()305 scene.render() 讲解
这个视频围绕「三重积分计算」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。
开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。
画面中出现的文字「三重积分计算(先二后一)」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。
随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。
核心公式可以写成:
这类公式可以和画面中的符号一一对应。
观察路径
观察路径可以分三步:先锁定「三重积分计算」中的核心对象,尤其留意三重积分、累次积分、空间区域之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。
本页可以从三重积分、累次积分、空间区域、积分求体积这些概念进入,继续沿相邻问题观察同一类数学结构。