189 lines
5.5 KiB
Python
189 lines
5.5 KiB
Python
|
from manim import *
|
|||
|
|
|||
|
|
|||
|
class ParabolaLesson(Scene):
|
|||
|
# 坐标常量定义
|
|||
|
LEFT_MARGIN = 3.2
|
|||
|
AXIS_RANGE = [-4, 4, -1, 3]
|
|||
|
|
|||
|
# 颜色定义
|
|||
|
BG_COLOR = "#F5F5F5"
|
|||
|
CURVE_COLOR = "#00BFFF"
|
|||
|
FOCUS_COLOR = "#FF4500"
|
|||
|
DIRECTRIX_COLOR = "#AAAAAA"
|
|||
|
|
|||
|
def construct(self):
|
|||
|
self.camera.background_color = self.BG_COLOR
|
|||
|
self._intro()
|
|||
|
self._discovery()
|
|||
|
self._proof()
|
|||
|
self._practice()
|
|||
|
|
|||
|
def _intro(self):
|
|||
|
# 创建坐标系
|
|||
|
axes = Axes(
|
|||
|
x_range=[-4, 4, 1],
|
|||
|
y_range=[-1, 3, 1],
|
|||
|
axis_config={"color": BLACK},
|
|||
|
tips=False
|
|||
|
)
|
|||
|
axes_labels = axes.get_axis_labels(x_label="x", y_label="y")
|
|||
|
|
|||
|
# 初始抛物线 (a=1)
|
|||
|
a_value = ValueTracker(1)
|
|||
|
parabola = always_redraw(lambda: axes.plot(
|
|||
|
lambda x: a_value.get_value() * x ** 2,
|
|||
|
color=self.CURVE_COLOR
|
|||
|
))
|
|||
|
|
|||
|
# 添加所有元素
|
|||
|
self.add(axes, axes_labels, parabola)
|
|||
|
|
|||
|
def _discovery(self):
|
|||
|
# 标题
|
|||
|
title = Tex("$a$是正数,开口向上", color=BLUE).to_edge(UP)
|
|||
|
|
|||
|
# 公式
|
|||
|
formula = MathTex("y = a x^2", color=BLACK).next_to(title, DOWN)
|
|||
|
|
|||
|
# 滑杆
|
|||
|
slider = NumberLine(
|
|||
|
x_range=[0, 2, 0.5],
|
|||
|
length=4,
|
|||
|
color=BLACK,
|
|||
|
include_numbers=True
|
|||
|
).to_edge(DOWN)
|
|||
|
slider_label = Tex("$a$值:", color=BLACK).next_to(slider, LEFT)
|
|||
|
|
|||
|
# 滑杆指示器
|
|||
|
dot = Dot(color=RED).move_to(slider.n2p(1))
|
|||
|
a_value = ValueTracker(1)
|
|||
|
dot.add_updater(lambda d: d.move_to(slider.n2p(
|
|||
|
a_value.get_value()
|
|||
|
)))
|
|||
|
a_text = always_redraw(lambda: DecimalNumber(
|
|||
|
a_value.get_value(),
|
|||
|
color=RED
|
|||
|
).next_to(dot, UP))
|
|||
|
|
|||
|
# 添加元素
|
|||
|
self.play(
|
|||
|
Write(title),
|
|||
|
Write(formula),
|
|||
|
Create(slider),
|
|||
|
Write(slider_label)
|
|||
|
)
|
|||
|
self.add(dot, a_text)
|
|||
|
|
|||
|
# 动画:a值变化 (0.5 -> 1 -> 2)
|
|||
|
self.play(
|
|||
|
a_value.animate.set_value(0.5),
|
|||
|
run_time=5,
|
|||
|
rate_func=rate_functions.ease_in_out_sine
|
|||
|
)
|
|||
|
self.play(
|
|||
|
a_value.animate.set_value(1),
|
|||
|
run_time=5,
|
|||
|
rate_func=rate_functions.ease_in_out_sine
|
|||
|
)
|
|||
|
self.play(
|
|||
|
a_value.animate.set_value(2),
|
|||
|
run_time=5,
|
|||
|
rate_func=rate_functions.ease_in_out_sine
|
|||
|
)
|
|||
|
|
|||
|
# 5秒静止帧
|
|||
|
self.wait(5)
|
|||
|
|
|||
|
def _proof(self):
|
|||
|
# 更新标题
|
|||
|
new_title = Tex("$a$是负数,开口向下", color=BLUE).to_edge(UP)
|
|||
|
|
|||
|
# 滑杆范围变为负数
|
|||
|
slider = NumberLine(
|
|||
|
x_range=[-2, 0, 0.5],
|
|||
|
length=4,
|
|||
|
color=BLACK,
|
|||
|
include_numbers=True
|
|||
|
).to_edge(DOWN)
|
|||
|
slider_label = Tex("$a$值:", color=BLACK).next_to(slider, LEFT)
|
|||
|
|
|||
|
# 更新滑杆指示器
|
|||
|
a_value = ValueTracker(-1)
|
|||
|
dot = Dot(color=RED).move_to(slider.n2p(-1))
|
|||
|
dot.add_updater(lambda d: d.move_to(slider.n2p(
|
|||
|
a_value.get_value()
|
|||
|
)))
|
|||
|
a_text = always_redraw(lambda: DecimalNumber(
|
|||
|
a_value.get_value(),
|
|||
|
color=RED
|
|||
|
).next_to(dot, UP))
|
|||
|
|
|||
|
# 更新公式
|
|||
|
formula = MathTex("y = a x^2", color=BLACK).next_to(new_title, DOWN)
|
|||
|
|
|||
|
# 焦点和准线
|
|||
|
focus = always_redraw(lambda: Dot(
|
|||
|
color=self.FOCUS_COLOR
|
|||
|
).move_to([0, 1 / (4 * a_value.get_value()), 0]))
|
|||
|
|
|||
|
directrix = always_redraw(lambda: DashedLine(
|
|||
|
start=[-4, -1 / (4 * a_value.get_value()), 0],
|
|||
|
end=[4, -1 / (4 * a_value.get_value()), 0],
|
|||
|
color=self.DIRECTRIX_COLOR
|
|||
|
))
|
|||
|
|
|||
|
# 切换元素
|
|||
|
self.play(
|
|||
|
Transform(self.mobjects[0], new_title),
|
|||
|
Transform(self.mobjects[1], formula),
|
|||
|
Transform(self.mobjects[2], slider),
|
|||
|
Transform(self.mobjects[3], slider_label)
|
|||
|
)
|
|||
|
self.add(focus, directrix)
|
|||
|
|
|||
|
# 动画:a值变化 (-0.5 -> -1 -> -2)
|
|||
|
self.play(
|
|||
|
a_value.animate.set_value(-0.5),
|
|||
|
run_time=5,
|
|||
|
rate_func=rate_functions.ease_in_out_sine
|
|||
|
)
|
|||
|
self.play(
|
|||
|
a_value.animate.set_value(-1),
|
|||
|
run_time=5,
|
|||
|
rate_func=rate_functions.ease_in_out_sine
|
|||
|
)
|
|||
|
self.play(
|
|||
|
a_value.animate.set_value(-2),
|
|||
|
run_time=5,
|
|||
|
rate_func=rate_functions.ease_in_out_sine
|
|||
|
)
|
|||
|
|
|||
|
# 5秒静止帧
|
|||
|
self.wait(5)
|
|||
|
|
|||
|
def _practice(self):
|
|||
|
# 添加练习题文本
|
|||
|
practice = Tex(
|
|||
|
"思考题:当$a=-1$时,\\\\焦点和准线在哪里?",
|
|||
|
color=BLUE
|
|||
|
).scale(0.8).to_edge(DOWN)
|
|||
|
|
|||
|
# 显示练习题
|
|||
|
self.play(Write(practice))
|
|||
|
self.wait(5)
|
|||
|
|
|||
|
|
|||
|
if __name__ == "__main__":
|
|||
|
# 全局指定 ctex 模板(一次性)
|
|||
|
config.tex_template = TexTemplateLibrary.ctex
|
|||
|
# 使用临时配置渲染场景(配置只在with块内有效)
|
|||
|
with tempconfig({
|
|||
|
"quality": "low_quality",
|
|||
|
"preview": True,
|
|||
|
"output_file": "ParabolaLesson"
|
|||
|
}):
|
|||
|
# 实例化场景类
|
|||
|
scene = ParabolaLesson()
|
|||
|
# 执行渲染流程(包含文件生成和预览)
|
|||
|
scene.render()
|