Sure, I *could* separate all these updates into separate reasonable commits, but where's the fun in that?

This commit is contained in:
Grant Sanderson
2017-02-16 13:03:26 -08:00
parent 0c1bb9d41e
commit 3535b39dd1
10 changed files with 1280 additions and 39 deletions

View File

@ -9,6 +9,7 @@ from helpers import *
from animation import Animation from animation import Animation
from simple_animations import DelayByOrder from simple_animations import DelayByOrder
from mobject import Mobject, Point, VMobject, Group from mobject import Mobject, Point, VMobject, Group
from topics.geometry import Dot
class Transform(Animation): class Transform(Animation):
CONFIG = { CONFIG = {
@ -160,6 +161,26 @@ class ShimmerIn(DelayByOrder):
mobject.sort_points(lambda p : np.dot(p, DOWN+RIGHT)) mobject.sort_points(lambda p : np.dot(p, DOWN+RIGHT))
DelayByOrder.__init__(self, FadeIn(mobject, **kwargs)) DelayByOrder.__init__(self, FadeIn(mobject, **kwargs))
class FocusOn(Transform):
CONFIG = {
"opacity" : 0.2,
"color" : GREY,
"run_time" : 2,
"remover" : True,
}
def __init__(self, mobject_or_point, **kwargs):
digest_config(self, kwargs)
big_dot = Dot(
radius = SPACE_WIDTH+SPACE_HEIGHT,
stroke_width = 0,
fill_color = self.color,
fill_opacity = 0,
)
little_dot = Dot(radius = 0)
little_dot.set_fill(self.color, opacity = self.opacity)
little_dot.move_to(mobject_or_point)
Transform.__init__(self, big_dot, little_dot, **kwargs)
class Rotate(ApplyMethod): class Rotate(ApplyMethod):
CONFIG = { CONFIG = {

File diff suppressed because it is too large Load Diff

View File

@ -37,10 +37,7 @@ class GraphScene(Scene):
def setup_axes(self, animate = False): def setup_axes(self, animate = False):
x_num_range = float(self.x_max - self.x_min) x_num_range = float(self.x_max - self.x_min)
self.space_unit_to_x = self.x_axis_width/x_num_range self.space_unit_to_x = self.x_axis_width/x_num_range
if self.x_labeled_nums is None: self.x_labeled_nums = self.x_labeled_nums or []
self.x_labeled_nums = np.arange(
self.x_min, self.x_max, 2*self.x_tick_frequency
)
x_axis = NumberLine( x_axis = NumberLine(
x_min = self.x_min, x_min = self.x_min,
x_max = self.x_max, x_max = self.x_max,
@ -67,10 +64,7 @@ class GraphScene(Scene):
y_num_range = float(self.y_max - self.y_min) y_num_range = float(self.y_max - self.y_min)
self.space_unit_to_y = self.y_axis_height/y_num_range self.space_unit_to_y = self.y_axis_height/y_num_range
if self.y_labeled_nums is None: self.y_labeled_nums = self.y_labeled_nums or []
self.y_labeled_nums = np.arange(
self.y_min, self.y_max, 2*self.y_tick_frequency
)
y_axis = NumberLine( y_axis = NumberLine(
x_min = self.y_min, x_min = self.y_min,
x_max = self.y_max, x_max = self.y_max,
@ -221,7 +215,21 @@ class GraphScene(Scene):
self.coords_to_point(x, 0), self.coords_to_point(x, 0),
self.input_to_graph_point(x, graph), self.input_to_graph_point(x, graph),
**line_kwargs **line_kwargs
) )
def get_vertical_lines_to_graph(
self, graph,
x_min = None,
x_max = None,
num_lines = 20,
**kwargs
):
x_min = x_min or self.x_min
x_max = x_max or self.x_max
return VGroup(*[
self.get_vertical_line_to_graph(x, graph, **kwargs)
for x in np.linspace(x_min, x_max, num_lines)
])
def get_secant_slope_group( def get_secant_slope_group(
self, self,
@ -236,6 +244,15 @@ class GraphScene(Scene):
secant_line_color = None, secant_line_color = None,
secant_line_length = 10, secant_line_length = 10,
): ):
"""
Resulting group is of the form VGroup(
dx_line,
df_line,
dx_label, (if applicable)
df_label, (if applicable)
secant_line, (if applicable)
)
"""
kwargs = locals() kwargs = locals()
kwargs.pop("self") kwargs.pop("self")
group = VGroup() group = VGroup()

View File

@ -310,7 +310,7 @@ class Mobject(object):
def scale_to_fit_height(self, height): def scale_to_fit_height(self, height):
return self.stretch_to_fit(height, 1, stretch = False) return self.stretch_to_fit(height, 1, stretch = False)
def space_out_submobjects(self, factor = 1.5): def space_out_submobjects(self, factor = 1.5, **kwargs):
self.scale_in_place(factor) self.scale_in_place(factor)
for submob in self.submobjects: for submob in self.submobjects:
submob.scale_in_place(1./factor) submob.scale_in_place(1./factor)

View File

@ -1,4 +1,4 @@
from vectorized_mobject import VMobject from vectorized_mobject import VMobject, VGroup
from svg_mobject import SVGMobject, VMobjectFromSVGPathstring from svg_mobject import SVGMobject, VMobjectFromSVGPathstring
from topics.geometry import BackgroundRectangle from topics.geometry import BackgroundRectangle
from helpers import * from helpers import *
@ -74,8 +74,24 @@ class TexMobject(SVGMobject):
result = result.replace("\n", " \\\\ \n ") result = result.replace("\n", " \\\\ \n ")
result = " ".join([self.alignment, result]) result = " ".join([self.alignment, result])
result = result.strip() result = result.strip()
result = self.remove_stray_braces(result)
return result return result
def remove_stray_braces(self, tex):
"""
Makes TexMobject resiliant to unmatched { at start
"""
num_lefts, num_rights = [
tex.count(char)
for char in "{}"
]
if tex.startswith("{") and num_lefts > num_rights:
return tex[1:]
elif tex.endswith("}") and num_rights > num_lefts:
return tex[:-1]
return tex
def get_tex_string(self): def get_tex_string(self):
return self.tex_string return self.tex_string
@ -86,7 +102,7 @@ class TexMobject(SVGMobject):
for expr in self.args: for expr in self.args:
model = TexMobject(expr, **self.CONFIG) model = TexMobject(expr, **self.CONFIG)
new_index = curr_index + len(model.submobjects) new_index = curr_index + len(model.submobjects)
new_submobjects.append(VMobject( new_submobjects.append(VGroup(
*self.submobjects[curr_index:new_index] *self.submobjects[curr_index:new_index]
)) ))
curr_index = new_index curr_index = new_index
@ -129,8 +145,9 @@ class TextMobject(TexMobject):
class Brace(TexMobject): class Brace(TexMobject):
CONFIG = { CONFIG = {
"buff" : 0.2, "buff" : 0.2,
"n_quads" : 3, "width_multiplier" : 2,
"tex_string" : "\\underbrace{%s}"%(3*"\\qquad"), "max_num_quads" : 15,
"min_num_quads" : 0,
} }
def __init__(self, mobject, direction = DOWN, **kwargs): def __init__(self, mobject, direction = DOWN, **kwargs):
digest_config(self, kwargs, locals()) digest_config(self, kwargs, locals())
@ -141,7 +158,11 @@ class Brace(TexMobject):
target_width = right[0]-left[0] target_width = right[0]-left[0]
## Adding int(target_width) qquads gives approximately the right width ## Adding int(target_width) qquads gives approximately the right width
tex_string = "\\underbrace{%s}"%(int(target_width)*"\\qquad") num_quads = np.clip(
int(self.width_multiplier*target_width),
self.min_num_quads, self.max_num_quads
)
tex_string = "\\underbrace{%s}"%(num_quads*"\\qquad")
TexMobject.__init__(self, tex_string, **kwargs) TexMobject.__init__(self, tex_string, **kwargs)
self.stretch_to_fit_width(target_width) self.stretch_to_fit_width(target_width)
self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN) self.shift(left - self.get_corner(UP+LEFT) + self.buff*DOWN)

View File

@ -1,6 +1,7 @@
import numpy as np import numpy as np
from scene import Scene from scene import Scene
from animation.transform import FadeIn
from mobject import Mobject from mobject import Mobject
from topics.geometry import Rectangle from topics.geometry import Rectangle
from camera import MovingCamera, Camera from camera import MovingCamera, Camera
@ -18,6 +19,7 @@ class ZoomedScene(Scene):
"zoomed_canvas_corner" : UP+RIGHT, "zoomed_canvas_corner" : UP+RIGHT,
"zoomed_canvas_corner_buff" : DEFAULT_MOBJECT_TO_EDGE_BUFFER, "zoomed_canvas_corner_buff" : DEFAULT_MOBJECT_TO_EDGE_BUFFER,
"zoomed_camera_background" : None, "zoomed_camera_background" : None,
"little_rectangle_start_position" : ORIGIN,
"zoom_factor" : 6, "zoom_factor" : 6,
"square_color" : WHITE, "square_color" : WHITE,
"zoom_activated" : False, "zoom_activated" : False,
@ -28,6 +30,12 @@ class ZoomedScene(Scene):
self.setup_zoomed_camera() self.setup_zoomed_camera()
self.zoom_activated = True self.zoom_activated = True
def animate_activate_zooming(self):
self.activate_zooming()
self.play(*map(FadeIn, [
self.little_rectangle, self.big_rectangle
]))
def disactivate_zooming(self): def disactivate_zooming(self):
self.remove(self.big_rectangle, self.little_rectangle) self.remove(self.big_rectangle, self.little_rectangle)
self.zoom_activated = False self.zoom_activated = False
@ -71,7 +79,9 @@ class ZoomedScene(Scene):
def setup_zoomed_camera(self): def setup_zoomed_camera(self):
self.little_rectangle = self.big_rectangle.copy() self.little_rectangle = self.big_rectangle.copy()
self.little_rectangle.scale(1./self.zoom_factor) self.little_rectangle.scale(1./self.zoom_factor)
self.little_rectangle.center() self.little_rectangle.move_to(
self.little_rectangle_start_position
)
self.zoomed_camera = MovingCamera( self.zoomed_camera = MovingCamera(
self.little_rectangle, self.little_rectangle,
pixel_shape = self.zoomed_canvas_pixel_shape, pixel_shape = self.zoomed_canvas_pixel_shape,

View File

@ -242,8 +242,8 @@ class PiCreatureBubbleIntroduction(AnimationGroup):
} }
def __init__(self, pi_creature, *content, **kwargs): def __init__(self, pi_creature, *content, **kwargs):
digest_config(self, kwargs) digest_config(self, kwargs)
if isinstance(content, Mobject): if isinstance(content[0], Mobject):
bubble_content = content bubble_content = content[0]
else: else:
bubble_content = TextMobject(*content) bubble_content = TextMobject(*content)
bubble = pi_creature.get_bubble( bubble = pi_creature.get_bubble(
@ -390,11 +390,13 @@ class PiCreatureScene(Scene):
first mobject being animated with each .play call first mobject being animated with each .play call
""" """
animations = Scene.compile_play_args_to_animation_list(self, *args) animations = Scene.compile_play_args_to_animation_list(self, *args)
if len(animations) == 0: non_pi_creature_anims = filter(
return animations lambda anim : anim.mobject not in self.get_pi_creatures(),
first_anim = animations[0] animations
if isinstance(first_anim, Blink): )
if len(non_pi_creature_anims) == 0:
return animations return animations
first_anim = non_pi_creature_anims[0]
#Look at ending state #Look at ending state
first_anim.update(1) first_anim.update(1)
point_of_interest = first_anim.mobject.get_center() point_of_interest = first_anim.mobject.get_center()
@ -405,10 +407,10 @@ class PiCreatureScene(Scene):
continue continue
if pi_creature in first_anim.mobject.submobject_family(): if pi_creature in first_anim.mobject.submobject_family():
continue continue
anims_with_pi_creature = [ anims_with_pi_creature = filter(
anim for anim in animations lambda anim : pi_creature in anim.mobject.submobject_family(),
if pi_creature in anim.mobject.submobject_family() animations
] )
if anims_with_pi_creature: if anims_with_pi_creature:
for anim in anims_with_pi_creature: for anim in anims_with_pi_creature:
if isinstance(anim, Transform): if isinstance(anim, Transform):

View File

@ -154,6 +154,7 @@ class DashedLine(Line):
for p1, p2, include in zip(points, points[1:], includes): for p1, p2, include in zip(points, points[1:], includes):
if include: if include:
self.add(Line(p1, p2, **self.init_kwargs)) self.add(Line(p1, p2, **self.init_kwargs))
self.put_start_and_end_on(self.start, self.end)
return self return self
def get_start(self): def get_start(self):

View File

@ -16,6 +16,7 @@ class NumberLine(VMobject):
"tick_frequency" : 1, "tick_frequency" : 1,
"leftmost_tick" : None, #Defaults to ceil(x_min) "leftmost_tick" : None, #Defaults to ceil(x_min)
"numbers_with_elongated_ticks" : [0], "numbers_with_elongated_ticks" : [0],
"numbers_to_show" : None,
"longer_tick_multiple" : 2, "longer_tick_multiple" : 2,
"number_at_center" : 0, "number_at_center" : 0,
"propogate_style_to_family" : True "propogate_style_to_family" : True
@ -62,11 +63,19 @@ class NumberLine(VMobject):
) )
def point_to_number(self, point): def point_to_number(self, point):
dist_from_left = float(point[0]-self.main_line.get_left()[0]) left_point, right_point = self.main_line.get_start_and_end()
num_dist_from_left = dist_from_left/self.space_unit_to_num full_vect = right_point-left_point
return self.x_min + num_dist_from_left def distance_from_left(p):
return np.dot(p-left_point, full_vect)/np.linalg.norm(full_vect)
return interpolate(
self.x_min, self.x_max,
distance_from_left(point)/distance_from_left(right_point)
)
def default_numbers_to_display(self): def default_numbers_to_display(self):
if self.numbers_to_show is not None:
return self.numbers_to_show
return np.arange(self.leftmost_tick, self.x_max, 1) return np.arange(self.leftmost_tick, self.x_max, 1)
def get_vertical_number_offset(self, direction = DOWN): def get_vertical_number_offset(self, direction = DOWN):

View File

@ -1,6 +1,8 @@
from helpers import * from helpers import *
from mobject.vectorized_mobject import VGroup
from topics.geometry import Square
from scene import Scene from scene import Scene
from camera import Camera from camera import Camera
@ -60,7 +62,24 @@ class ThreeDScene(Scene):
"camera_class" : ThreeDCamera, "camera_class" : ThreeDCamera,
} }
##############
class Cube(VGroup):
CONFIG = {
"fill_opacity" : 0.75,
"fill_color" : BLUE,
"stroke_width" : 0,
"propogate_style_to_family" : True,
"side_length" : 2,
}
def generate_points(self):
faces = [
Square(side_length = self.side_length).shift(OUT).apply_function(
lambda p : np.dot(p, z_to_vector(vect).T)
)
for vect in IN, OUT, LEFT, RIGHT, UP, DOWN
]
self.add(*faces)