circle_inscription_limit.py
1from manim import *2import numpy as np3 4# 配置中文字体支持5config.tex_template = TexTemplateLibrary.ctex6config.tex_template.add_to_preamble(r"\setCJKmainfont{STSong}")7 8class CircleInscriptionLimit(Scene):9 def construct(self):10 # 标题11 title = Text("割圆术与数列极限", font="STSong", font_size=40, color=BLUE)12 title.to_edge(UP, buff=0.5)13 self.play(Write(title), run_time=1.5)14 self.wait(1)15 16 # 说明文字17 explanation = Text(18 "通过不断增加正多边形的边数,观察周长数列如何收敛到圆的周长",19 font="STSong", font_size=24, color=WHITE20 ).next_to(title, DOWN, buff=0.3)21 self.play(FadeIn(explanation), run_time=1.5)22 self.wait(1)23 24 # 创建圆(固定半径,但显示大小不变)25 radius = 1 # 半径改为126 display_radius = 2 # 显示半径保持2,图形大小不变27 circle = Circle(radius=display_radius, color=BLUE, stroke_width=3)28 circle.shift(LEFT * 4) # 更靠左一些,为右侧文字留出空间29 30 # 圆心31 center = circle.get_center()32 center_dot = Dot(center, color=YELLOW, radius=0.08)33 center_label = MathTex("O", font_size=24, color=YELLOW).next_to(center_dot, DOWN, buff=0.15)34 35 # 显示半径标注(从圆心到圆上一点,标注r=1)36 radius_point = circle.point_at_angle(0) # 右侧的点37 radius_line = Line(center, radius_point, color=GREEN, stroke_width=2)38 radius_label = MathTex("r=1", font_size=20, color=GREEN)39 radius_label.next_to(radius_line, UP, buff=0.1).shift(RIGHT * 0.3)40 41 # 显示圆42 self.play(Create(circle), run_time=1.5)43 self.play(Create(center_dot), Write(center_label), run_time=1)44 self.play(Create(radius_line), Write(radius_label), run_time=1)45 self.wait(0.5)46 47 # 计算圆的中心位置和右侧目标位置(与圆对称)48 circle_center = circle.get_center()49 circle_right_edge = circle_center[0] + display_radius50 51 # 统一的字体大小和行间距52 font_size = 2253 line_spacing = 0.3554 55 # 目标位置:圆的右侧,垂直方向与圆中心对齐(对称),整体右移1个单位56 target_x = circle_right_edge + 0.8 + 1.0 # 整体右移1个单位57 target_y_start = circle_center[1] - 1 # 向下平移一个单位(标量值)58 59 # 先显示实际值60 actual_value_text = Text("实际值:", font="STSong", font_size=font_size, color=GREEN)61 actual_value = MathTex(62 r"2\pi \cdot 1 = 2\pi \approx 6.283",63 font_size=font_size, color=GREEN64 )65 actual_value_group = VGroup(actual_value_text, actual_value).arrange(RIGHT, buff=0.15)66 actual_value_group.move_to([target_x, target_y_start + 2.5, 0])67 68 # 显示极限公式(n趋近于无穷显示在右边)69 limit_title = Text("极限:", font="STSong", font_size=font_size, color=YELLOW)70 limit_formula = MathTex(71 r"\lim_{n \to \infty} P_n = 2\pi r",72 font_size=font_size, color=YELLOW73 )74 limit_group = VGroup(limit_title, limit_formula).arrange(RIGHT, buff=0.2)75 limit_group.next_to(actual_value_group, DOWN, buff=line_spacing).align_to(actual_value_group, LEFT)76 77 # 数列标题78 sequence_title = Text("周长数列:", font="STSong", font_size=font_size, color=GREEN)79 sequence_title.next_to(limit_group, DOWN, buff=line_spacing).align_to(actual_value_group, LEFT)80 81 # 显示文字(按顺序:实际值 -> 极限 -> 周长数列)82 self.play(Write(actual_value_group), run_time=1)83 self.wait(0.3)84 self.play(Write(limit_group), run_time=1.2)85 self.wait(0.3)86 self.play(Write(sequence_title), run_time=1)87 self.wait(0.5)88 89 # 存储多边形和标签90 current_polygon = None91 current_label = None92 sequence_values = []93 94 # 从正四边形开始,每次边数乘以2,计算到64边形95 n_values = [4, 8, 16, 32, 64]96 97 # 存储数列显示项(保留所有,不删除)98 sequence_items = []99 error_texts = []100 101 # 列管理:每列最多显示6项,然后换列102 items_per_column = 6103 current_column = 0104 items_in_current_column = 0105 column_start_x = target_x # 第一列的x坐标106 column_spacing = 1.8 # 列之间的间距107 108 # 动画:逐步增加边数109 for idx, n in enumerate(n_values):110 # 计算正n边形的顶点(使用显示半径)111 vertices = []112 for i in range(n):113 angle = 2 * PI * i / n - PI / 2 # 从顶部开始114 x = center[0] + display_radius * np.cos(angle)115 y = center[1] + display_radius * np.sin(angle)116 vertices.append([x, y, 0])117 118 # 创建正多边形119 polygon = Polygon(*vertices, color=RED, stroke_width=2.5)120 121 # 计算周长(使用实际半径值1)122 # 正n边形内接于半径为r的圆,边长 = 2r * sin(π/n)123 side_length = 2 * radius * np.sin(PI / n)124 perimeter = n * side_length125 sequence_values.append(perimeter)126 127 # 更新多边形(替换旧的)128 if current_polygon is not None:129 self.play(Transform(current_polygon, polygon), run_time=0.8)130 else:131 current_polygon = polygon132 self.add(polygon)133 self.play(Create(polygon), run_time=1)134 135 # 更新标签136 new_label = Text(137 f"正{n}边形",138 font="STSong", font_size=20, color=ORANGE139 ).next_to(circle, DOWN, buff=0.3)140 141 if current_label is not None:142 self.play(Transform(current_label, new_label), run_time=0.5)143 else:144 current_label = new_label145 self.add(new_label)146 self.play(Write(new_label), run_time=0.5)147 148 # 检查是否需要换列(P32开始换到右侧列)149 # P4, P8, P16在第一列,P32, P64在第二列150 if n >= 32:151 if current_column == 0:152 current_column = 1153 items_in_current_column = 0154 elif items_in_current_column >= items_per_column:155 current_column += 1156 items_in_current_column = 0157 158 # 计算当前列的x坐标159 if current_column == 1:160 # 第二列:找到第一列最右侧的位置(P16的位置),然后在其右侧显示P32161 # 此时sequence_items中应该有P4, P8, P16(都是第一列的)162 if len(sequence_items) >= 3:163 # 找到所有第一列项的右边界(包括公式和误差文本)164 max_right = 0165 for item in sequence_items:166 # item是VGroup,包含formula_with_result和error_text167 # 需要找到整个组的右边界168 item_right = item.get_right()[0]169 if item_right > max_right:170 max_right = item_right171 172 # P32显示在P16右侧,留出适当间距(1.2个单位),再右移1个单位173 current_column_x = max_right + 1.2 + 1.0174 else:175 # 如果还没有足够的项,使用默认位置176 current_column_x = column_start_x + column_spacing * 2177 else:178 current_column_x = column_start_x + current_column * column_spacing179 180 # 计算目标位置(从数列标题下方开始,使用统一行间距)181 if len(sequence_items) == 0:182 # 第一项:从数列标题下方开始183 target_pos = sequence_title.get_bottom() + DOWN * line_spacing184 target_pos = np.array([current_column_x, target_pos[1], 0])185 else:186 if items_in_current_column == 0:187 # 新列的第一项:从数列标题下方开始188 target_pos = sequence_title.get_bottom() + DOWN * line_spacing189 # 如果是第二列,整体上移两个单位190 if current_column == 1:191 target_pos = np.array([current_column_x, target_pos[1] + 2.0, 0])192 else:193 target_pos = np.array([current_column_x, target_pos[1], 0])194 else:195 # 同一列的后续项:放在上一项下方(使用统一行间距)196 prev_item = sequence_items[-1]197 target_pos = prev_item.get_bottom() + DOWN * line_spacing198 target_pos = np.array([prev_item.get_center()[0], target_pos[1], 0])199 200 # 显示计算公式和结果(在同一行,用≈连接,使用统一字体大小)201 formula_with_result = MathTex(202 f"P_{{{n}}} = {n} \\times 2r \\times \\sin\\left(\\frac{{\\pi}}{{{n}}}\\right) \\approx {perimeter:.4f}",203 font_size=font_size, color=YELLOW204 )205 # 移动到目标位置206 formula_with_result.move_to(target_pos)207 # 只有第一列才进行左对齐,第二列使用绝对位置208 if current_column == 0:209 formula_with_result.align_to(sequence_title, LEFT)210 211 # 显示公式和结果212 self.play(Write(formula_with_result), run_time=1.5)213 self.wait(0.3)214 215 # 计算误差216 true_perimeter = 2 * PI * radius217 error = abs(perimeter - true_perimeter)218 219 # 显示误差(用绝对值≈误差的形式,使用统一字体大小和行间距)220 error_text = MathTex(221 f"|P_{{{n}}} - 2\\pi r| \\approx {error:.4f}",222 font_size=font_size, color=GRAY223 )224 error_text.next_to(formula_with_result, DOWN, buff=line_spacing)225 # 误差文本与公式左对齐226 error_text.align_to(formula_with_result, LEFT)227 error_texts.append(error_text)228 self.play(Write(error_text), run_time=0.8)229 230 # 将公式和误差组合在一起231 complete_group = VGroup(formula_with_result, error_text)232 sequence_items.append(complete_group)233 items_in_current_column += 1234 235 # 短暂等待236 self.wait(0.3)237 238 # 显示收敛过程总结239 self.wait(1)240 241 # 创建收敛性说明 - 接在第二列下面显示(n趋近于无穷)242 convergence_text = VGroup(243 Text("当 ", font="STSong", font_size=font_size, color=YELLOW),244 MathTex(r"n \to \infty", font_size=font_size, color=YELLOW),245 Text(" 时,", font="STSong", font_size=font_size, color=YELLOW),246 MathTex(r"P_n = n \times 2r \times \sin\left(\frac{\pi}{n}\right)", font_size=font_size, color=YELLOW),247 MathTex(r" \to 2\pi r", font_size=font_size, color=YELLOW)248 ).arrange(RIGHT, buff=0.1)249 250 # 找到最后一个数列项的位置,接在第二列下面251 if len(sequence_items) > 0:252 # 找到第二列(P32, P64)的位置253 second_column_items = []254 for item in sequence_items:255 item_x = item.get_center()[0]256 # 判断是否属于第二列(x坐标大于第一列)257 if item_x > column_start_x + column_spacing:258 second_column_items.append(item)259 260 if second_column_items:261 # 找到第二列最后一项的位置262 last_second_column_item = second_column_items[-1]263 convergence_text.next_to(last_second_column_item, DOWN, buff=line_spacing)264 convergence_text.align_to(last_second_column_item, LEFT)265 else:266 # 如果没有第二列,放在最后一项下面267 convergence_text.next_to(sequence_items[-1], DOWN, buff=line_spacing)268 convergence_text.align_to(sequence_items[-1], LEFT)269 else:270 convergence_text.next_to(sequence_title, DOWN, buff=line_spacing)271 convergence_text.align_to(sequence_title, LEFT)272 273 self.play(Write(convergence_text), run_time=1.5)274 self.wait(1)275 276 # 显示数列的极限定义 - 放在收敛说明下方277 limit_def_title = Text("数列极限的定义:", font="STSong", font_size=20, color=BLUE)278 limit_def_title.next_to(convergence_text, DOWN, buff=0.3).align_to(convergence_text, LEFT)279 280 limit_def = MathTex(281 r"\forall \varepsilon > 0, \exists N \in \mathbb{N},",282 font_size=18, color=WHITE283 )284 limit_def.next_to(limit_def_title, DOWN, buff=0.2).align_to(limit_def_title, LEFT)285 286 limit_def2 = MathTex(287 r"\text{当 } n > N \text{ 时}, |P_n - 2\pi r| < \varepsilon",288 font_size=18, color=WHITE289 )290 limit_def2.next_to(limit_def, DOWN, buff=0.1).align_to(limit_def, LEFT)291 292 self.play(Write(limit_def_title), run_time=1)293 self.play(Write(limit_def), run_time=1.5)294 self.play(Write(limit_def2), run_time=1.5)295 self.wait(2)296 297 # 高亮显示最后的几个值,展示收敛298 if len(sequence_items) > 0:299 highlight_animations = []300 for item in sequence_items[-3:]:301 highlight_animations.append(item.animate.set_color(YELLOW))302 303 self.play(*highlight_animations, run_time=1.5)304 self.wait(1)305 306 # 最终总结(放在极限定义下面显示,分两行,字体小一点)307 final_text_line1 = Text(308 "割圆术展示了通过不断倍增正多边形的边数,",309 font="STSong", font_size=font_size - 3, color=GREEN310 )311 final_text_line2 = Text(312 "以有限步骤刻画无限逼近的过程。",313 font="STSong", font_size=font_size - 3, color=GREEN314 )315 316 final_text = VGroup(final_text_line1, final_text_line2).arrange(DOWN, buff=0.2, aligned_edge=LEFT)317 318 # 放在极限定义下面,确保不出框319 final_text.next_to(limit_def2, DOWN, buff=line_spacing).align_to(limit_def_title, LEFT)320 321 # 检查是否出框,如果出框则调整位置322 if final_text.get_bottom()[1] < -config.frame_height / 2 + 0.5:323 # 如果出框,上移一些324 final_text.shift(UP * 0.3)325 326 self.play(Write(final_text_line1), run_time=1.0)327 self.play(Write(final_text_line2), run_time=1.0)328 self.wait(2)329 330 # 不淡出,保持所有内容显示 讲解
这个视频围绕「割圆术与数列极限」展开。画面把问题、图像和公式放在同一条理解路径中,让抽象关系先被看见。
开头先建立问题背景和主要对象,让观察从标题、坐标或第一组关系进入。
画面中出现的文字「割圆术与数列极限」给出视觉入口。随后画面通过对象创建、移动、淡入淡出和高亮,把抽象命题拆成可以跟随的步骤。
随后出现更具体的图像或公式提示,动画会把局部对象和整体结论联系起来。这里可以重点观察变量、曲线、区域或向量如何随镜头推进而变化。
核心公式可以写成:
这类公式可以和画面中的符号一一对应。
观察路径
观察路径可以分三步:先锁定「割圆术与数列极限」中的核心对象,尤其留意割圆术、数列极限、多边形逼近之间的联系;再跟随画面中变量、图像或向量的变化;最后回到公式或结论,判断局部变化如何支撑整体关系。
本页可以从割圆术、数列极限、多边形逼近、极限过程这些概念进入,继续沿相邻问题观察同一类数学结构。