First scene of eoc/chapter3

This commit is contained in:
Grant Sanderson
2017-01-25 13:00:01 -08:00
parent 2930c094f1
commit 694297d4ad
5 changed files with 311 additions and 47 deletions

227
eoc/chapter3.py Normal file
View 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)

View File

@ -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

View File

@ -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):

View File

@ -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
) )

View File

@ -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):