Farther along EoLA chapter 3

This commit is contained in:
Grant Sanderson 2016-07-22 11:22:31 -07:00
parent b16695b6f3
commit 627f16b057
12 changed files with 408 additions and 168 deletions

View file

@ -30,6 +30,12 @@ class Animation(object):
self.name = self.__class__.__name__ + str(self.mobject)
self.update(0)
def update_config(self, **kwargs):
if "path_arc" in kwargs:
kwargs["path_arc"]
digest_config(self, kwargs)
return self
def __str__(self):
return self.name
@ -65,7 +71,7 @@ class Animation(object):
def set_name(self, name):
self.name = name
return self
return self
def update_mobject(self, alpha):
#Typically ipmlemented by subclass

View file

@ -26,6 +26,11 @@ class Transform(Animation):
self.name += "To" + str(ending_mobject)
self.mobject.stroke_width = ending_mobject.stroke_width
def update_config(self, **kwargs):
Animation.update_config(self, **kwargs)
if "path_arc" in kwargs:
self.path_func = path_along_arc(kwargs["path_arc"])
def init_path_func(self):
if self.path_func is not None:
return

View file

@ -57,7 +57,7 @@ class CoordinatesAsScalars(VectorScene):
}
def construct(self):
self.lock_in_dim_grid()
self.lock_in_faded_grid()
vector = self.add_vector(self.vector_coords)
array, x_line, y_line = self.vector_to_coords(vector)
@ -194,7 +194,7 @@ class CoordinatesAsScalarsExample2(CoordinatesAsScalars):
}
def construct(self):
self.lock_in_dim_grid()
self.lock_in_faded_grid()
basis_vectors = self.get_basis_vectors()
labels = self.get_basis_vector_labels()
@ -246,7 +246,7 @@ class ShowVaryingLinearCombinations(VectorScene):
"finish_by_drawing_lines" : False,
}
def construct(self):
self.lock_in_dim_grid()
self.lock_in_faded_grid()
v1 = self.add_vector(self.vector1, color = self.vector1_color)
v2 = self.add_vector(self.vector2, color = self.vector2_color)
v1_label = self.label_vector(
@ -651,7 +651,7 @@ class VectorsToDotsScene(VectorScene):
"end_color" : BLUE_E,
}
def construct(self):
self.lock_in_dim_grid()
self.lock_in_faded_grid()
vectors = self.get_vectors()
colors = Color(self.start_color).range_to(
@ -761,9 +761,6 @@ class VectorsInThePlane(VectorsToDotsScene):
)
self.remove(*vectors)
self.dither()
self.play(Homotopy(plane_wave_homotopy, plane, run_time = 3))
self.play(Transform(plane, NumberPlane(), rate_func = rush_from))
self.dither()
class HowToThinkVectorsVsPoint(Scene):
@ -1238,7 +1235,6 @@ class CheckYourUnderstanding(TeacherStudentsScene):
self.random_blink()
class TechnicalDefinitionOfBasis(Scene):
def construct(self):
title = TextMobject("Technical definition of basis:")
@ -1288,4 +1284,3 @@ class NextVideo(Scene):

View file

@ -75,32 +75,226 @@ class Introduction(TeacherStudentsScene):
class ShowGridCreation(Scene):
def construct(self):
plane = NumberPlane()
coords = plane.get_coordinate_labels()
self.play(ShowCreation(plane))
self.play(Write(coords, run_time = 5))
coords = VMobject(*plane.get_coordinate_labels())
self.play(ShowCreation(plane, run_time = 3))
self.play(Write(coords, run_time = 3))
self.dither()
class IntroduceLinearTransformations(LinearTransformationScene):
CONFIG = {
"show_basis_vectors" : False,
"include_background_plane" : False
}
def construct(self):
self.setup()
self.dither()
self.apply_transposed_matrix([[2, 1], [1, 2]])
self.dither()
lines_rule = TextMobject("Lines remain lines")
lines_rule.shift(2*UP).to_edge(LEFT)
origin_rule = TextMobject("Origin remains fixed")
origin_rule.shift(2*UP).to_edge(RIGHT)
arrow = Arrow(origin_rule, ORIGIN)
dot = Dot(ORIGIN, radius = 0.1, color = RED)
for rule in lines_rule, origin_rule:
rule.add_background_rectangle()
self.play(
# FadeIn(lines_rule_rect),
Write(lines_rule, run_time = 2),
)
self.dither()
self.play(
# FadeIn(origin_rule_rect),
Write(origin_rule, run_time = 2),
ShowCreation(arrow),
GrowFromCenter(dot)
)
self.dither()
class SimpleLinearTransformationScene(LinearTransformationScene):
CONFIG = {
"show_basis_vectors" : False,
"transposed_matrix" : [[2, 1], [1, 2]]
}
def construct(self):
self.setup()
self.dither()
self.apply_transposed_matrix(self.transposed_matrix)
self.dither()
class SimpleNonlinearTransformationScene(LinearTransformationScene):
CONFIG = {
"show_basis_vectors" : False,
"words" : "Not linear: some lines get curved"
}
def construct(self):
self.setup()
self.dither()
self.apply_nonlinear_transformation(self.func)
words = TextMobject(self.words)
words.to_corner(UP+RIGHT)
words.highlight(RED)
words.add_background_rectangle()
self.play(Write(words))
self.dither()
def func(self, point):
x, y, z = point
return (x+np.cos(y))*RIGHT + (y+np.sin(x))*UP
class MovingOrigin(SimpleNonlinearTransformationScene):
CONFIG = {
"words" : "Not linear: Origin moves"
}
def setup(self):
LinearTransformationScene.setup(self)
dot = Dot(ORIGIN, color = RED)
self.add_transformable_mobject(dot)
def func(self, point):
matrix_transform = self.get_matrix_transformation([[2, 0], [1, 1]])
return matrix_transform(point) + 2*UP+3*LEFT
class SneakyNonlinearTransformation(SimpleNonlinearTransformationScene):
CONFIG = {
"words" : "\\dots"
}
def func(self, point):
x, y, z = point
new_x = np.sign(x)*SPACE_WIDTH*smooth(abs(x) / SPACE_WIDTH)
new_y = np.sign(y)*SPACE_HEIGHT*smooth(abs(y) / SPACE_HEIGHT)
return [new_x, new_y, 0]
class SneakyNonlinearTransformationExplained(SneakyNonlinearTransformation):
CONFIG = {
"words" : "Not linear: diagonal lines get curved"
}
def setup(self):
LinearTransformationScene.setup(self)
diag = Line(
SPACE_HEIGHT*LEFT+SPACE_HEIGHT*DOWN,
SPACE_HEIGHT*RIGHT + SPACE_HEIGHT*UP
)
diag.insert_n_anchor_points(20)
diag.change_anchor_mode("smooth")
diag.highlight(YELLOW)
self.play(ShowCreation(diag))
self.add_transformable_mobject(diag)
class AnotherLinearTransformation(SimpleLinearTransformationScene):
CONFIG = {
"transposed_matrix" : [
[3, 0],
[1, 2]
]
}
def construct(self):
SimpleLinearTransformationScene.construct(self)
text = TextMobject([
"Grid lines remain",
"parallel",
"and",
"evenly spaced",
])
glr, p, a, es = text.split()
p.highlight(YELLOW)
es.highlight(GREEN)
text.add_background_rectangle()
text.shift(-text.get_bottom())
self.play(Write(text))
self.dither()
class Rotation(SimpleLinearTransformationScene):
CONFIG = {
"angle" : np.pi/3,
}
def construct(self):
self.transposed_matrix = [
[np.cos(self.angle), np.sin(self.angle)],
[-np.sin(self.angle), np.cos(self.angle)]
]
SimpleLinearTransformationScene.construct(self)
class YetAnotherLinearTransformation(SimpleLinearTransformationScene):
CONFIG = {
"transposed_matrix" : [
[-1, 1],
[3, 2],
]
}
class MoveAroundAllVectors(LinearTransformationScene):
CONFIG = {
"show_basis_vectors" : False,
"focus_on_one_vector" : False,
}
def construct(self):
self.setup()
vectors = VMobject(*[
Vector([x, y])
for x in np.arange(-int(SPACE_WIDTH)+0.5, int(SPACE_WIDTH)+0.5)
for y in np.arange(-int(SPACE_HEIGHT)+0.5, int(SPACE_HEIGHT)+0.5)
])
vectors.submobject_gradient_highlight(PINK, BLUE_E)
dots = VMobject(*[
Dot(v.get_end(), color = v.get_color())
for v in vectors.split()
])
self.dither()
self.play(ShowCreation(dots))
self.dither()
self.play(Transform(dots, vectors))
self.dither()
self.remove(dots)
if self.focus_on_one_vector:
vector = vectors.split()[43]#yeah, great coding Grant
self.remove(vectors)
self.add_vector(vector)
self.play(*[
FadeOut(v)
for v in vectors.split()
if v is not vector
])
self.dither()
self.add(vector.copy().highlight(DARK_GREY))
else:
for vector in vectors.split():
self.add_vector(vector)
self.apply_transposed_matrix([[3, 0], [1, 2]])
self.dither()
class MoveAroundJustOneVector(MoveAroundAllVectors):
CONFIG = {
"focus_on_one_vector" : True,
}
class RotateIHat(LinearTransformationScene):
CONFIG = {
"show_basis_vectors" : False
}
def construct(self):
self.setup()
i_hat, j_hat = self.get_basis_vectors()
i_label, j_label = self.get_basis_vector_labels()
self.play(ShowCreation(i_hat))
self.play(Write(i_label, run_time = 1))
self.dither()
self.play(FadeOut(i_label))
self.apply_transposed_matrix([[0, 1], [-1, 0]])
self.play(Write(j_label, run_time = 1))
self.dither()

View file

@ -19,99 +19,6 @@ Y_COLOR = RED_C
Z_COLOR = BLUE_D
class LinearTransformationScene(Scene):
CONFIG = {
"include_background_plane" : True,
"include_foreground_plane" : True,
"foreground_plane_kwargs" : {
"x_radius" : 2*SPACE_WIDTH,
"y_radius" : 2*SPACE_HEIGHT,
"secondary_line_ratio" : 0
},
"background_plane_kwargs" : {
"color" : GREY,
"secondary_color" : DARK_GREY,
"axes_color" : GREY,
},
"show_coordinates" : False,
"show_basis_vectors" : True,
"i_hat_color" : X_COLOR,
"j_hat_color" : Y_COLOR,
}
def setup(self):
self.background_mobjects = []
self.transformable_mobject = []
self.moving_vectors = []
self.background_plane = NumberPlane(
**self.background_plane_kwargs
)
if self.show_coordinates:
self.background_plane.add_coordinates()
if self.include_background_plane:
self.add_background_mobject(self.background_plane)
if self.include_foreground_plane:
self.plane = NumberPlane(**self.foreground_plane_kwargs)
self.add_transformable_mobject(self.plane)
if self.show_basis_vectors:
self.add_vector((1, 0), self.i_hat_color)
self.add_vector((0, 1), self.j_hat_color)
def add_background_mobject(self, *mobjects):
for mobject in mobjects:
if mobject not in self.background_mobjects:
self.background_mobjects.append(mobject)
self.add(mobject)
def add_transformable_mobject(self, *mobjects):
for mobject in mobjects:
if mobject not in self.transformable_mobject:
self.transformable_mobject.append(mobject)
self.add(mobject)
def add_vector(self, coords, color = YELLOW):
vector = Vector(self.background_plane.num_pair_to_point(coords))
vector.highlight(color)
self.moving_vectors.append(vector)
return vector
def apply_matrix(self, matrix, **kwargs):
matrix = np.array(matrix)
if matrix.shape == (2, 2):
new_matrix = np.identity(3)
new_matrix[:2, :2] = matrix
matrix = new_matrix
elif matrix.shape != (3, 3):
raise "Matrix has bad dimensions"
transpose = np.transpose(matrix)
def func(point):
return np.dot(point, transpose)
new_vectors = [
Vector(func(v.get_end()), color = v.get_stroke_color())
for v in self.moving_vectors
]
self.play(
ApplyPointwiseFunction(
func,
VMobject(*self.transformable_mobject),
**kwargs
),
Transform(
VMobject(*self.moving_vectors),
VMobject(*new_vectors),
**kwargs
)
)
def apply_nonlinear_transformation(self, function, **kwargs):
pass #TODO
class VectorScene(Scene):
CONFIG = {
"basis_vector_stroke_width" : 6
@ -130,7 +37,7 @@ class VectorScene(Scene):
self.add(axes)
return axes
def lock_in_dim_grid(self, dimness = 0.7, axes_dimness = 0.5):
def lock_in_faded_grid(self, dimness = 0.7, axes_dimness = 0.5):
plane = self.add_plane()
axes = plane.get_axes()
plane.fade(dimness)
@ -324,6 +231,112 @@ class VectorScene(Scene):
class LinearTransformationScene(VectorScene):
CONFIG = {
"include_background_plane" : True,
"include_foreground_plane" : True,
"foreground_plane_kwargs" : {
"x_radius" : 2*SPACE_WIDTH,
"y_radius" : 2*SPACE_HEIGHT,
"secondary_line_ratio" : 0
},
"background_plane_kwargs" : {
"color" : GREY,
"secondary_color" : DARK_GREY,
"axes_color" : GREY,
"stroke_width" : 2,
},
"show_coordinates" : False,
"show_basis_vectors" : True,
"i_hat_color" : X_COLOR,
"j_hat_color" : Y_COLOR,
}
def setup(self):
self.background_mobjects = []
self.transformable_mobject = []
self.moving_vectors = []
self.background_plane = NumberPlane(
**self.background_plane_kwargs
)
if self.show_coordinates:
self.background_plane.add_coordinates()
if self.include_background_plane:
self.add_background_mobject(self.background_plane)
if self.include_foreground_plane:
self.plane = NumberPlane(**self.foreground_plane_kwargs)
self.add_transformable_mobject(self.plane)
if self.show_basis_vectors:
self.add_vector((1, 0), self.i_hat_color)
self.add_vector((0, 1), self.j_hat_color)
def add_background_mobject(self, *mobjects):
for mobject in mobjects:
if mobject not in self.background_mobjects:
self.background_mobjects.append(mobject)
self.add(mobject)
def add_transformable_mobject(self, *mobjects):
for mobject in mobjects:
if mobject not in self.transformable_mobject:
self.transformable_mobject.append(mobject)
self.add(mobject)
def add_vector(self, vector, color = YELLOW, animate = False):
vector = VectorScene.add_vector(
self, vector, color = color, animate = animate
)
self.moving_vectors.append(vector)
return vector
def get_matrix_transformation(self, transposed_matrix):
transposed_matrix = np.array(transposed_matrix)
if transposed_matrix.shape == (2, 2):
new_matrix = np.identity(3)
new_matrix[:2, :2] = transposed_matrix
transposed_matrix = new_matrix
elif transposed_matrix.shape != (3, 3):
raise "Matrix has bad dimensions"
return lambda point: np.dot(point, transposed_matrix)
def get_vector_movement(self, func):
start = VMobject(*self.moving_vectors)
target = VMobject(*[
Vector(func(v.get_end()), color = v.get_color())
for v in self.moving_vectors
])
return Transform(start, target)
def apply_transposed_matrix(self, transposed_matrix, **kwargs):
func = self.get_matrix_transformation(transposed_matrix)
if "path_arc" not in kwargs:
net_rotation = np.mean([
angle_of_vector(func(RIGHT)),
angle_of_vector(func(UP))-np.pi/2
])
kwargs["path_arc"] = net_rotation
self.apply_function(func, **kwargs)
def apply_nonlinear_transformation(self, function, **kwargs):
self.plane.prepare_for_nonlinear_transform(100)
self.apply_function(function, **kwargs)
def apply_function(self, function, **kwargs):
if "run_time" not in kwargs:
kwargs["run_time"] = 3
self.play(
ApplyPointwiseFunction(
function,
VMobject(*self.transformable_mobject),
),
self.get_vector_movement(function),
**kwargs
)

View file

@ -22,8 +22,10 @@ class Mobject(object):
"name" : None,
"dim" : 3,
"target" : None,
#Options are lagged_start, one_at_a_time, all_at_once
#Options are lagged_start, smoothed_lagged_start,
#one_at_a_time, all_at_once
"submobject_partial_creation_mode" : "lagged_start",
#TODO, probably make this Animations's responsibility?
}
def __init__(self, *submobjects, **kwargs):
digest_config(self, kwargs)
@ -286,14 +288,21 @@ class Mobject(object):
## Color functions
def highlight(self, color = YELLOW_C, condition = None):
def highlight(self, color = YELLOW_C, family = True, condition = None):
"""
Condition is function which takes in one arguments, (x, y, z).
"""
raise Exception("Not implemented")
def gradient_highlight(self, start_color, end_color):
raise Exception("Not implemented")
raise Exception("Not implemented")
def submobject_gradient_highlight(self, start_color, end_color):
mobs = self.family_members_with_points()
colors = Color(start_color).range_to(end_color, len(mobs))
for mob, color in zip(mobs, colors):
mob.highlight(color, family = False)
return self
def set_color(self, color):
self.highlight(color)
@ -305,10 +314,10 @@ class Mobject(object):
return self
def fade_to(self, color, alpha):
start = color_to_rgb(self.get_color())
end = color_to_rgb(color)
new_rgb = interpolate(start, end, alpha)
for mob in self.family_members_with_points():
start = color_to_rgb(mob.get_color())
end = color_to_rgb(color)
new_rgb = interpolate(start, end, alpha)
mob.highlight(Color(rgb = new_rgb))
return self
@ -554,8 +563,10 @@ class Mobject(object):
mobject.family_members_with_points()
)
for i, (self_sub, mob_sub) in enumerate(pairs):
if spcm == "lagged_start":
if spcm in ["lagged_start", "smoothed_lagged_start"]:
prop = float(i)/len(pairs)
if spcm is "smoothed_lagged_start":
prop = smooth(prop)
sub_a = np.clip(2*a - prop, 0, 1)
sub_b = np.clip(2*b - prop, 0, 1)
elif spcm == "one_at_a_time":

View file

@ -26,9 +26,10 @@ class PMobject(Mobject):
self.rgbs = np.append(self.rgbs, rgbs, axis = 0)
return self
def highlight(self, color = YELLOW_C, condition = None):
def highlight(self, color = YELLOW_C, family = True, condition = None):
rgb = Color(color).get_rgb()
for mob in self.family_members_with_points():
mobs = self.family_members_with_points() if family else [self]
for mob in mobs:
if condition:
to_change = np.apply_along_axis(condition, 1, mob.points)
mob.rgbs[to_change, :] = rgb

View file

@ -1,5 +1,6 @@
from vectorized_mobject import VMobject
from svg_mobject import SVGMobject, VMobjectFromSVGPathstring
from topics.geometry import Rectangle
from helpers import *
import collections
@ -101,7 +102,15 @@ class TexMobject(SVGMobject):
lambda m1, m2 : int((m1.get_left()-m2.get_left())[0])
)
def add_background_rectangle(self, color = BLACK, opacity = 0.75):
rect = Rectangle(
stroke_width = 0,
fill_color = color,
fill_opacity = opacity
)
rect.replace(self, stretch = True)
self.submobjects = [rect] + self.submobjects
return self
class TextMobject(TexMobject):
CONFIG = {

View file

@ -6,8 +6,9 @@ from helpers import *
class VMobject(Mobject):
CONFIG = {
"fill_color" : BLACK,
"fill_color" : None,
"fill_opacity" : 0.0,
"stroke_color" : None,
#Indicates that it will not be displayed, but
#that it should count in parent mobject's path
"is_subpath" : False,
@ -21,12 +22,10 @@ class VMobject(Mobject):
## Colors
def init_colors(self):
if not hasattr(self, "stroke_color"):
self.stroke_color = self.color
self.set_style_data(
stroke_color = self.stroke_color,
stroke_color = self.stroke_color or self.color,
stroke_width = self.stroke_width,
fill_color = self.fill_color,
fill_color = self.fill_color or self.color,
fill_opacity = self.fill_opacity,
family = self.propogate_style_to_family
)
@ -58,6 +57,8 @@ class VMobject(Mobject):
return self
def set_fill(self, color = None, opacity = None, family = True):
if self.fill_opacity == 0 and opacity is None:
opacity = 1
return self.set_style_data(
fill_color = color,
fill_opacity = opacity,
@ -71,19 +72,22 @@ class VMobject(Mobject):
family = family
)
def highlight(self, color):
self.set_fill(color = color)
self.set_stroke(color = color)
def highlight(self, color, family = True):
self.set_fill(
color = color,
opacity = self.get_fill_opacity(),
family = family
)
self.set_stroke(color = color, family = family)
return self
def fade(self, darkness = 0.5):
Mobject.fade(self, darkness)
return self
# def fade(self, darkness = 0.5):
# Mobject.fade(self, darkness)
# return self
def get_fill_color(self):
try:
self.fill_rgb[self.fill_rgb<0] = 0
self.fill_rgb[self.fill_rgb>1] = 1
self.fill_rgb = np.clip(self.fill_rgb, 0, 1)
return Color(rgb = self.fill_rgb)
except:
return Color(WHITE)
@ -98,8 +102,10 @@ class VMobject(Mobject):
except:
return Color(WHITE)
#TODO, get color? Specify if stroke or fill
#is the predominant color attribute?
def get_color(self):
if self.fill_opacity == 0:
return self.get_stroke_color()
return self.get_fill_color()
## Drawing
def start_at(self, point):

View file

@ -132,20 +132,16 @@ class Scene(object):
return [m.copy() for m in self.mobjects]
def align_run_times(self, *animations, **kwargs):
if "run_time" in kwargs:
run_time = kwargs["run_time"]
for animation in animations:
animation.set_run_time(run_time)
else:
max_run_time = max([a.run_time for a in animations])
for animation in animations:
if animation.run_time != max_run_time:
new_rate_func = squish_rate_func(
animation.get_rate_func(),
0, 1./max_run_time
)
animation.set_rate_func(new_rate_func)
animation.set_run_time(max_run_time)
max_run_time = max([a.run_time for a in animations])
for animation in animations:
animation.update_config(**kwargs)
if animation.run_time != max_run_time:
new_rate_func = squish_rate_func(
animation.get_rate_func(),
0, 1./max_run_time
)
animation.set_rate_func(new_rate_func)
animation.set_run_time(max_run_time)
return animations
def separate_moving_and_static_mobjects(self, *animations):

View file

@ -44,8 +44,8 @@ class Dot(Circle): #Use 1D density, even though 2D
CONFIG = {
"radius" : 0.05,
"stroke_width" : 0,
"fill_color" : WHITE,
"fill_opacity" : 1.0
"fill_opacity" : 1.0,
"color" : WHITE
}
def __init__(self, point = ORIGIN, **kwargs):
Circle.__init__(self, **kwargs)

View file

@ -116,6 +116,7 @@ class NumberPlane(VMobject):
"color" : BLUE_D,
"secondary_color" : BLUE_E,
"axes_color" : WHITE,
"secondary_stroke_width" : 1,
"x_radius": SPACE_WIDTH,
"y_radius": SPACE_HEIGHT,
"space_unit_to_x_unit" : 1,
@ -127,6 +128,7 @@ class NumberPlane(VMobject):
"written_coordinate_nudge" : 0.1*(DOWN+RIGHT),
"num_pair_at_center" : (0, 0),
"propogate_style_to_family" : False,
"submobject_partial_creation_mode" : "smoothed_lagged_start",
}
def generate_points(self):
@ -170,9 +172,11 @@ class NumberPlane(VMobject):
def init_colors(self):
VMobject.init_colors(self)
self.axes.set_stroke(self.axes_color)
self.main_lines.set_stroke(self.color)
self.secondary_lines.set_stroke(self.secondary_color, 1)
self.axes.set_stroke(self.axes_color, self.stroke_width)
self.main_lines.set_stroke(self.color, self.stroke_width)
self.secondary_lines.set_stroke(
self.secondary_color, self.secondary_stroke_width
)
return self
def get_center_point(self):
@ -236,10 +240,10 @@ class NumberPlane(VMobject):
arrow = Arrow(ORIGIN, coords, **kwargs)
return arrow
def prepare_for_nonlinear_transform(self):
def prepare_for_nonlinear_transform(self, num_inserted_anchor_points = 40):
for mob in self.submobject_family():
if mob.get_num_points() > 0:
mob.insert_n_anchor_points(20)
mob.insert_n_anchor_points(num_inserted_anchor_points)
mob.change_anchor_mode("smooth")