mirror of
https://github.com/3b1b/manim.git
synced 2025-08-01 08:54:38 +08:00
First scene of eoc/chapter3
This commit is contained in:
227
eoc/chapter3.py
Normal file
227
eoc/chapter3.py
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
from helpers import *
|
||||||
|
|
||||||
|
from mobject.tex_mobject import TexMobject
|
||||||
|
from mobject import Mobject
|
||||||
|
from mobject.image_mobject import ImageMobject
|
||||||
|
from mobject.vectorized_mobject import *
|
||||||
|
|
||||||
|
from animation.animation import Animation
|
||||||
|
from animation.transform import *
|
||||||
|
from animation.simple_animations import *
|
||||||
|
from animation.playground import *
|
||||||
|
from topics.geometry import *
|
||||||
|
from topics.characters import *
|
||||||
|
from topics.functions import *
|
||||||
|
from topics.fractals import *
|
||||||
|
from topics.number_line import *
|
||||||
|
from topics.combinatorics import *
|
||||||
|
from topics.numerals import *
|
||||||
|
from topics.three_dimensions import *
|
||||||
|
from topics.objects import *
|
||||||
|
from scene import Scene
|
||||||
|
from scene.zoomed_scene import ZoomedScene
|
||||||
|
from camera import Camera
|
||||||
|
from mobject.svg_mobject import *
|
||||||
|
from mobject.tex_mobject import *
|
||||||
|
|
||||||
|
from eoc.chapter1 import OpeningQuote, PatreonThanks
|
||||||
|
from eoc.chapter2 import DISTANCE_COLOR, TIME_COLOR, VELOCITY_COLOR
|
||||||
|
from eoc.graph_scene import *
|
||||||
|
|
||||||
|
OUTPUT_COLOR = DISTANCE_COLOR
|
||||||
|
INPUT_COLOR = TIME_COLOR
|
||||||
|
DERIVATIVE_COLOR = VELOCITY_COLOR
|
||||||
|
|
||||||
|
class Chapter3OpeningQuote(OpeningQuote):
|
||||||
|
CONFIG = {
|
||||||
|
"quote" : [
|
||||||
|
"You know, for a mathematician, he did not have \\\\ enough",
|
||||||
|
"imagination.",
|
||||||
|
"But he has become a poet and \\\\ now he is fine.",
|
||||||
|
],
|
||||||
|
"highlighted_quote_terms" : {
|
||||||
|
"imagination." : BLUE,
|
||||||
|
},
|
||||||
|
"author" : "David Hilbert"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DerivativeOfXSquaredAsGraph(GraphScene, ZoomedScene, PiCreatureScene):
|
||||||
|
CONFIG = {
|
||||||
|
"start_x" : 2,
|
||||||
|
"big_x" : 3,
|
||||||
|
"dx" : 0.1,
|
||||||
|
"x_min" : -5,
|
||||||
|
"x_labeled_nums" : range(-4, 0, 2) + range(2, 10, 2),
|
||||||
|
"y_labeled_nums" : range(2, 12, 2),
|
||||||
|
"little_rect_nudge" : 0.5*(2*UP+RIGHT),
|
||||||
|
"graph_origin" : 2.5*DOWN + 2*LEFT,
|
||||||
|
"zoomed_canvas_corner" : UP+LEFT,
|
||||||
|
}
|
||||||
|
def construct(self):
|
||||||
|
self.draw_graph()
|
||||||
|
self.ask_about_df_dx()
|
||||||
|
self.show_differing_slopes()
|
||||||
|
self.mention_alternate_view()
|
||||||
|
|
||||||
|
def draw_graph(self):
|
||||||
|
self.setup_axes()
|
||||||
|
graph = self.get_graph(lambda x : x**2)
|
||||||
|
label = self.get_graph_label(
|
||||||
|
graph, "f(x) = x^2",
|
||||||
|
)
|
||||||
|
self.play(ShowCreation(graph))
|
||||||
|
self.play(Write(label))
|
||||||
|
self.dither()
|
||||||
|
self.graph = graph
|
||||||
|
|
||||||
|
def ask_about_df_dx(self):
|
||||||
|
ss_group = self.get_secant_slope_group(
|
||||||
|
self.start_x, self.graph,
|
||||||
|
dx = self.dx,
|
||||||
|
dx_label = "dx",
|
||||||
|
df_label = "df",
|
||||||
|
)
|
||||||
|
secant_line = ss_group.secant_line
|
||||||
|
ss_group.remove(secant_line)
|
||||||
|
|
||||||
|
v_line, nudged_v_line = [
|
||||||
|
self.get_vertical_line_to_graph(
|
||||||
|
x, self.graph,
|
||||||
|
line_class = DashedLine,
|
||||||
|
color = RED,
|
||||||
|
dashed_segment_length = 0.025
|
||||||
|
)
|
||||||
|
for x in self.start_x, self.start_x+self.dx
|
||||||
|
]
|
||||||
|
|
||||||
|
df_dx = TexMobject("\\frac{df}{dx} ?")
|
||||||
|
VGroup(*df_dx[:2]).highlight(ss_group.df_line.get_color())
|
||||||
|
VGroup(*df_dx[3:5]).highlight(ss_group.dx_line.get_color())
|
||||||
|
df_dx.next_to(
|
||||||
|
self.input_to_graph_point(self.start_x, self.graph),
|
||||||
|
DOWN+RIGHT,
|
||||||
|
buff = MED_BUFF
|
||||||
|
)
|
||||||
|
|
||||||
|
self.play(ShowCreation(v_line))
|
||||||
|
self.dither()
|
||||||
|
self.play(Transform(v_line.copy(), nudged_v_line))
|
||||||
|
self.remove(self.get_mobjects_from_last_animation()[0])
|
||||||
|
self.add(nudged_v_line)
|
||||||
|
self.dither()
|
||||||
|
self.activate_zooming()
|
||||||
|
self.play(
|
||||||
|
FadeIn(self.little_rectangle),
|
||||||
|
FadeIn(self.big_rectangle),
|
||||||
|
)
|
||||||
|
self.play(
|
||||||
|
self.little_rectangle.scale_to_fit_width,
|
||||||
|
4*self.dx,
|
||||||
|
self.little_rectangle.move_to,
|
||||||
|
self.input_to_graph_point(self.start_x, self.graph),
|
||||||
|
self.little_rectangle.shift,
|
||||||
|
self.dx*self.little_rect_nudge,
|
||||||
|
self.pi_creature.change_mode, "pondering",
|
||||||
|
self.pi_creature.look_at, ss_group
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
self.play(
|
||||||
|
ShowCreation(ss_group.dx_line),
|
||||||
|
Write(ss_group.dx_label),
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
self.play(
|
||||||
|
ShowCreation(ss_group.df_line),
|
||||||
|
Write(ss_group.df_label),
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
self.play(Write(df_dx))
|
||||||
|
self.dither()
|
||||||
|
self.play(*map(FadeOut, [
|
||||||
|
v_line, nudged_v_line,
|
||||||
|
]))
|
||||||
|
self.ss_group = ss_group
|
||||||
|
|
||||||
|
def show_differing_slopes(self):
|
||||||
|
ss_group = self.ss_group
|
||||||
|
def rect_update(rect):
|
||||||
|
rect.move_to(ss_group.dx_line.get_left())
|
||||||
|
rect.shift(self.dx*self.little_rect_nudge)
|
||||||
|
return rect
|
||||||
|
|
||||||
|
|
||||||
|
self.play(
|
||||||
|
ShowCreation(ss_group.secant_line),
|
||||||
|
self.pi_creature.change_mode, "thinking"
|
||||||
|
)
|
||||||
|
ss_group.add(ss_group.secant_line)
|
||||||
|
self.dither()
|
||||||
|
for target_x in self.big_x, -self.dx/2, 1, 2:
|
||||||
|
self.animate_secant_slope_group_change(
|
||||||
|
ss_group, target_x = target_x,
|
||||||
|
added_anims = [
|
||||||
|
UpdateFromFunc(self.little_rectangle, rect_update)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.dither()
|
||||||
|
|
||||||
|
def mention_alternate_view(self):
|
||||||
|
# self.remove(self.pi_creature)
|
||||||
|
# everything = VGroup(*self.get_mobjects())
|
||||||
|
# self.add(self.pi_creature)
|
||||||
|
# self.disactivate_zooming()
|
||||||
|
# self.play(FadeOut(everything))
|
||||||
|
|
||||||
|
self.say("Let's try \\\\ another view.", target_mode = "speaking")
|
||||||
|
self.dither(2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -137,12 +137,12 @@ class GraphScene(Scene):
|
|||||||
color = color or graph.get_color()
|
color = color or graph.get_color()
|
||||||
label.highlight(color)
|
label.highlight(color)
|
||||||
if x_val is None:
|
if x_val is None:
|
||||||
x_range = np.linspace(self.x_min, self.x_max, 20)
|
#Search from right to left
|
||||||
for left_x, right_x in zip(x_range, x_range[1:]):
|
for x in np.linspace(self.x_max, self.x_min, 100):
|
||||||
right_point = self.input_to_graph_point(right_x, graph)
|
point = self.input_to_graph_point(x, graph)
|
||||||
if right_point[1] > SPACE_HEIGHT:
|
if point[1] < SPACE_HEIGHT:
|
||||||
break
|
break
|
||||||
x_val = left_x
|
x_val = x
|
||||||
label.next_to(
|
label.next_to(
|
||||||
self.input_to_graph_point(x_val, graph),
|
self.input_to_graph_point(x_val, graph),
|
||||||
direction,
|
direction,
|
||||||
@ -180,10 +180,8 @@ class GraphScene(Scene):
|
|||||||
self,
|
self,
|
||||||
x, graph,
|
x, graph,
|
||||||
line_class = Line,
|
line_class = Line,
|
||||||
line_kwargs = None,
|
**line_kwargs
|
||||||
):
|
):
|
||||||
if line_kwargs is None:
|
|
||||||
line_kwargs = {}
|
|
||||||
if "color" not in line_kwargs:
|
if "color" not in line_kwargs:
|
||||||
line_kwargs["color"] = graph.get_color()
|
line_kwargs["color"] = graph.get_color()
|
||||||
return line_class(
|
return line_class(
|
||||||
@ -226,18 +224,33 @@ class GraphScene(Scene):
|
|||||||
interim_point, p2,
|
interim_point, p2,
|
||||||
color = df_line_color
|
color = df_line_color
|
||||||
)
|
)
|
||||||
|
|
||||||
|
labels = VGroup()
|
||||||
if dx_label is not None:
|
if dx_label is not None:
|
||||||
group.dx_label = TexMobject(dx_label)
|
group.dx_label = TexMobject(dx_label)
|
||||||
if group.dx_label.get_width() > group.dx_line.get_width():
|
labels.add(group.dx_label)
|
||||||
group.dx_label.scale_to_fit_width(group.dx_line.get_width())
|
if df_label is not None:
|
||||||
group.dx_label.next_to(group.dx_line, DOWN, SMALL_BUFF)
|
group.df_label = TexMobject(df_label)
|
||||||
|
labels.add(group.df_label)
|
||||||
|
|
||||||
|
if len(labels) > 0:
|
||||||
|
max_width = 0.8*group.dx_line.get_width()
|
||||||
|
max_height = 0.8*group.df_line.get_height()
|
||||||
|
if labels.get_width() > max_width:
|
||||||
|
labels.scale_to_fit_width(max_width)
|
||||||
|
if labels.get_height() > max_height:
|
||||||
|
labels.scale_to_fit_height(max_height)
|
||||||
|
|
||||||
|
if dx_label is not None:
|
||||||
|
group.dx_label.next_to(
|
||||||
|
group.dx_line, DOWN, buff = group.dx_label.get_height()/2
|
||||||
|
)
|
||||||
group.dx_label.highlight(group.dx_line.get_color())
|
group.dx_label.highlight(group.dx_line.get_color())
|
||||||
|
|
||||||
if df_label is not None:
|
if df_label is not None:
|
||||||
group.df_label = TexMobject(df_label)
|
group.df_label.next_to(
|
||||||
if group.df_label.get_height() > group.df_line.get_height():
|
group.df_line, RIGHT, buff = group.df_label.get_height()/2
|
||||||
group.df_label.scale_to_fit_height(group.df_line.get_height())
|
)
|
||||||
group.df_label.next_to(group.df_line, RIGHT, SMALL_BUFF)
|
|
||||||
group.df_label.highlight(group.df_line.get_color())
|
group.df_label.highlight(group.df_line.get_color())
|
||||||
|
|
||||||
if include_secant_line:
|
if include_secant_line:
|
||||||
@ -250,25 +263,45 @@ class GraphScene(Scene):
|
|||||||
group.digest_mobject_attrs()
|
group.digest_mobject_attrs()
|
||||||
return group
|
return group
|
||||||
|
|
||||||
def animate_secant_slope_group_dx_change(
|
def animate_secant_slope_group_change(
|
||||||
self, secant_slope_group, target_dx,
|
self, secant_slope_group,
|
||||||
|
target_dx = None,
|
||||||
|
target_x = None,
|
||||||
run_time = 3,
|
run_time = 3,
|
||||||
|
added_anims = None,
|
||||||
**anim_kwargs
|
**anim_kwargs
|
||||||
):
|
):
|
||||||
|
if target_dx is None and target_x is None:
|
||||||
|
raise Exception("At least one of target_x and target_dx must not be None")
|
||||||
|
if added_anims is None:
|
||||||
|
added_anims = []
|
||||||
|
|
||||||
start_dx = secant_slope_group.kwargs["dx"]
|
start_dx = secant_slope_group.kwargs["dx"]
|
||||||
|
start_x = secant_slope_group.kwargs["x"]
|
||||||
|
if target_dx is None:
|
||||||
|
target_dx = start_dx
|
||||||
|
if target_x is None:
|
||||||
|
target_x = start_x
|
||||||
def update_func(group, alpha):
|
def update_func(group, alpha):
|
||||||
dx = interpolate(start_dx, target_dx, alpha)
|
dx = interpolate(start_dx, target_dx, alpha)
|
||||||
|
x = interpolate(start_x, target_x, alpha)
|
||||||
kwargs = dict(secant_slope_group.kwargs)
|
kwargs = dict(secant_slope_group.kwargs)
|
||||||
kwargs["dx"] = dx
|
kwargs["dx"] = dx
|
||||||
|
kwargs["x"] = x
|
||||||
new_group = self.get_secant_slope_group(**kwargs)
|
new_group = self.get_secant_slope_group(**kwargs)
|
||||||
Transform(group, new_group).update(1)
|
Transform(group, new_group).update(1)
|
||||||
return group
|
return group
|
||||||
|
|
||||||
self.play(UpdateFromAlphaFunc(
|
self.play(
|
||||||
secant_slope_group, update_func,
|
UpdateFromAlphaFunc(
|
||||||
run_time = run_time,
|
secant_slope_group, update_func,
|
||||||
**anim_kwargs
|
run_time = run_time,
|
||||||
))
|
**anim_kwargs
|
||||||
|
),
|
||||||
|
*added_anims
|
||||||
|
)
|
||||||
|
secant_slope_group.kwargs["x"] = target_x
|
||||||
|
secant_slope_group.kwargs["dx"] = target_dx
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
12
helpers.py
12
helpers.py
@ -112,6 +112,12 @@ def is_closed(points):
|
|||||||
def color_to_rgb(color):
|
def color_to_rgb(color):
|
||||||
return np.array(Color(color).get_rgb())
|
return np.array(Color(color).get_rgb())
|
||||||
|
|
||||||
|
def rgb_to_color(rgb):
|
||||||
|
try:
|
||||||
|
return Color(rgb = rgb)
|
||||||
|
except:
|
||||||
|
return Color(WHITE)
|
||||||
|
|
||||||
def color_to_int_rgb(color):
|
def color_to_int_rgb(color):
|
||||||
return (255*color_to_rgb(color)).astype('uint8')
|
return (255*color_to_rgb(color)).astype('uint8')
|
||||||
|
|
||||||
@ -126,7 +132,7 @@ def color_gradient(reference_colors, length_of_output):
|
|||||||
alphas_mod1[-1] = 1
|
alphas_mod1[-1] = 1
|
||||||
floors[-1] = len(rgbs) - 2
|
floors[-1] = len(rgbs) - 2
|
||||||
return [
|
return [
|
||||||
Color(rgb = interpolate(rgbs[i], rgbs[i+1], alpha))
|
rgb_to_color(interpolate(rgbs[i], rgbs[i+1], alpha))
|
||||||
for i, alpha in zip(floors, alphas_mod1)
|
for i, alpha in zip(floors, alphas_mod1)
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -397,7 +403,7 @@ def make_even_by_cycling(iterable_1, iterable_2):
|
|||||||
[cycle2.next() for x in range(length)]
|
[cycle2.next() for x in range(length)]
|
||||||
)
|
)
|
||||||
|
|
||||||
### Alpha Functions ###
|
### Rate Functions ###
|
||||||
|
|
||||||
def sigmoid(x):
|
def sigmoid(x):
|
||||||
return 1.0/(1 + np.exp(-x))
|
return 1.0/(1 + np.exp(-x))
|
||||||
@ -419,6 +425,8 @@ def there_and_back(t, inflection = 10.0):
|
|||||||
new_t = 2*t if t < 0.5 else 2*(1 - t)
|
new_t = 2*t if t < 0.5 else 2*(1 - t)
|
||||||
return smooth(new_t, inflection)
|
return smooth(new_t, inflection)
|
||||||
|
|
||||||
|
def running_start(t, pull_factor = -0.5):
|
||||||
|
return bezier([0, 0, pull_factor, pull_factor, 1, 1, 1])(t)
|
||||||
|
|
||||||
def not_quite_there(func = smooth, proportion = 0.7):
|
def not_quite_there(func = smooth, proportion = 0.7):
|
||||||
def result(t):
|
def result(t):
|
||||||
|
@ -345,14 +345,16 @@ class VMobject(Mobject):
|
|||||||
return self
|
return self
|
||||||
self.mark_paths_closed = False
|
self.mark_paths_closed = False
|
||||||
num_cubics = mobject.get_num_anchor_points()-1
|
num_cubics = mobject.get_num_anchor_points()-1
|
||||||
lower_index = int(a*num_cubics)
|
lower_index = np.floor(a*num_cubics)
|
||||||
upper_index = int(b*num_cubics)
|
upper_index = np.ceil(b*num_cubics)
|
||||||
points = np.array(
|
points = np.array(
|
||||||
mobject.points[3*lower_index:3*upper_index+4]
|
mobject.points[3*lower_index:3*upper_index+4]
|
||||||
)
|
)
|
||||||
if len(points) > 1:
|
if len(points) > 1:
|
||||||
a_residue = (num_cubics*a)%1
|
a_residue = (num_cubics*a)%1
|
||||||
b_residue = (num_cubics*b)%1
|
b_residue = (num_cubics*b)%1
|
||||||
|
if b == 1:
|
||||||
|
b_residue = 1
|
||||||
points[:4] = partial_bezier_points(
|
points[:4] = partial_bezier_points(
|
||||||
points[:4], a_residue, 1
|
points[:4], a_residue, 1
|
||||||
)
|
)
|
||||||
|
@ -279,20 +279,17 @@ class PiCreatureScene(Scene):
|
|||||||
else:
|
else:
|
||||||
return Randolph().to_corner(DOWN+LEFT)
|
return Randolph().to_corner(DOWN+LEFT)
|
||||||
|
|
||||||
def say(self, words, target_mode = "speaking"):
|
def say(self, *content, **kwargs):
|
||||||
if isinstance(words, str):
|
added_anims = []
|
||||||
words = TextMobject(words)
|
if "target_mode" in kwargs:
|
||||||
bubble = SpeechBubble()
|
target_mode = kwargs["target_mode"]
|
||||||
bubble.add_content(words)
|
else:
|
||||||
bubble.resize_to_content()
|
target_mode = "speaking"
|
||||||
bubble.pin_to(self.pi_creature)
|
added_anims += [self.pi_creature.change_mode, target_mode]
|
||||||
self.play(
|
self.play(
|
||||||
self.pi_creature.change_mode, target_mode,
|
PiCreatureSays(self.pi_creature, *content, **kwargs),
|
||||||
self.pi_creature.look_at, bubble.content,
|
*added_anims
|
||||||
ShowCreation(bubble),
|
|
||||||
Write(bubble.content)
|
|
||||||
)
|
)
|
||||||
self.pi_creature.bubble = bubble
|
|
||||||
|
|
||||||
def get_bubble_fade_anims(self, target_mode = "plain"):
|
def get_bubble_fade_anims(self, target_mode = "plain"):
|
||||||
return [
|
return [
|
||||||
@ -311,20 +308,17 @@ class PiCreatureScene(Scene):
|
|||||||
if first_anim.mobject is self.pi_creature:
|
if first_anim.mobject is self.pi_creature:
|
||||||
return animations
|
return animations
|
||||||
|
|
||||||
if isinstance(first_anim, Transform):
|
#Look at ending state
|
||||||
mobject_of_interest = first_anim.ending_mobject
|
first_anim.update(1)
|
||||||
else:
|
point_of_interest = first_anim.mobject.get_center()
|
||||||
mobject_of_interest = first_anim.mobject
|
first_anim.update(0)
|
||||||
|
|
||||||
last_anim = animations[-1]
|
last_anim = animations[-1]
|
||||||
if last_anim.mobject is self.pi_creature and isinstance(last_anim, Transform):
|
if last_anim.mobject is self.pi_creature and isinstance(last_anim, Transform):
|
||||||
if isinstance(animations[-1], Transform):
|
if isinstance(animations[-1], Transform):
|
||||||
animations[-1].ending_mobject.look_at(mobject_of_interest)
|
animations[-1].ending_mobject.look_at(point_of_interest)
|
||||||
return animations
|
return animations
|
||||||
new_anim = ApplyMethod(
|
new_anim = ApplyMethod(self.pi_creature.look_at, point_of_interest)
|
||||||
self.pi_creature.look_at,
|
|
||||||
mobject_of_interest
|
|
||||||
)
|
|
||||||
return list(animations) + [new_anim]
|
return list(animations) + [new_anim]
|
||||||
|
|
||||||
def blink(self):
|
def blink(self):
|
||||||
|
Reference in New Issue
Block a user