Merge branch 'master' into master

This commit is contained in:
Grant Sanderson
2018-01-29 16:59:00 -08:00
committed by GitHub
16 changed files with 1338 additions and 427 deletions

View File

@@ -32,64 +32,8 @@ from topics.graph_scene import *
# TODO/WARNING: There's a lot of refactoring and cleanup to be done in this code,
# (and it will be done, but first I'll figure out what I'm doing with all this...)
# -SR
class DualScene(Scene):
CONFIG = {
"num_needed_anchor_points" : 10
}
def setup(self):
split_line = DashedLine(SPACE_HEIGHT * UP, SPACE_HEIGHT * DOWN)
self.num_plane = NumberPlane(x_radius = SPACE_WIDTH/2)
self.num_plane.to_edge(LEFT, buff = 0)
self.num_plane.prepare_for_nonlinear_transform()
self.add(self.num_plane, split_line)
def apply_function(self, func, run_time = 3):
self.func = func
right_plane = self.num_plane.copy()
right_plane.center()
right_plane.prepare_for_nonlinear_transform()
right_plane.apply_function(func)
right_plane.shift(SPACE_WIDTH/2 * RIGHT)
self.right_plane = right_plane
crappy_cropper = FullScreenFadeRectangle(fill_opacity = 1)
crappy_cropper.stretch_to_fit_width(SPACE_WIDTH)
crappy_cropper.to_edge(LEFT, buff = 0)
self.play(
ReplacementTransform(self.num_plane.copy(), right_plane),
FadeIn(crappy_cropper),
Animation(self.num_plane),
run_time = run_time
)
def squash_onto_left(self, object):
object.shift(SPACE_WIDTH/2 * LEFT)
def squash_onto_right(self, object):
object.shift(SPACE_WIDTH/2 * RIGHT)
def path_draw(self, input_object, run_time = 3):
output_object = input_object.copy()
if input_object.get_num_anchor_points() < self.num_needed_anchor_points:
input_object.insert_n_anchor_points(self.num_needed_anchor_points)
output_object.apply_function(self.func)
self.squash_onto_left(input_object)
self.squash_onto_right(output_object)
self.play(
ShowCreation(input_object),
ShowCreation(output_object),
run_time = run_time
)
class TestDual(DualScene):
def construct(self):
self.force_skipping()
self.apply_function(lambda (x, y, z) : complex_to_R3(complex(x,y)**2))
self.revert_to_original_skipping_status()
self.path_draw(Line(LEFT + DOWN, RIGHT + DOWN))
class EquationSolver1d(GraphScene, ZoomedScene, ReconfigurableScene):
class EquationSolver1d(GraphScene, ZoomedScene):
CONFIG = {
"func" : lambda x : x,
"targetX" : 0,
@@ -302,7 +246,7 @@ class ArrowCircleTest(Scene):
base_arrow = Arrow(circle_radius * 0.7 * RIGHT, circle_radius * 1.3 * RIGHT)
def rev_rotate(x, revs):
x.rotate(revs * TAU)
x.rotate(revs * TAU, about_point = ORIGIN)
x.set_color(color_func(revs))
return x
@@ -560,8 +504,11 @@ class PiWalker(Scene):
rev_func = rev_func,
remover = (i < len(walk_coords) - 1)
),
ShowCreation(Line(start_point, end_point)),
ShowCreation(Line(start_point, end_point), rate_func = None),
run_time = self.step_run_time)
# TODO: Allow smooth paths instead of brekaing them up into lines, and
# use point_from_proportion to get points along the way
self.wait()
@@ -626,10 +573,10 @@ class EquationSolver2d(Scene):
alpha_winder = make_alpha_winder(rev_func, start, end, self.num_checkpoints)
a0 = alpha_winder(0)
rebased_winder = lambda alpha: alpha_winder(alpha) - a0 + start_wind
line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end),
flashing_line = Line(num_plane.coords_to_point(*start), num_plane.coords_to_point(*end),
stroke_width = 5,
color = RED)
thin_line = line.copy()
thin_line = flashing_line.copy()
thin_line.set_stroke(width = 1)
walker_anim = LinearWalker(
start_coords = start,
@@ -638,12 +585,12 @@ class EquationSolver2d(Scene):
rev_func = rev_func,
remover = True
)
line_draw_anim = AnimationGroup(ShowCreation(line, rate_func = None), walker_anim,
run_time = 2)
anim = Succession(
line_draw_anim,
Transform, line, thin_line
)
line_draw_anim = AnimationGroup(
ShowCreation(thin_line),
#ShowPassingFlash(flashing_line),
walker_anim,
rate_func = None)
anim = line_draw_anim
return (anim, rebased_winder(1))
wind_so_far = 0
@@ -736,6 +683,29 @@ class FirstSqrtScene(EquationSolver1d):
"show_target_line" : True,
}
class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
# TODO: Don't bother with ReconfigurableScene; just use new config from start
# (But can also use this as written, and just cut into middle in Premiere)
def setup(self):
FirstSqrtScene.setup(self)
ReconfigurableScene.setup(self)
def construct(self):
shiftVal = self.targetY
self.drawGraph()
newOrigin = self.coords_to_point(0, shiftVal)
self.transition_to_alt_config(
func = lambda x : x**2 - shiftVal,
targetY = 0,
graph_label = "y = x^2 - " + str(shiftVal),
y_min = self.y_min - shiftVal,
y_max = self.y_max - shiftVal,
show_target_line = False,
graph_origin = newOrigin)
self.solveEquation()
# TODO: Pi creatures intrigued
class ComplexPlaneIs2d(Scene):
@@ -758,15 +728,22 @@ class NumberLineScene(Scene):
left_point = num_line.number_to_point(-1)
right_point = num_line.number_to_point(1)
# TODO: Make this line a thin rectangle
interval_1d = Line(left_point, right_point,
stroke_color = inner_color, stroke_width = stroke_width)
rect_1d = Rectangle(stroke_width = 0, fill_opacity = 1, fill_color = inner_color)
rect_1d.replace(interval_1d)
rect_1d.stretch_to_fit_height(SMALL_BUFF)
left_dot = Dot(left_point, stroke_width = stroke_width, color = border_color)
right_dot = Dot(right_point, stroke_width = stroke_width, color = border_color)
endpoints_1d = VGroup(left_dot, right_dot)
full_1d = VGroup(interval_1d, endpoints_1d)
full_1d = VGroup(rect_1d, endpoints_1d)
self.play(ShowCreation(full_1d))
self.wait()
# TODO: Can polish the morphing above; have dots become left and right sides, and
# only then fill in the top and bottom
num_plane = NumberPlane()
random_points = [UP + LEFT, UP + RIGHT, DOWN + RIGHT, DOWN + LEFT]
@@ -790,20 +767,78 @@ class NumberLineScene(Scene):
self.wait()
class Initial2dFuncScene(Scene):
initial_2d_func = point_func_from_complex_func(lambda c : np.exp(c))
class Initial2dFuncSceneMorphing(Scene):
CONFIG = {
"num_needed_anchor_points" : 10,
"func" : initial_2d_func,
}
def setup(self):
split_line = DashedLine(SPACE_HEIGHT * UP, SPACE_HEIGHT * DOWN)
self.num_plane = NumberPlane(x_radius = SPACE_WIDTH/2)
self.num_plane.to_edge(LEFT, buff = 0)
self.num_plane.prepare_for_nonlinear_transform()
self.add(self.num_plane, split_line)
def squash_onto_left(self, object):
object.shift(SPACE_WIDTH/2 * LEFT)
def squash_onto_right(self, object):
object.shift(SPACE_WIDTH/2 * RIGHT)
def obj_draw(self, input_object):
output_object = input_object.copy()
if input_object.get_num_anchor_points() < self.num_needed_anchor_points:
input_object.insert_n_anchor_points(self.num_needed_anchor_points)
output_object.apply_function(self.func)
self.squash_onto_left(input_object)
self.squash_onto_right(output_object)
self.play(
ShowCreation(input_object),
ShowCreation(output_object)
)
def construct(self):
right_plane = self.num_plane.copy()
right_plane.center()
right_plane.prepare_for_nonlinear_transform()
right_plane.apply_function(self.func)
right_plane.shift(SPACE_WIDTH/2 * RIGHT)
self.right_plane = right_plane
crappy_cropper = FullScreenFadeRectangle(fill_opacity = 1)
crappy_cropper.stretch_to_fit_width(SPACE_WIDTH)
crappy_cropper.to_edge(LEFT, buff = 0)
self.play(
ReplacementTransform(self.num_plane.copy(), right_plane),
FadeIn(crappy_cropper),
Animation(self.num_plane),
run_time = 3
)
points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP]
for i in range(len(points) - 1):
line = Line(points[i], points[i + 1], color = RED)
self.obj_draw(line)
# Alternative to the above, using MappingCameras, but no morphing animation
class Initial2dFuncSceneWithoutMorphing(Scene):
def setup(self):
left_camera = Camera(**self.camera_config)
right_camera = MappingCamera(
mapping_func = point_func_from_complex_func(lambda c : np.exp(c)),
mapping_func = initial_2d_func,
**self.camera_config)
split_screen_camera = SplitScreenCamera(left_camera, right_camera, **self.camera_config)
self.camera = split_screen_camera
def construct(self):
num_plane = NumberPlane()
num_plane.fade()
num_plane.prepare_for_nonlinear_transform()
#num_plane.fade()
self.add(num_plane)
points = [LEFT + DOWN, RIGHT + DOWN, LEFT + UP, RIGHT + UP]
for i in range(len(points) - 1):
line = Line(points[i], points[i + 1], color = RED)
@@ -814,28 +849,8 @@ class Initial2dFuncScene(Scene):
# TODO: Bunch of Pi walker scenes
# TODO: An odometer scene when introducing winding numbers
class SecondSqrtScene(FirstSqrtScene, ReconfigurableScene):
# TODO: Don't bother with ReconfigurableScene; just use new config from start
def setup(self):
FirstSqrtScene.setup(self)
ReconfigurableScene.setup(self)
def construct(self):
shiftVal = self.targetY
self.drawGraph()
newOrigin = self.coords_to_point(0, shiftVal)
self.transition_to_alt_config(
func = lambda x : x**2 - shiftVal,
targetY = 0,
graph_label = "y = x^2 - " + str(shiftVal),
y_min = self.y_min - shiftVal,
y_max = self.y_max - shiftVal,
show_target_line = False,
graph_origin = newOrigin)
self.solveEquation()
# (Just set up an OdometerScene with function matching the walking of the Pi
# creature from previous scene, then place it as a simultaneous inset with Premiere)
class LoopSplitScene(Scene):
@@ -963,12 +978,12 @@ class LoopSplitSceneMapped(LoopSplitScene):
# to illustrate relation between degree and large-scale winding number
class FundThmAlg(EquationSolver2d):
CONFIG = {
"func" : plane_poly_with_roots((1, 2), (-1, 3), (-1, 3)),
"func" : plane_poly_with_roots((1, 2), (-1, 2.5), (-1, 2.5)),
"num_iterations" : 1,
}
# TODO: Borsuk-Ulam visuals
# Note: May want to do an ordinary square scene, then mapping func it into a circle
# Note: May want to do an ordinary square scene, then MappingCamera it into a circle
# class BorsukUlamScene(PiWalker):
# 3-way scene of "Good enough"-illustrating odometers; to be composed in Premiere
@@ -1002,15 +1017,16 @@ class DiffOdometer(OdometerScene):
# TODOs, from easiest to hardest:
# Minor fiddling with little things in each animation; placements, colors, timing
# Minor fiddling with little things in each animation; placements, colors, timing, text
# Odometer/swinging arrows stuff
# Initial odometer scene (simple once previous Pi walker scene is decided upon)
# Writing new Pi creature walker scenes off of general template
# Writing new Pi walker scenes by parametrizing general template
# Split screen illustration of 2d function (before domain coloring)
# Generalizing Pi walker stuff to make bullets on pulsing lines change colors dynamically according to
# function traced out
# Generalizing Pi color walker stuff/making bullets on pulsing lines change colors dynamically according to function traced out
# Debugging Pi walker stuff added to EquationSolver2d
# ----
@@ -1022,4 +1038,6 @@ class DiffOdometer(OdometerScene):
# Domain coloring
# TODO: Ask about tracked mobject, which is probably very useful for our animations
# FIN

File diff suppressed because it is too large Load Diff

View File

@@ -4190,15 +4190,11 @@ class FourierEndScreen(PatreonEndScreen):
class Thumbnail(Scene):
def construct(self):
title = TextMobject("Fourier\\\\", "Visualized")
title[1].highlight(YELLOW)
title[1].set_stroke(RED, 1)
title.highlight(YELLOW)
title.set_stroke(RED, 1)
title.set_stroke(RED, 2)
title.scale(2.5)
title.add_background_rectangle()
# title.to_edge(UP)
# self.add(title)
def func(t):
return np.cos(2*TAU*t) + np.cos(3*TAU*t) + np.cos(5*t)
fourier = get_fourier_transform(func, -5, 5)
@@ -4206,36 +4202,40 @@ class Thumbnail(Scene):
graph = FunctionGraph(func, x_min = -5, x_max = 5)
graph.highlight(BLUE)
fourier_graph = FunctionGraph(fourier, x_min = 0, x_max = 6)
fourier_graph.highlight(RED_C)
fourier_graph.highlight(YELLOW)
for g in graph, fourier_graph:
g.stretch_to_fit_height(2)
g.stretch_to_fit_width(10)
g.set_stroke(width = 8)
arrow = Vector(
2.5*DOWN,
rectangular_stem_width = 0.2,
tip_length = 0.5,
color = WHITE
)
q_mark = TextMobject("?").scale(2)
q_mark.highlight(YELLOW)
q_mark.set_stroke(RED, 1)
arrows = VGroup(*it.chain(*zip(
[q_mark.copy() for x in range(5)],
[arrow.copy() for x in range(5)]
)))
# arrows.submobjects.pop()
# arrows.arrange_submobjects(RIGHT, buff = MED_LARGE_BUFF)
group = VGroup(graph, title, fourier_graph)
arrows = VGroup(*[arrow.copy() for x in range(2)])
arrows.arrange_submobjects(RIGHT, buff = 6*LARGE_BUFF)
# group = VGroup(graph, arrows, fourier_graph)
group.arrange_submobjects(DOWN)
# group.next_to(title, DOWN, MED_LARGE_BUFF)
arrows.move_to(title)
title.shift(MED_SMALL_BUFF*UP)
graph.shift(SMALL_BUFF*UP)
self.add(arrows, group)
pol_graphs = VGroup()
for f in np.linspace(1.98, 2.02, 7):
pol_graph = ParametricFunction(
lambda t : complex_to_R3(
(2+np.cos(2*TAU*t)+np.cos(3*TAU*t))*np.exp(-complex(0, TAU*f*t))
),
t_min = -5,
t_max = 5,
num_graph_points = 200,
)
pol_graph.match_color(graph)
pol_graph.scale_to_fit_height(2)
pol_graphs.add(pol_graph)
pol_graphs.arrange_submobjects(RIGHT, buff = LARGE_BUFF)
pol_graphs.gradient_highlight(BLUE_C, YELLOW)
pol_graphs.match_width(graph)
pol_graphs.set_stroke(width = 2)
self.clear()
title.center().to_edge(UP)
pol_graphs.scale_to_fit_width(2*SPACE_WIDTH - 1)
pol_graphs.center()
title.move_to(pol_graphs)
title.shift(SMALL_BUFF*LEFT)
graph.next_to(title, UP)
fourier_graph.next_to(title, DOWN)
self.add(pol_graphs, title, graph, fourier_graph)

View File

@@ -408,9 +408,8 @@ class Succession(Animation):
run_time = kwargs.pop("run_time")
else:
run_time = sum(self.run_times)
self.num_anims = len(animations)
self.num_anims = len(animations) #TODO: If this is zero, some special handling below
self.animations = animations
self.last_index = 0
#Have to keep track of this run_time, because Scene.play
#might very well mess with it.
self.original_run_time = run_time
@@ -420,29 +419,52 @@ class Succession(Animation):
critical_times = np.concatenate(([0], np.cumsum(self.run_times)))
self.critical_alphas = map (lambda x : np.true_divide(x, run_time), critical_times)
mobject = Group(*[anim.mobject for anim in self.animations])
Animation.__init__(self, mobject, run_time = run_time, **kwargs)
# self.scene_mobjects_at_time[i] is the scene's mobjects at start of self.animations[i]
# self.scene_mobjects_at_time[i + 1] is the scene mobjects at end of self.animations[i]
self.scene_mobjects_at_time = [None for i in range(self.num_anims + 1)]
self.scene_mobjects_at_time[0] = Group()
for i in range(self.num_anims):
self.scene_mobjects_at_time[i + 1] = self.scene_mobjects_at_time[i].copy()
self.animations[i].clean_up(self.scene_mobjects_at_time[i + 1])
def rewind_to_start(self):
for anim in reversed(self.animations):
anim.update(0)
self.current_alpha = 0
self.current_anim_index = 0 #TODO: What if self.num_anims == 0?
self.mobject = Group()
self.jump_to_start_of_anim(0)
Animation.__init__(self, self.mobject, run_time = run_time, **kwargs)
def jump_to_start_of_anim(self, index):
self.current_anim_index = index
self.current_alpha = self.critical_alphas[index]
self.mobject.remove(*self.mobject.submobjects) # Should probably have a cleaner "remove_all" method...
self.mobject.add(self.animations[index].mobject)
for m in self.scene_mobjects_at_time[index].submobjects:
self.mobject.add(m)
self.animations[index].update(0)
def update_mobject(self, alpha):
self.rewind_to_start()
i = 0
while self.critical_alphas[i + 1] < alpha:
i = i + 1
# TODO: Special handling if alpha < 0 or alpha > 1, to use
# first or last sub-animation
for i in range(len(self.animations)):
sub_alpha = inverse_interpolate(
self.critical_alphas[i],
self.critical_alphas[i + 1],
alpha
)
if sub_alpha < 0:
return
# At this point, we should have self.critical_alphas[i] <= alpha <= self.critical_alphas[i +1]
sub_alpha = clamp(0, 1, sub_alpha) # Could possibly adopt a non-clamping convention here
self.animations[i].update(sub_alpha)
self.jump_to_start_of_anim(i)
sub_alpha = inverse_interpolate(
self.critical_alphas[i],
self.critical_alphas[i + 1],
alpha
)
self.animations[i].update(sub_alpha)
def clean_up(self, *args, **kwargs):
# We clean up as though we've played ALL animations, even if
# clean_up is called in middle of things
for anim in self.animations:
anim.clean_up(*args, **kwargs)
@@ -461,8 +483,6 @@ class AnimationGroup(Animation):
for anim in self.sub_anims:
anim.update(alpha)
def clean_up(self, surrounding_scene = None):
if surrounding_scene is not None:
surrounding_scene.mobjects.remove(self.everything)
def clean_up(self, *args, **kwargs):
for anim in self.sub_anims:
anim.clean_up(surrounding_scene = surrounding_scene)
anim.clean_up(*args, **kwargs)

288
ben_playground.py Normal file
View File

@@ -0,0 +1,288 @@
#!/usr/bin/env python
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 mobject.point_cloud_mobject import PointCloudDot
from animation.animation import Animation
from animation.transform import *
from animation.simple_animations import *
from animation.continual_animation import *
from animation.playground import *
from topics.geometry import *
from topics.characters import *
from topics.functions import *
from topics.number_line import *
from topics.combinatorics import *
from scene import Scene
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from mobject.vectorized_mobject import *
## To watch one of these scenes, run the following:
## python extract_scene.py -p file_name <SceneName>
LIGHT_COLOR = YELLOW
DEGREES = 360/TAU
SWITCH_ON_RUN_TIME = 1.5
class AmbientLight(VMobject):
# Parameters are:
# * a source point
# * an opacity function
# * a light color
# * a max opacity
# * a radius (larger than the opacity's dropoff length)
# * the number of subdivisions (levels, annuli)
CONFIG = {
"source_point" : ORIGIN,
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
"color" : LIGHT_COLOR,
"max_opacity" : 1.0,
"num_levels" : 10,
"radius" : 5.0
}
def generate_points(self):
# in theory, this method is only called once, right?
# so removing submobs shd not be necessary
for submob in self.submobjects:
self.remove(submob)
# create annuli
dr = self.radius / self.num_levels
for r in np.arange(0, self.radius, dr):
alpha = self.max_opacity * self.opacity_function(r)
annulus = Annulus(
inner_radius = r,
outer_radius = r + dr,
color = self.color,
fill_opacity = alpha
)
annulus.move_arc_center_to(self.source_point)
self.add(annulus)
def move_source_to(self,point):
self.shift(point - self.source_point)
self.source_point = np.array(point)
# for submob in self.submobjects:
# if type(submob) == Annulus:
# submob.shift(self.source_point - submob.get_center())
def dimming(self,new_alpha):
old_alpha = self.max_opacity
self.max_opacity = new_alpha
for submob in self.submobjects:
old_submob_alpha = submob.fill_opacity
new_submob_alpha = old_submob_alpha * new_alpha/old_alpha
submob.set_fill(opacity = new_submob_alpha)
class Spotlight(VMobject):
CONFIG = {
"source_point" : ORIGIN,
"opacity_function" : lambda r : 1.0/(r+1.0)**2,
"color" : LIGHT_COLOR,
"max_opacity" : 1.0,
"num_levels" : 10,
"radius" : 5.0,
"screen" : None,
"shadow" : VMobject(fill_color = RED, stroke_width = 0, fill_opacity = 1.0)
}
def track_screen(self):
self.generate_points()
def generate_points(self):
for submob in self.submobjects:
self.remove(submob)
if self.screen != None:
# look for the screen and create annular sectors
lower_angle, upper_angle = self.viewing_angles(self.screen)
dr = self.radius / self.num_levels
for r in np.arange(0, self.radius, dr):
alpha = self.max_opacity * self.opacity_function(r)
annular_sector = AnnularSector(
inner_radius = r,
outer_radius = r + dr,
color = self.color,
fill_opacity = alpha,
start_angle = lower_angle,
angle = upper_angle - lower_angle
)
annular_sector.move_arc_center_to(self.source_point)
self.add(annular_sector)
self.update_shadow(point = self.source_point)
self.add(self.shadow)
def viewing_angle_of_point(self,point):
distance_vector = point - self.source_point
angle = angle_of_vector(distance_vector)
return angle
def viewing_angles(self,screen):
viewing_angles = np.array(map(self.viewing_angle_of_point,
screen.get_anchors()))
lower_angle = upper_angle = 0
if len(viewing_angles) != 0:
lower_angle = np.min(viewing_angles)
upper_angle = np.max(viewing_angles)
return lower_angle, upper_angle
def move_source_to(self,point):
self.source_point = np.array(point)
self.recalculate_sectors(point = point, screen = self.screen)
self.update_shadow(point = point)
def recalculate_sectors(self, point = ORIGIN, screen = None):
if screen == None:
return
for submob in self.submobject_family():
if type(submob) == AnnularSector:
lower_angle, upper_angle = self.viewing_angles(screen)
new_submob = AnnularSector(
start_angle = lower_angle,
angle = upper_angle - lower_angle,
inner_radius = submob.inner_radius,
outer_radius = submob.outer_radius
)
new_submob.move_arc_center_to(point)
submob.points = new_submob.points
def update_shadow(self,point = ORIGIN):
print "updating shadow"
use_point = point #self.source_point
self.shadow.points = self.screen.points
ray1 = self.screen.points[0] - use_point
ray2 = self.screen.points[-1] - use_point
ray1 = ray1/np.linalg.norm(ray1) * 100
ray1 = rotate_vector(ray1,-TAU/16)
ray2 = ray2/np.linalg.norm(ray2) * 100
ray2 = rotate_vector(ray2,TAU/16)
outpoint1 = self.screen.points[0] + ray1
outpoint2 = self.screen.points[-1] + ray2
self.shadow.add_control_points([outpoint2,outpoint1,self.screen.points[0]])
self.shadow.mark_paths_closed = True
def dimming(self,new_alpha):
old_alpha = self.max_opacity
self.max_opacity = new_alpha
for submob in self.submobjects:
if type(submob) != AnnularSector:
# it's the shadow, don't dim it
continue
old_submob_alpha = submob.fill_opacity
new_submob_alpha = old_submob_alpha * new_alpha/old_alpha
submob.set_fill(opacity = new_submob_alpha)
class SwitchOn(LaggedStart):
CONFIG = {
"lag_ratio": 0.2,
"run_time": SWITCH_ON_RUN_TIME
}
def __init__(self, light, **kwargs):
if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight):
raise Exception("Only LightCones and Candles can be switched on")
LaggedStart.__init__(self,
FadeIn, light, **kwargs)
class SwitchOff(LaggedStart):
CONFIG = {
"lag_ratio": 0.2,
"run_time": SWITCH_ON_RUN_TIME
}
def __init__(self, light, **kwargs):
if not isinstance(light,AmbientLight) and not isinstance(light,Spotlight):
raise Exception("Only LightCones and Candles can be switched on")
light.submobjects = light.submobjects[::-1]
LaggedStart.__init__(self,
FadeOut, light, **kwargs)
light.submobjects = light.submobjects[::-1]
class ScreenTracker(ContinualAnimation):
def __init__(self, mobject, **kwargs):
ContinualAnimation.__init__(self, mobject, **kwargs)
def update_mobject(self, dt):
self.mobject.recalculate_sectors(
point = self.mobject.source_point,
screen = self.mobject.screen)
self.mobject.update_shadow(self.mobject.source_point)
class IntroScene(Scene):
def construct(self):
screen = Line([2,-2,0],[1,2,0]).shift([1,0,0])
self.add(screen)
ambient_light = AmbientLight(
source_point = np.array([-1,1,0]),
max_opacity = 1.0,
opacity_function = lambda r: 1.0/(r/2+1)**2,
num_levels = 4,
)
spotlight = Spotlight(
source_point = np.array([-1,1,0]),
max_opacity = 1.0,
opacity_function = lambda r: 1.0/(r/2+1)**2,
num_levels = 4,
screen = screen,
)
self.add(spotlight)
screen_updater = ScreenTracker(spotlight)
#self.add(ca)
#self.play(SwitchOn(ambient_light))
#self.play(ApplyMethod(ambient_light.move_source_to,[-3,1,0]))
#self.play(SwitchOn(spotlight))
self.add(screen_updater)
self.play(ApplyMethod(spotlight.screen.rotate,TAU/8))
self.remove(screen_updater)
self.play(ApplyMethod(spotlight.move_source_to,[-3,-1,0]))
self.add(screen_updater)
spotlight.source_point = [-3,-1,0]
self.play(ApplyMethod(spotlight.dimming,0.2))
#self.play(ApplyMethod(spotlight.move_source_to,[-4,0,0]))
#self.wait()

View File

@@ -438,11 +438,6 @@ class MappingCamera(Camera):
excluded_mobjects = None,
)
# TODO: Put this in different utility/helpers file? Convenient for me (Sridhar); I like it.
class DictAsObject(object):
def __init__(self, dict):
self.__dict__ = dict
# Note: This allows layering of multiple cameras onto the same portion of the pixel array,
# the later cameras overwriting the former
#

1
container/__init__.py Normal file
View File

@@ -0,0 +1 @@
from container import *

21
container/container.py Normal file
View File

@@ -0,0 +1,21 @@
from helpers import *
# Currently, this is only used by both Scene and MOBject.
# Still, we abstract its functionality here, albeit purely nominally.
# All actual implementation has to be handled by derived classes for now.
#
# Note that although the prototypical instances add and remove MObjects,
# there is also the possibility to add ContinualAnimations to Scenes. Thus,
# in the Container class in general, we do not make any presumptions about
# what types of objects may be added; this is again dependent on the specific
# derived instance.
class Container(object):
def __init__(self, *submobjects, **kwargs):
digest_config(self, kwargs)
def add(self, *items):
raise Exception("Container.add is not implemented; it is up to derived classes to implement")
def remove(self, *items):
raise Exception("Container.remove is not implemented; it is up to derived classes to implement")

View File

@@ -629,6 +629,12 @@ def angle_of_vector(vector):
return 0
return np.angle(complex(*vector[:2]))
def concatenate_lists(*list_of_lists):
return [item for l in list_of_lists for item in l]
# Occasionally convenient in order to write dict.x instead of more laborious
# (and less in keeping with all other attr accesses) dict["x"]
class DictAsObject(object):
def __init__(self, dict):
self.__dict__ = dict

View File

@@ -7,10 +7,11 @@ from colour import Color
from helpers import *
from container import *
#TODO: Explain array_attrs
class Mobject(object):
class Mobject(Container):
"""
Mathematical Object
"""
@@ -22,7 +23,7 @@ class Mobject(object):
"target" : None,
}
def __init__(self, *submobjects, **kwargs):
digest_config(self, kwargs)
Container.__init__(self, *submobjects, **kwargs)
if not all(map(lambda m : isinstance(m, Mobject), submobjects)):
raise Exception("All submobjects must be of type Mobject")
self.submobjects = list(submobjects)

View File

@@ -353,7 +353,8 @@ class VMobject(Mobject):
for index in range(num_curves):
curr_bezier_points = self.points[3*index:3*index+4]
num_inter_curves = sum(index_allocation == index)
alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
alphas = np.linspace(0, 1, num_inter_curves+1)
# alphas = np.arange(0, num_inter_curves+1)/float(num_inter_curves)
for a, b in zip(alphas, alphas[1:]):
new_points = partial_bezier_points(
curr_bezier_points, a, b

View File

@@ -29,6 +29,12 @@ IMAGE_MAP_DATA_FILE = os.path.join(NN_DIRECTORY, "image_map")
# DEFAULT_LAYER_SIZES = [28**2, 80, 10]
DEFAULT_LAYER_SIZES = [28**2, 16, 16, 10]
try:
xrange # Python 2
except NameError:
xrange = range # Python 3
class Network(object):
def __init__(self, sizes, non_linearity = "sigmoid"):
"""The list ``sizes`` contains the number of neurons in the

View File

@@ -1,6 +1,10 @@
import numpy as np
from animation.transform import Transform
from animation.transform import ApplyMethod, Transform
from constants import RIGHT, SPACE_WIDTH, UP
from helpers import counterclockwise_path, straight_path
from point_cloud_mobject import Point
from scene import Scene
from topics.geometry import Line
from topics.number_line import NumberLine
class NumberLineScene(Scene):
@@ -23,7 +27,7 @@ class NumberLineScene(Scene):
number_at_center = number
)
new_displayed_numbers = new_number_line.default_numbers_to_display()
new_number_mobs = new_number_line.get_number_mobjects(*new_displayed_numbers)
new_number_mobs = new_number_line.get_number_mobjects(*new_displayed_numbers)
transforms = []
additional_mobjects = []
@@ -78,11 +82,3 @@ class NumberLineScene(Scene):
ApplyMethod(mob.shift, (num-1)*mob.get_center()[0]*RIGHT, **kwargs)
for mob in self.number_mobs
])

View File

@@ -20,8 +20,9 @@ from animation import Animation
from animation.animation import sync_animation_run_times_and_rate_funcs
from animation.transform import MoveToTarget
from animation.continual_animation import ContinualAnimation
from container import *
class Scene(object):
class Scene(Container):
CONFIG = {
"camera_class" : Camera,
"camera_config" : {},
@@ -41,7 +42,7 @@ class Scene(object):
"skip_to_animation_number" : None,
}
def __init__(self, **kwargs):
digest_config(self, kwargs)
Container.__init__(self, **kwargs) # Perhaps allow passing in a non-empty *mobjects parameter?
self.camera = self.camera_class(**self.camera_config)
self.mobjects = []
self.continual_animations = []

View File

@@ -125,6 +125,19 @@ class Dot(Circle):
self.shift(point)
self.init_colors()
class Ellipse(VMobject):
CONFIG = {
"width" : 2,
"height" : 1
}
def generate_points(self):
circle = Circle(radius = 1)
circle = circle.stretch_to_fit_width(self.width)
circle = circle.stretch_to_fit_height(self.height)
self.points = circle.points
class AnnularSector(VMobject):
CONFIG = {
"inner_radius" : 1,
@@ -174,6 +187,7 @@ class AnnularSector(VMobject):
self.shift(v)
return self
class Sector(AnnularSector):
CONFIG = {
"outer_radius" : 1,
@@ -200,10 +214,12 @@ class Annulus(Circle):
}
def generate_points(self):
self.points = []
self.radius = self.outer_radius
Circle.generate_points(self)
outer_circle = Circle(radius = self.outer_radius)
inner_circle = Circle(radius=self.inner_radius)
inner_circle.flip()
self.points = outer_circle.points
self.add_subpath(inner_circle.points)
class Line(VMobject):
@@ -228,6 +244,10 @@ class Line(VMobject):
])
self.account_for_buff()
def set_path_arc(self,new_value):
self.path_arc = new_value
self.generate_points()
def account_for_buff(self):
length = self.get_arc_length()
if length < 2*self.buff or self.buff == 0:
@@ -333,6 +353,17 @@ class Line(VMobject):
self.shift(new_start - self.get_start())
return self
def insert_n_anchor_points(self, n):
if not self.path_arc:
n_anchors = self.get_num_anchor_points()
new_num_points = 3*(n_anchors + n)+1
self.points = np.array([
self.point_from_proportion(alpha)
for alpha in np.linspace(0, 1, new_num_points)
])
else:
VMobject.insert_n_anchor_points(self, n)
class DashedLine(Line):
CONFIG = {
"dashed_segment_length" : 0.05

View File

@@ -12,7 +12,7 @@ class DecimalNumber(VMobject):
"num_decimal_points" : 2,
"digit_to_digit_buff" : 0.05,
"show_ellipsis" : False,
"unit" : None,
"unit" : None, #Aligned to bottom unless it starts with "^"
"include_background_rectangle" : False,
}
def __init__(self, number, **kwargs):
@@ -41,8 +41,17 @@ class DecimalNumber(VMobject):
if self.show_ellipsis:
self.add(TexMobject("\\dots"))
if self.unit is not None:
self.add(TexMobject(self.unit))
if num_string.startswith("-"):
minus = self.submobjects[0]
minus.next_to(
self.submobjects[1], LEFT,
buff = self.digit_to_digit_buff
)
if self.unit != None:
self.unit_sign = TexMobject(self.unit)
self.add(self.unit_sign)
self.arrange_submobjects(
buff = self.digit_to_digit_buff,
@@ -54,8 +63,8 @@ class DecimalNumber(VMobject):
for i, c in enumerate(num_string):
if c == "-" and len(num_string) > i+1:
self[i].align_to(self[i+1], alignment_vect = UP)
if self.unit == "\\circ":
self[-1].align_to(self, UP)
if self.unit and self.unit.startswith("^"):
self.unit_sign.align_to(self, UP)
#
if self.include_background_rectangle:
self.add_background_rectangle()
@@ -80,7 +89,7 @@ class ChangingDecimal(Animation):
"num_decimal_points" : None,
"show_ellipsis" : None,
"position_update_func" : None,
"tracked_mobject" : None
"tracked_mobject" : None,
}
def __init__(self, decimal_number_mobject, number_update_func, **kwargs):
digest_config(self, kwargs, locals())