3b1b-manim/mug.py

1308 lines
41 KiB
Python
Raw Normal View History

2017-12-21 13:43:27 -08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
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 animation.continual_animation 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 topics.probability import *
from topics.complex_numbers import *
from scene import Scene
from scene.reconfigurable_scene import ReconfigurableScene
from scene.zoomed_scene import *
from camera import Camera
from mobject.svg_mobject import *
from mobject.tex_mobject import *
from topics.graph_scene import *
from old_projects.efvgt import ConfettiSpiril
#revert_to_original_skipping_status
class HappyHolidays(TeacherStudentsScene):
def construct(self):
hats = VGroup(*map(
self.get_hat, self.pi_creatures
))
self.add(self.get_snowflakes())
self.change_student_modes(
*["hooray"]*3,
look_at_arg = SPACE_HEIGHT*UP,
added_anims = [self.teacher.change, "hooray"]
)
self.play(LaggedStart(
DrawBorderThenFill, hats
), Animation(self.pi_creatures))
self.change_student_modes(
"happy", "wave_2", "wave_1",
look_at_arg = SPACE_HEIGHT*UP,
)
self.look_at(self.teacher.get_corner(UP+LEFT))
self.dither(2)
self.play(self.teacher.change, "happy")
self.dither(2)
def get_hat(self, pi):
hat = SVGMobject(
file_name = "santa_hat",
height = 0.5*pi.get_height()
)
hat.rotate(-np.pi/12)
vect = RIGHT
if not pi.is_flipped():
hat.flip()
vect = LEFT
hat.set_fill(RED_D)
hat[0].remove(hat[0][1])
hat[0].set_fill("#EEE")
hat[2].set_fill(WHITE)
hat.add(hat[0])
hat.next_to(pi.body, UP, buff = SMALL_BUFF)
hat.shift(SMALL_BUFF*vect)
return hat
def get_snowflakes(self, n_flakes = 50):
snowflakes = VGroup(*[
SVGMobject(
file_name = "snowflake",
height = 0.5,
stroke_width = 0,
fill_opacity = 0.75,
fill_color = WHITE,
).rotate(np.pi/12, RIGHT)
for x in range(n_flakes)
])
def random_confetti_spiral(mob, **kwargs):
return ConfettiSpiril(
mob, x_start = 2*random.random()*SPACE_WIDTH - SPACE_WIDTH,
**kwargs
)
snowflake_spirils = LaggedStart(
random_confetti_spiral, snowflakes,
run_time = 10,
rate_func = lambda x : x,
)
return NormalAnimationAsContinualAnimation(snowflake_spirils)
class UtilitiesPuzzleScene(Scene):
CONFIG = {
"object_height" : 0.75,
"h_distance" : 2,
"v_distance" : 2,
"line_width" : 4,
}
def setup_configuration(self):
houses = VGroup()
for x in range(3):
house = SVGMobject(file_name = "house")
house.scale_to_fit_height(self.object_height)
house.set_fill(LIGHT_GREY)
house.move_to(x*self.h_distance*RIGHT)
houses.add(house)
houses.move_to(self.v_distance*UP/2)
utilities = VGroup(*[
self.get_utility(u, c).move_to(x*self.h_distance*RIGHT)
for x, u, c in zip(
it.count(),
["fire", "electricity", "water"],
[RED_D, YELLOW_C, BLUE_D]
)
])
utilities.move_to(self.v_distance*DOWN/2)
objects = VGroup(houses, utilities)
bounding_box = SurroundingRectangle(
objects,
buff = MED_LARGE_BUFF,
stroke_width = 0
)
objects.add(bounding_box)
self.add_foreground_mobjects(objects)
self.set_variables_as_attrs(
houses, utilities, objects, bounding_box
)
def get_utility(self, name, color):
circle = Circle(
fill_color = color,
fill_opacity = 1,
stroke_width = 0,
)
utility = SVGMobject(
file_name = name,
height = 0.65*circle.get_height(),
fill_color = WHITE,
)
if color == YELLOW:
utility.set_fill(DARK_GREY)
utility.move_to(circle)
circle.add(utility)
circle.scale_to_fit_height(self.object_height)
return circle
def get_line(
self, utility_index, house_index,
*midpoints,
**kwargs
):
prop = kwargs.pop("prop", 1.0)
utility = self.utilities[utility_index]
points = [utility.get_center()]
points += list(midpoints)
points += [self.houses[house_index].get_center()]
line = Line(
points[0], points[-1],
color = utility[0].get_color(),
stroke_width = self.line_width
)
line.set_points_smoothly(points)
line.pointwise_become_partial(line, 0, prop)
return line
def get_almost_solution_lines(self):
bb = self.bounding_box
return VGroup(
VGroup(
self.get_line(0, 0),
self.get_line(0, 1),
self.get_line(0, 2, bb.get_top()),
),
VGroup(
self.get_line(1, 0, bb.get_corner(DOWN+LEFT)),
self.get_line(1, 1),
self.get_line(1, 2, bb.get_corner(DOWN+RIGHT)),
),
VGroup(
self.get_line(2, 0, bb.get_top()),
self.get_line(2, 1),
self.get_line(2, 2),
),
)
def get_straight_lines(self):
return VGroup(*[
VGroup(*[self.get_line(i, j) for j in range(3)])
for i in range(3)
])
def get_no_crossing_words(self):
arrow = Vector(DOWN)
arrow.next_to(self.bounding_box.get_top(), UP, SMALL_BUFF)
words = TextMobject("No crossing!")
words.next_to(arrow, UP, buff = SMALL_BUFF)
result = VGroup(words, arrow)
result.highlight("RED")
return result
def get_region(self, *bounding_edges):
region = VMobject(mark_paths_closed = True)
for i, edge in enumerate(bounding_edges):
new_edge = edge.copy()
if i%2 == 1:
new_edge.points = new_edge.points[::-1]
region.append_vectorized_mobject(new_edge)
region.set_stroke(width = 0)
region.set_fill(WHITE, opacity = 1)
return region
def convert_objects_to_dots(self, run_time = 2):
group = VGroup(*it.chain(self.houses, self.utilities))
for mob in group:
mob.target = Dot(color = mob.get_color())
mob.target.scale(2)
mob.target.move_to(mob)
self.play(LaggedStart(MoveToTarget, group, run_time = run_time))
class AboutToyPuzzles(UtilitiesPuzzleScene, TeacherStudentsScene, ThreeDScene):
def construct(self):
self.remove(self.pi_creatures)
self.lay_out_puzzle()
self.point_to_abstractions()
self.show_this_video()
def lay_out_puzzle(self):
self.setup_configuration()
houses, utilities = self.houses, self.utilities
lines = VGroup(*it.chain(*self.get_almost_solution_lines()))
no_crossing_words = self.get_no_crossing_words()
self.remove(self.objects)
self.play(
ReplacementTransform(
VGroup(houses[1], houses[1]).copy().fade(1),
VGroup(houses[0], houses[2]),
rate_func = squish_rate_func(smooth, 0.35, 1),
run_time = 2,
),
FadeIn(houses[1]),
LaggedStart(DrawBorderThenFill, utilities, run_time = 2)
)
self.play(
LaggedStart(
ShowCreation, lines,
run_time = 3
),
Animation(self.objects)
)
self.play(
Write(no_crossing_words[0]),
GrowArrow(no_crossing_words[1]),
)
self.dither()
self.objects.add_to_back(lines, no_crossing_words)
def point_to_abstractions(self):
objects = self.objects
objects.generate_target()
objects.target.scale(0.5)
objects.target.move_to(
(SPACE_HEIGHT*DOWN + SPACE_WIDTH*LEFT)/2
)
eulers = TexMobject(*"V-E+F=2")
eulers.highlight_by_tex_to_color_map({
"V" : RED,
"E" : GREEN,
"F" : BLUE,
})
eulers.to_edge(UP, buff = 2)
cube = Cube()
cube.set_stroke(WHITE, 2)
cube.scale_to_fit_height(0.75)
cube.pose_at_angle()
cube.next_to(eulers, UP)
tda = TextMobject("Topological \\\\ Data Analysis")
tda.move_to(DOWN + 4*RIGHT)
arrow_from_eulers = Arrow(
eulers.get_bottom(), tda.get_corner(UP+LEFT),
color = WHITE
)
self.play(
objects.scale, 0.5,
objects.move_to, DOWN + 4*LEFT,
)
arrow_to_eulers = Arrow(
self.houses[2].get_corner(UP+LEFT),
eulers.get_bottom(),
color = WHITE
)
self.add(AmbientRotation(cube, axis = UP))
self.play(
GrowArrow(arrow_to_eulers),
Write(eulers),
FadeIn(cube)
)
self.dither(5)
self.play(
GrowArrow(arrow_from_eulers),
Write(tda)
)
self.dither(2)
self.set_variables_as_attrs(
eulers, cube, tda,
arrows = VGroup(arrow_to_eulers, arrow_from_eulers),
)
def show_this_video(self):
screen_rect = FullScreenFadeRectangle(
stroke_color = WHITE,
stroke_width = 2,
fill_opacity = 0,
)
everything = VGroup(
self.objects, self.arrows, self.eulers,
self.cube, self.tda,
screen_rect,
)
self.teacher.save_state()
self.teacher.fade(1)
self.play(
everything.scale, 0.5,
everything.next_to, self.teacher, UP+LEFT,
self.teacher.restore,
self.teacher.change, "raise_right_hand", UP+LEFT,
LaggedStart(FadeIn, self.students)
)
self.change_student_modes(
*["pondering"]*3, look_at_arg = everything
)
self.dither(5)
class ThisPuzzleIsHard(UtilitiesPuzzleScene, PiCreatureScene):
def construct(self):
self.introduce_components()
self.failed_attempts()
self.ask_meta_puzzle()
def introduce_components(self):
randy = self.pi_creature
try_it = TextMobject("Try it yourself!")
try_it.to_edge(UP)
self.setup_configuration()
houses, utilities = self.houses, self.utilities
self.remove(self.objects)
house = houses[0]
puzzle_words = TextMobject("""
Puzzle: Connect each house to \\\\
each utility without crossing lines.
""")
# puzzle_words.next_to(self.objects, UP)
puzzle_words.to_edge(UP)
self.add(try_it)
self.play(Animation(try_it))
self.play(
LaggedStart(DrawBorderThenFill, houses),
LaggedStart(GrowFromCenter, utilities),
try_it.scale_to_fit_width, house.get_width(),
try_it.fade, 1,
try_it.move_to, house,
self.pi_creature.change, "happy",
)
self.play(LaggedStart(FadeIn, puzzle_words))
self.add_foreground_mobjects(self.objects)
self.set_variables_as_attrs(puzzle_words)
def failed_attempts(self):
bb = self.bounding_box
utilities = self.utilities
houses = self.houses
randy = self.pi_creature
line_sets = [
[
self.get_line(0, 0),
self.get_line(1, 1),
self.get_line(2, 2),
self.get_line(0, 1),
self.get_line(2, 1),
self.get_line(0, 2, bb.get_corner(UP+LEFT)),
self.get_line(
2, 0, bb.get_corner(DOWN+LEFT),
prop = 0.85,
),
self.get_line(
2, 0, bb.get_corner(UP+RIGHT), bb.get_top(),
prop = 0.73,
),
],
[
self.get_line(0, 0),
self.get_line(1, 1),
self.get_line(2, 2),
self.get_line(0, 1),
self.get_line(1, 2),
self.get_line(
2, 0,
bb.get_bottom(),
bb.get_corner(DOWN+LEFT)
),
self.get_line(0, 2, bb.get_top()),
self.get_line(
2, 1,
utilities[1].get_bottom() + MED_SMALL_BUFF*DOWN,
utilities[1].get_left() + MED_SMALL_BUFF*LEFT,
),
self.get_line(
1, 0, houses[2].get_corner(UP+LEFT) + MED_LARGE_BUFF*LEFT,
prop = 0.49,
),
self.get_line(
1, 2, bb.get_right(),
prop = 0.25,
),
],
[
self.get_line(0, 0),
self.get_line(0, 1),
self.get_line(0, 2, bb.get_top()),
self.get_line(1, 0, bb.get_corner(DOWN+LEFT)),
self.get_line(1, 1),
self.get_line(1, 2, bb.get_corner(DOWN+RIGHT)),
self.get_line(2, 1),
self.get_line(2, 2),
self.get_line(2, 0, bb.get_top(), prop = 0.45),
self.get_line(2, 0, prop = 0.45),
],
]
modes = ["confused", "sassy", "angry"]
for line_set, mode in zip(line_sets, modes):
good_lines = VGroup(*line_set[:-2])
bad_lines = line_set[-2:]
self.play(LaggedStart(ShowCreation, good_lines))
for bl in bad_lines:
self.play(
ShowCreation(
bl,
rate_func = bezier([0, 0, 0, 1, 1, 1, 1, 1, 0.3, 1, 1]),
run_time = 1.5
),
randy.change, mode,
)
self.play(ShowCreation(
bl, rate_func = lambda t : smooth(1-t),
))
self.remove(bl)
self.play(LaggedStart(FadeOut, good_lines))
def ask_meta_puzzle(self):
randy = self.pi_creature
group = VGroup(
self.puzzle_words,
self.objects,
)
rect = SurroundingRectangle(group, color = BLUE, buff = MED_LARGE_BUFF)
group.add(rect)
group.generate_target()
group.target.scale(0.75)
group.target.shift(DOWN)
group[-1].set_stroke(width = 0)
meta_puzzle_words = TextMobject("""
Meta-puzzle: Prove that this\\\\
is impossible.
""")
meta_puzzle_words.next_to(group.target, UP)
meta_puzzle_words.highlight(BLUE)
self.play(
MoveToTarget(group),
randy.change, "pondering"
)
self.play(Write(meta_puzzle_words))
self.play(randy.change, "confused")
straight_lines = self.get_straight_lines()
almost_solution_lines = self.get_almost_solution_lines()
self.play(LaggedStart(
ShowCreation, straight_lines,
run_time = 2,
lag_ratio = 0.8
), Blink(randy))
self.play(Transform(
straight_lines, almost_solution_lines,
run_time = 3,
submobject_mode = "lagged_start"
))
self.dither()
######
def create_pi_creature(self):
return Randolph().to_corner(DOWN+LEFT)
class IntroduceGraph(PiCreatureScene):
def construct(self):
pi_creatures = self.pi_creatures
dots = VGroup(*[
Dot(color = pi.get_color()).scale(2).move_to(pi)
for pi in pi_creatures
])
lines = VGroup(*[
Line(pi1.get_center(), pi2.get_center())
for pi1, pi2 in it.combinations(pi_creatures, 2)
])
graph_word = TextMobject("``", "", "Graph", "''", arg_separator = "")
graph_word.to_edge(UP)
planar_graph_word = TextMobject("``", "Planar", " graph", "''", arg_separator = "")
planar_graph_word.move_to(graph_word)
vertices_word = TextMobject("Vertices")
vertices_word.to_edge(RIGHT, buff = LARGE_BUFF)
vertices_word.highlight(YELLOW)
vertex_arrows = VGroup(*[
Arrow(vertices_word.get_left(), dot)
for dot in dots[-2:]
])
edge_word = TextMobject("Edge")
edge_word.next_to(lines, LEFT, LARGE_BUFF)
edge_arrow = Arrow(
edge_word, lines, buff = SMALL_BUFF,
color = WHITE
)
self.play(LaggedStart(GrowFromCenter, pi_creatures))
self.play(
LaggedStart(ShowCreation, lines),
LaggedStart(
ApplyMethod, pi_creatures,
lambda pi : (pi.change, "pondering", lines)
)
)
self.play(Write(graph_word))
self.play(ReplacementTransform(
pi_creatures, dots,
run_time = 2,
submobject_mode = "lagged_start"
))
self.add_foreground_mobjects(dots)
self.play(
FadeIn(vertex_arrows),
FadeIn(vertices_word),
)
self.dither()
self.play(LaggedStart(
ApplyMethod, lines,
lambda l : (l.rotate_in_place, np.pi/12),
rate_func = wiggle
))
self.play(
FadeIn(edge_word),
GrowArrow(edge_arrow),
)
self.dither(2)
line = lines[2]
self.play(
line.set_points_smoothly, [
line.get_start(),
dots.get_left() + MED_SMALL_BUFF*LEFT,
dots.get_corner(DOWN+LEFT) + MED_SMALL_BUFF*(DOWN+LEFT),
dots.get_bottom() + MED_SMALL_BUFF*DOWN,
line.get_end(),
],
VGroup(edge_word, edge_arrow).shift, MED_LARGE_BUFF*LEFT,
)
self.dither()
self.play(ReplacementTransform(graph_word, planar_graph_word))
self.dither(2)
###
def create_pi_creatures(self):
pis = VGroup(
PiCreature(color = BLUE_D),
PiCreature(color = GREY_BROWN),
PiCreature(color = BLUE_C).flip(),
PiCreature(color = BLUE_E).flip(),
)
pis.scale(0.5)
pis.arrange_submobjects_in_grid(buff = 2)
return pis
class IsK33Planar(UtilitiesPuzzleScene):
def construct(self):
self.setup_configuration()
self.objects.shift(MED_LARGE_BUFF*DOWN)
straight_lines = self.get_straight_lines()
almost_solution_lines = self.get_almost_solution_lines()
question = TextMobject("Is", "this graph", "planar?")
question.highlight_by_tex("this graph", YELLOW)
question.to_edge(UP)
brace = Brace(question.get_part_by_tex("graph"), DOWN, buff = SMALL_BUFF)
fancy_name = brace.get_text(
"``Complete bipartite graph $K_{3, 3}$''",
buff = SMALL_BUFF
)
fancy_name.highlight(YELLOW)
self.add(question)
self.convert_objects_to_dots()
self.play(LaggedStart(ShowCreation, straight_lines))
self.play(
GrowFromCenter(brace),
LaggedStart(FadeIn, fancy_name),
)
self.play(ReplacementTransform(
straight_lines, almost_solution_lines,
run_time = 3,
submobject_mode = "lagged_start"
))
self.dither(2)
class TwoKindsOfViewers(PiCreatureScene, UtilitiesPuzzleScene):
def construct(self):
self.setup_configuration()
objects = self.objects
objects.remove(self.bounding_box)
lines = self.get_straight_lines()
objects.add_to_back(lines)
objects.scale(0.75)
objects.next_to(ORIGIN, RIGHT, LARGE_BUFF)
self.remove(objects)
pi1, pi2 = self.pi_creatures
words = TextMobject(
"$(V-E+F)$", "kinds of viewers"
)
words.to_edge(UP)
eulers = words.get_part_by_tex("V-E+F")
eulers.highlight(GREEN)
non_eulers = VGroup(*filter(lambda m : m is not eulers, words))
self.add(words)
self.dither()
self.play(
pi1.shift, 2*LEFT,
pi2.shift, 2*RIGHT,
)
know_eulers = TextMobject("Know about \\\\ Euler's formula")
know_eulers.next_to(pi1, DOWN)
know_eulers.highlight(GREEN)
dont = TextMobject("Don't")
dont.next_to(pi2, DOWN)
dont.highlight(RED)
self.play(
FadeIn(know_eulers),
pi1.change, "hooray",
)
self.play(
FadeIn(dont),
pi2.change, "maybe", eulers,
)
self.dither()
self.pi_creature_thinks(
pi1, "",
bubble_kwargs = {"width" : 3, "height" : 2},
target_mode = "thinking"
)
self.play(pi2.change, "confused", eulers)
self.dither()
### Out of thin air
self.play(*map(FadeOut, [
non_eulers, pi1, pi2, pi1.bubble,
know_eulers, dont
]))
self.play(eulers.next_to, ORIGIN, LEFT, LARGE_BUFF)
arrow = Arrow(eulers, objects, color = WHITE)
self.play(
GrowArrow(arrow),
LaggedStart(DrawBorderThenFill, VGroup(*it.chain(*objects)))
)
self.dither()
self.play(
objects.move_to, eulers, RIGHT,
eulers.move_to, objects, LEFT,
path_arc = np.pi,
run_time = 1.5,
)
self.dither(2)
###
def create_pi_creatures(self):
group = VGroup(Randolph(color = BLUE_C), Randolph())
group.scale(0.7)
group.shift(MED_LARGE_BUFF*DOWN)
return group
class IntroduceRegions(UtilitiesPuzzleScene):
def construct(self):
self.setup_configuration()
houses, utilities = self.houses, self.utilities
objects = self.objects
lines, line_groups, regions = self.get_lines_line_groups_and_regions()
back_region = regions[0]
front_regions = VGroup(*regions[1:])
self.convert_objects_to_dots(run_time = 0)
self.play(LaggedStart(
ShowCreation, lines,
run_time = 3,
))
self.add_foreground_mobjects(lines, objects)
self.dither()
for region in front_regions:
self.play(FadeIn(region))
self.play(
FadeIn(back_region),
Animation(front_regions),
)
self.dither()
self.play(FadeOut(regions))
##Paint bucket
paint_bucket = SVGMobject(
file_name = "paint_bucket",
height = 0.5,
)
paint_bucket.flip()
paint_bucket.move_to(8*LEFT + 5*UP)
def click(region):
self.play(
UpdateFromAlphaFunc(
region,
lambda m, a : m.set_fill(opacity = int(2*a)),
),
ApplyMethod(
paint_bucket.scale_in_place, 0.5,
rate_func = there_and_back,
),
run_time = 0.25,
)
self.play(
paint_bucket.next_to, utilities, DOWN+LEFT, SMALL_BUFF
)
click(regions[1])
self.play(paint_bucket.next_to, utilities[1], UP+RIGHT, SMALL_BUFF)
click(regions[2])
self.play(paint_bucket.next_to, houses[1], RIGHT)
click(regions[3])
self.play(paint_bucket.move_to, 4*LEFT + 2*UP)
self.add_foreground_mobjects(front_regions, lines, objects)
click(back_region)
self.remove_foreground_mobjects(front_regions)
self.dither()
self.play(
FadeOut(back_region),
FadeOut(front_regions[0]),
FadeOut(paint_bucket),
*map(Animation, front_regions[1:])
)
#Line tries to escape
point_sets = [
[
VGroup(*houses[1:]).get_center(),
houses[2].get_top() + MED_SMALL_BUFF*UP,
],
[
houses[1].get_top() + SMALL_BUFF*UP,
utilities[0].get_center(),
],
[VGroup(houses[1], utilities[1]).get_center()],
[
utilities[2].get_center() + 0.75*(DOWN+RIGHT)
],
]
escape_lines = VGroup(*[
Line(LEFT, RIGHT).set_points_smoothly(
[utilities[2].get_center()] + point_set
)
for point_set in point_sets
])
self.dither()
for line in escape_lines:
self.play(ShowCreation(line,
rate_func = lambda t : 0.8*smooth(t)
))
self.play(ShowCreation(line,
rate_func = lambda t : smooth(1 - t)
))
def get_lines_line_groups_and_regions(self):
lines = self.get_almost_solution_lines()
flat_lines = VGroup(*it.chain(*lines))
flat_lines.remove(lines[2][0])
line_groups = [
VGroup(*[lines[i][j] for i, j in ij_set])
for ij_set in [
[(0, 0), (1, 0), (1, 1), (0, 1)],
[(1, 1), (2, 1), (2, 2), (1, 2)],
[(0, 2), (2, 2), (2, 1), (0, 1)],
[(0, 0), (1, 0), (1, 2), (0, 2)],
]
]
regions = VGroup(*[
self.get_region(*line_group)
for line_group in line_groups
])
back_region = FullScreenFadeRectangle(fill_opacity = 1 )
regions.submobjects.pop()
regions.submobjects.insert(0, back_region)
front_regions = VGroup(*regions[1:])
back_region.highlight(BLUE_E)
front_regions.gradient_highlight(GREEN_E, MAROON_E)
return flat_lines, line_groups, regions
class FromLastVideo(Scene):
def construct(self):
title = TextMobject("From last video")
title.to_edge(UP)
rect = ScreenRectangle(height = 6)
rect.next_to(title, DOWN)
self.add(title)
self.play(ShowCreation(rect))
self.dither(2)
class AskAboutRegions(IntroduceRegions):
def construct(self):
self.setup_configuration()
houses, utilities = self.houses, self.utilities
self.convert_objects_to_dots(run_time = 0)
objects = self.objects
lines, line_groups, regions = self.get_lines_line_groups_and_regions()
back_region = regions[0]
front_regions = VGroup(*regions[1:])
missing_lines = VGroup(
self.get_line(2, 0, self.objects.get_top()),
self.get_line(
2, 0,
self.objects.get_bottom() + DOWN,
self.objects.get_corner(DOWN+LEFT) + DOWN+LEFT,
)
)
missing_lines.set_stroke(width = 5)
front_regions.save_state()
front_regions.generate_target()
front_regions.target.scale(0.5)
front_regions.target.arrange_submobjects(RIGHT, buff = LARGE_BUFF)
front_regions.target.to_edge(UP)
self.add(front_regions)
self.add_foreground_mobjects(lines, objects)
self.dither()
self.play(MoveToTarget(front_regions))
self.play(LaggedStart(
ApplyMethod, front_regions,
lambda m : (m.rotate_in_place, np.pi/12),
rate_func = wiggle,
lag_ratio = 0.75,
run_time = 1
))
self.play(front_regions.restore)
self.dither()
#Show missing lines
for line in missing_lines:
self.play(ShowCreation(
line,
rate_func = there_and_back,
run_time = 2,
))
#Count regions
count = TexMobject("1")
count.scale(1.5)
count.to_edge(UP)
self.play(
FadeIn(back_region),
FadeIn(count),
Animation(front_regions)
)
last_region = None
for n, region in zip(it.count(2), front_regions):
new_count = TexMobject(str(n))
new_count.replace(count, dim_to_match = 1)
self.remove(count)
self.add(new_count)
count = new_count
region.save_state()
anims = [ApplyMethod(region.highlight, YELLOW)]
if last_region:
anims.append(ApplyMethod(last_region.restore))
anims.append(Animation(front_regions))
self.play(*anims, run_time = 0.25)
self.dither(0.5)
last_region = region
self.play(last_region.restore)
self.dither()
self.play(FadeOut(count))
#Count edges per region
fade_rect = FullScreenFadeRectangle(opacity = 0.8)
line_group = line_groups[0].copy()
region = front_regions[0].copy()
self.foreground_mobjects = []
def show_lines(line_group):
lg_copy = line_group.copy()
lg_copy.set_stroke(WHITE, 6)
self.play(LaggedStart(
FadeIn, lg_copy,
run_time = 3,
rate_func = there_and_back,
lag_ratio = 0.4,
remover = True,
))
self.play(
FadeIn(fade_rect),
Animation(region),
Animation(line_group),
)
show_lines(line_group)
last_line_group = line_group
last_region = region
for i in range(1, 3):
line_group = line_groups[i].copy()
region = front_regions[i].copy()
self.play(
FadeOut(last_region),
FadeOut(last_line_group),
FadeIn(region),
FadeIn(line_group),
)
show_lines(line_group)
last_line_group = line_group
last_region = region
self.play(
FadeOut(fade_rect),
FadeOut(last_region),
FadeOut(last_line_group),
)
self.dither()
class NewRegionClosedOnlyForNodesWithEdges(UtilitiesPuzzleScene):
def construct(self):
self.setup_configuration()
self.convert_objects_to_dots(run_time = 0)
objects = self.objects
houses, utilities = self.houses, self.utilities
bb = self.bounding_box
lines = VGroup(
self.get_line(2, 1),
self.get_line(0, 1),
self.get_line(0, 2,
bb.get_corner(UP+LEFT),
bb.get_top() + MED_LARGE_BUFF*UP,
),
self.get_line(2, 2),
)
lit_line = lines[2].copy()
lit_line.points = lit_line.points[::-1]
lit_line.set_stroke(WHITE, 5)
region = self.get_region(*lines)
region.set_fill(MAROON_E)
arrow = Vector(DOWN+LEFT, color = WHITE)
arrow.next_to(houses[2], UP+RIGHT, buff = SMALL_BUFF)
words = TextMobject("Already has \\\\ an edge")
words.next_to(arrow.get_start(), UP, SMALL_BUFF)
for line in lines[:-1]:
self.play(ShowCreation(line))
lines[-1].pointwise_become_partial(lines[-1], 0, 0.92)
lines[-1].save_state()
self.dither()
self.play(ShowCreation(lines[-1]))
self.add(region, lines, objects)
self.dither()
self.remove(region)
self.play(ShowCreation(lines[-1],
rate_func = lambda t : smooth(1-2*t*(1-t))
))
self.add(region, lines, objects)
self.dither()
self.remove(region)
self.play(
ShowCreation(lines[-1],
rate_func = lambda t : smooth(1-0.5*t)
),
FadeIn(words),
GrowArrow(arrow),
)
for x in range(2):
self.play(ShowCreationThenDestruction(lit_line))
self.play(lines[-1].restore)
self.add(region, lines, objects)
self.dither(2)
class LightUpNodes(IntroduceRegions):
def construct(self):
self.setup_configuration()
self.setup_regions()
self.setup_counters()
self.describe_one_as_lit()
self.show_rule_for_lighting()
def setup_configuration(self):
IntroduceRegions.setup_configuration(self)
self.convert_objects_to_dots(run_time = 1)
self.objects.shift(DOWN)
def setup_regions(self):
lines, line_groups, regions = self.get_lines_line_groups_and_regions()
back_region = regions[0]
front_regions = VGroup(*regions[1:])
self.set_variables_as_attrs(
lines, line_groups, regions,
back_region, front_regions,
)
def setup_counters(self):
titles = [
TextMobject("\\# Lit vertices"),
TextMobject("\\# Edges"),
TextMobject("\\# Regions"),
]
for title, vect in zip(titles, [LEFT, ORIGIN, RIGHT]):
title.shift(SPACE_WIDTH*vect/2)
title.to_edge(UP)
underline = Line(LEFT, RIGHT)
underline.stretch_to_fit_width(title.get_width())
underline.next_to(title, DOWN, SMALL_BUFF)
title.add(underline)
self.add(title)
self.v_count, self.e_count, self.f_count = self.counts = map(
Integer, [1, 0, 1]
)
for count, title in zip(self.counts, titles):
count.next_to(title, DOWN)
self.add(count)
def describe_one_as_lit(self):
houses, utilities = self.houses, self.utilities
vertices = VGroup(*it.chain(houses, utilities))
dim_arrows = VGroup()
for vertex in vertices:
arrow = Vector(0.5*(DOWN+LEFT), color = WHITE)
arrow.next_to(vertex, UP+RIGHT, SMALL_BUFF)
vertex.arrow = arrow
dim_arrows.add(arrow)
lit_vertex = utilities[0]
lit_arrow = lit_vertex.arrow
lit_arrow.rotate(np.pi/2, about_point = lit_vertex.get_center())
dim_arrows.remove(lit_arrow)
lit_word = TextMobject("Lit up")
lit_word.next_to(lit_arrow.get_start(), UP, SMALL_BUFF)
dim_word = TextMobject("Dim")
dim_word.next_to(dim_arrows[1].get_start(), UP, MED_LARGE_BUFF)
dot = Dot().move_to(self.v_count)
self.play(
vertices.set_fill, None, 0,
vertices.set_stroke, None, 1,
)
self.play(ReplacementTransform(dot, lit_vertex))
self.play(
FadeIn(lit_word),
GrowArrow(lit_arrow)
)
self.play(*self.get_lit_vertex_animations(lit_vertex))
self.play(
FadeIn(dim_word),
LaggedStart(GrowArrow, dim_arrows)
)
self.dither()
self.play(*map(FadeOut, [
lit_word, lit_arrow, dim_word, dim_arrows
]))
def show_rule_for_lighting(self):
lines = self.lines
regions = self.regions
line_groups = self.line_groups
objects = self.objects
houses, utilities = self.houses, self.utilities
#First region, lines 0, 1, 4, 3
lines[4].rotate_in_place(np.pi)
region = regions[1]
self.play(ShowCreation(lines[0]))
self.play(*self.get_count_change_animations(0, 1, 0))
self.play(*it.chain(
self.get_lit_vertex_animations(houses[0]),
self.get_count_change_animations(1, 0, 0)
))
self.dither()
for line, vertex in (lines[1], houses[1]), (lines[4], utilities[1]):
self.play(
ShowCreation(line),
*self.get_count_change_animations(0, 1, 0)
)
self.play(*it.chain(
self.get_lit_vertex_animations(vertex),
self.get_count_change_animations(1, 0, 0),
))
self.dither()
self.play(
ShowCreation(lines[3], run_time = 2),
*self.get_count_change_animations(0, 1, 0)
)
self.add_foreground_mobjects(line_groups[0])
self.add_foreground_mobjects(objects)
self.play(
FadeIn(region),
*self.get_count_change_animations(0, 0, 1)
)
self.dither()
#Next region, lines 2, 7, 8
region = regions[3]
lines[6].rotate_in_place(np.pi)
for line, vertex in (lines[2], houses[2]), (lines[6], utilities[2]):
self.play(ShowCreation(line), *it.chain(
self.get_lit_vertex_animations(
vertex,
run_time = 2,
squish_range = (0.5, 1),
),
self.get_count_change_animations(1, 1, 0)
))
self.dither()
self.play(
ShowCreation(lines[7]),
*self.get_count_change_animations(0, 1, 1)
)
self.add_foreground_mobjects(line_groups[2])
self.add_foreground_mobjects(objects)
self.play(FadeIn(region))
self.dither()
####
def get_count_change_animations(self, *changes):
anims = []
for change, count in zip(changes, self.counts):
if change == 0:
continue
new_count = Integer(count.number + 1)
new_count.move_to(count)
anims.append(Transform(
count, new_count,
run_time = 2,
rate_func = squish_rate_func(smooth, 0.5, 1)
))
count.number += 1
anims.append(self.get_plus_one_anim(count))
return anims
def get_plus_one_anim(self, count):
plus_one = TexMobject("+1")
plus_one.highlight(YELLOW)
plus_one.move_to(count)
plus_one.next_to(count, DOWN)
plus_one.generate_target()
plus_one.target.move_to(count)
plus_one.target.set_fill(opacity = 0)
move = MoveToTarget(plus_one, remover = True)
grow = GrowFromCenter(plus_one)
return UpdateFromAlphaFunc(
plus_one,
lambda m, a : (
(grow if a < 0.5 else move).update(2*a%1)
),
remover = True,
rate_func = double_smooth,
run_time = 2
)
def get_lit_vertex_animations(self, vertex, run_time = 1, squish_range = (0, 1)):
line = Line(
LEFT, RIGHT,
stroke_width = 0,
stroke_color = BLACK,
)
line.scale_to_fit_width(0.5*vertex.get_width())
line.next_to(ORIGIN, buff = 0.75*vertex.get_width())
lines = VGroup(*[
line.copy().rotate(angle)
for angle in np.arange(0, 2*np.pi, np.pi/4)
])
lines.move_to(vertex)
random.shuffle(lines.submobjects)
return [
LaggedStart(
ApplyMethod, lines,
lambda l : (l.set_stroke, YELLOW, 4),
rate_func = squish_rate_func(there_and_back, *squish_range),
lag_ratio = 0.75,
remover = True,
run_time = run_time
),
ApplyMethod(
vertex.set_fill, None, 1,
run_time = run_time,
rate_func = squish_rate_func(smooth, *squish_range)
),
]
class ShowRule(TeacherStudentsScene):
def construct(self):
new_edge = TextMobject("New edge")
new_vertex = TextMobject("New (lit) vertex")
new_vertex.next_to(new_edge, UP+RIGHT, MED_LARGE_BUFF)
new_region = TextMobject("New region")
new_region.next_to(new_edge, DOWN+RIGHT, MED_LARGE_BUFF)
VGroup(new_vertex, new_region).shift(MED_LARGE_BUFF*RIGHT)
arrows = VGroup(*[
Arrow(
new_edge.get_right(), mob.get_left(),
color = WHITE,
buff = SMALL_BUFF
)
for mob in new_vertex, new_region
])
new_vertex.highlight(YELLOW)
new_edge.highlight(BLUE)
new_region.highlight(RED)
rule = VGroup(new_edge, arrows, new_vertex, new_region)
rule.center().to_edge(UP)
self.add(rule)
class ConcludeFiveRegions(LightUpNodes):
def construct(self):
self.setup_configuration()
self.setup_regions()
self.setup_counters()
self.show_new_region_creation()
self.show_rule()
self.show_nine_lines_to_start()
self.conclude_about_five_regions()