3b1b-manim/old_projects/mug.py
2019-05-02 20:36:14 -07:00

2085 lines
64 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from manimlib.imports import *
from old_projects.efvgt import ConfettiSpiril
#revert_to_original_skipping_status
class HappyHolidays(TeacherStudentsScene):
def construct(self):
hats = VGroup(*list(map(
self.get_hat, self.pi_creatures
)))
self.add(self.get_snowflakes())
self.change_student_modes(
*["hooray"]*3,
look_at_arg = FRAME_Y_RADIUS*UP,
added_anims = [self.teacher.change, "hooray"]
)
self.play(LaggedStartMap(
DrawBorderThenFill, hats
), Animation(self.pi_creatures))
self.change_student_modes(
"happy", "wave_2", "wave_1",
look_at_arg = FRAME_Y_RADIUS*UP,
)
self.look_at(self.teacher.get_corner(UP+LEFT))
self.wait(2)
self.play(self.teacher.change, "happy")
self.wait(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()*FRAME_X_RADIUS - FRAME_X_RADIUS,
**kwargs
)
snowflake_spirils = LaggedStartMap(
random_confetti_spiral, snowflakes,
run_time = 10,
rate_func = lambda x : x,
)
return turn_animation_into_updater(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.set_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.set_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.set_color("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(LaggedStartMap(MoveToTarget, group, run_time = run_time))
class PauseIt(PiCreatureScene):
def construct(self):
morty = self.pi_creatures
morty.center().to_edge(DOWN)
self.pi_creature_says(
"Pause it!", target_mode = "surprised"
)
self.wait(2)
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]),
LaggedStartMap(DrawBorderThenFill, utilities, run_time = 2)
)
self.play(
LaggedStartMap(
ShowCreation, lines,
run_time = 3
),
Animation(self.objects)
)
self.play(
Write(no_crossing_words[0]),
GrowArrow(no_crossing_words[1]),
)
self.wait()
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(
(FRAME_Y_RADIUS*DOWN + FRAME_X_RADIUS*LEFT)/2
)
eulers = TexMobject(*"V-E+F=2")
eulers.set_color_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.set_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
)
always_rotate(cube, axis=UP)
self.play(
GrowArrow(arrow_to_eulers),
Write(eulers),
FadeIn(cube)
)
self.wait(5)
self.play(
GrowArrow(arrow_from_eulers),
Write(tda)
)
self.wait(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,
LaggedStartMap(FadeIn, self.students)
)
self.change_student_modes(
*["pondering"]*3, look_at_arg = everything
)
self.wait(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(
LaggedStartMap(DrawBorderThenFill, houses),
LaggedStartMap(GrowFromCenter, utilities),
try_it.set_width, house.get_width(),
try_it.fade, 1,
try_it.move_to, house,
self.pi_creature.change, "happy",
)
self.play(LaggedStartMap(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(LaggedStartMap(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(LaggedStartMap(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.set_color(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(LaggedStartMap(
ShowCreation, straight_lines,
run_time = 2,
lag_ratio = 0.8
), Blink(randy))
self.play(Transform(
straight_lines, almost_solution_lines,
run_time = 3,
lag_ratio = 0.5
))
self.wait()
######
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.set_color(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(LaggedStartMap(GrowFromCenter, pi_creatures))
self.play(
LaggedStartMap(ShowCreation, lines),
LaggedStartMap(
ApplyMethod, pi_creatures,
lambda pi : (pi.change, "pondering", lines)
)
)
self.play(Write(graph_word))
self.play(ReplacementTransform(
pi_creatures, dots,
run_time = 2,
lag_ratio = 0.5
))
self.add_foreground_mobjects(dots)
self.play(
FadeIn(vertex_arrows),
FadeIn(vertices_word),
)
self.wait()
self.play(LaggedStartMap(
ApplyMethod, lines,
lambda l : (l.rotate_in_place, np.pi/12),
rate_func = wiggle
))
self.play(
FadeIn(edge_word),
GrowArrow(edge_arrow),
)
self.wait(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.wait()
self.play(ReplacementTransform(graph_word, planar_graph_word))
self.wait(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_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.set_color_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.set_color(YELLOW)
self.add(question)
self.convert_objects_to_dots()
self.play(LaggedStartMap(ShowCreation, straight_lines))
self.play(
GrowFromCenter(brace),
LaggedStartMap(FadeIn, fancy_name),
)
self.play(ReplacementTransform(
straight_lines, almost_solution_lines,
run_time = 3,
lag_ratio = 0.5
))
self.wait(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.set_color(GREEN)
non_eulers = VGroup(*[m for m in words if m is not eulers])
self.add(words)
self.wait()
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.set_color(GREEN)
dont = TextMobject("Don't")
dont.next_to(pi2, DOWN)
dont.set_color(RED)
self.play(
FadeIn(know_eulers),
pi1.change, "hooray",
)
self.play(
FadeIn(dont),
pi2.change, "maybe", eulers,
)
self.wait()
self.pi_creature_thinks(
pi1, "",
bubble_kwargs = {"width" : 3, "height" : 2},
target_mode = "thinking"
)
self.play(pi2.change, "confused", eulers)
self.wait()
### Out of thin air
self.play(*list(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),
LaggedStartMap(DrawBorderThenFill, VGroup(*it.chain(*objects)))
)
self.wait()
self.play(
objects.move_to, eulers, RIGHT,
eulers.move_to, objects, LEFT,
path_arc = np.pi,
run_time = 1.5,
)
self.wait(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(LaggedStartMap(
ShowCreation, lines,
run_time = 3,
))
self.add_foreground_mobjects(lines, objects)
self.wait()
for region in front_regions:
self.play(FadeIn(region))
self.play(
FadeIn(back_region),
Animation(front_regions),
)
self.wait()
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.wait()
self.play(
FadeOut(back_region),
FadeOut(front_regions[0]),
FadeOut(paint_bucket),
*list(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.wait()
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.set_color(BLUE_E)
front_regions.set_color_by_gradient(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.wait(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(RIGHT, buff = LARGE_BUFF)
front_regions.target.to_edge(UP)
self.add(front_regions)
self.add_foreground_mobjects(lines, objects)
self.wait()
self.play(MoveToTarget(front_regions))
self.play(LaggedStartMap(
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.wait()
#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.set_color, YELLOW)]
if last_region:
anims.append(ApplyMethod(last_region.restore))
anims.append(Animation(front_regions))
self.play(*anims, run_time = 0.25)
self.wait(0.5)
last_region = region
self.play(last_region.restore)
self.wait()
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(LaggedStartMap(
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.wait()
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.wait()
self.play(ShowCreation(lines[-1]))
self.add(region, lines, objects)
self.wait()
self.remove(region)
self.play(ShowCreation(lines[-1],
rate_func = lambda t : smooth(1-2*t*(1-t))
))
self.add(region, lines, objects)
self.wait()
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.wait(2)
class LightUpNodes(IntroduceRegions):
CONFIG = {
"vertices_word" : "Lit vertices",
}
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 = 0)
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("\\# %s"%self.vertices_word),
TextMobject("\\# Edges"),
TextMobject("\\# Regions"),
]
for title, vect in zip(titles, [LEFT, ORIGIN, RIGHT]):
title.shift(FRAME_X_RADIUS*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.count_titles = titles
self.v_count, self.e_count, self.f_count = self.counts = list(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),
LaggedStartMap(GrowArrow, dim_arrows)
)
self.wait()
self.play(*list(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.wait()
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.wait()
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.wait()
#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.wait()
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.wait()
####
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.set_color(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.set_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 [
LaggedStartMap(
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(RIGHT)
arrows = VGroup(*[
Arrow(
new_edge.get_right(), mob.get_left(),
color = WHITE,
buff = SMALL_BUFF
)
for mob in (new_vertex, new_region)
])
for word, arrow in zip(["Either", "or"], arrows):
word_mob = TextMobject(word)
word_mob.scale(0.65)
word_mob.next_to(ORIGIN, UP, SMALL_BUFF)
word_mob.rotate(arrow.get_angle())
word_mob.shift(arrow.get_center())
word_mob.set_color(GREEN)
arrow.add(word_mob)
new_vertex.set_color(YELLOW)
new_edge.set_color(BLUE)
new_region.set_color(RED)
rule = VGroup(new_edge, arrows, new_vertex, new_region)
rule.center().to_edge(UP)
nine_total = TextMobject("(9 total)")
nine_total.next_to(new_edge, DOWN)
self.play(
Animation(rule),
self.teacher.change, "raise_right_hand"
)
self.change_student_modes(
*["confused"]*3,
look_at_arg = rule
)
self.wait(2)
self.play(
Write(nine_total),
self.teacher.change, "happy",
)
self.change_student_modes(
*["thinking"]*3,
look_at_arg = rule
)
self.wait(3)
class ConcludeFiveRegions(LightUpNodes):
def construct(self):
self.setup_configuration()
self.setup_regions()
self.setup_counters()
self.describe_start_setup()
self.show_nine_lines_to_start()
self.show_five_to_lit_up_nodes()
self.relate_four_lines_to_regions()
self.conclude_about_five_regions()
def describe_start_setup(self):
to_dim = VGroup(*it.chain(self.houses, self.utilities[1:]))
to_dim.set_stroke(width = 1)
to_dim.set_fill(opacity = 0)
full_screen_rect = FullScreenFadeRectangle(
fill_color = LIGHT_GREY,
fill_opacity = 0.25,
)
self.play(
Indicate(self.v_count),
*self.get_lit_vertex_animations(self.utilities[0])
)
self.play(
FadeIn(
full_screen_rect,
rate_func = there_and_back,
remover = True,
),
Indicate(self.f_count),
*list(map(Animation, self.mobjects))
)
self.wait()
def show_nine_lines_to_start(self):
line_sets = self.get_straight_lines()
line_sets.target = VGroup()
for lines in line_sets:
lines.generate_target()
for line in lines.target:
line.rotate(-line.get_angle())
line.set_width(1.5)
lines.target.arrange(DOWN)
line_sets.target.add(lines.target)
line_sets.target.arrange(DOWN)
line_sets.target.center()
line_sets.target.to_edge(RIGHT)
for lines in line_sets:
self.play(LaggedStartMap(ShowCreation, lines, run_time = 1))
self.play(MoveToTarget(lines))
self.wait()
ghost_lines = line_sets.copy()
ghost_lines.fade(0.9)
self.add(ghost_lines, line_sets)
self.side_lines = VGroup(*it.chain(*line_sets))
def show_five_to_lit_up_nodes(self):
side_lines = self.side_lines
lines = self.lines
vertices = VGroup(*it.chain(self.houses, self.utilities))
line_indices = [0, 1, 4, 6, 7]
vertex_indices = [0, 1, 4, 5, 2]
for li, vi in zip(line_indices, vertex_indices):
side_line = side_lines[li]
line = lines[li]
vertex = vertices[vi]
self.play(ReplacementTransform(side_line, line))
self.play(*it.chain(
self.get_count_change_animations(1, 1, 0),
self.get_lit_vertex_animations(vertex),
))
def relate_four_lines_to_regions(self):
f_rect = SurroundingRectangle(
VGroup(self.count_titles[-1], self.f_count)
)
on_screen_side_lines = VGroup(*[m for m in self.side_lines if m in self.mobjects])
side_lines_rect = SurroundingRectangle(on_screen_side_lines)
side_lines_rect.set_color(WHITE)
self.play(ShowCreation(side_lines_rect))
self.wait()
self.play(ReplacementTransform(side_lines_rect, f_rect))
self.play(FadeOut(f_rect))
self.wait()
def conclude_about_five_regions(self):
lines = self.lines
side_lines = self.side_lines
regions = self.regions[1:]
line_groups = self.line_groups
line_indices = [3, 5, 2]
objects = self.objects
for region, line_group, li in zip(regions, line_groups, line_indices):
self.play(ReplacementTransform(
side_lines[li], lines[li]
))
self.play(
FadeIn(region),
Animation(line_group),
Animation(objects),
*self.get_count_change_animations(0, 1, 1)
)
self.wait()
#Conclude
words = TextMobject("Last line must \\\\ introduce 5th region")
words.scale(0.8)
words.set_color(BLUE)
rect = SurroundingRectangle(self.f_count)
rect.set_color(BLUE)
words.next_to(rect, DOWN)
randy = Randolph().flip()
randy.scale(0.5)
randy.next_to(words, RIGHT, SMALL_BUFF, DOWN)
self.play(ShowCreation(rect), Write(words))
self.play(FadeIn(randy))
self.play(randy.change, "pondering")
self.play(Blink(randy))
self.wait(2)
class WhatsWrongWithFive(TeacherStudentsScene):
def construct(self):
self.student_says(
"What's wrong with \\\\ 5 regions?",
target_mode = "maybe"
)
self.wait(2)
class CyclesHaveAtLeastFour(UtilitiesPuzzleScene):
def construct(self):
self.setup_configuration()
houses, utilities = self.houses, self.utilities
vertices = VGroup(
houses[0], utilities[0],
houses[1], utilities[1], houses[0],
)
lines = [
VectorizedPoint(),
self.get_line(0, 0),
self.get_line(0, 1),
self.get_line(1, 1),
self.get_line(1, 0, self.objects.get_corner(DOWN+LEFT)),
]
for line in lines[1::2]:
line.points = line.points[::-1]
arrows = VGroup()
for vertex in vertices:
vect = vertices.get_center() - vertex.get_center()
arrow = Vector(vect, color = WHITE)
arrow.next_to(vertex, -vect, buff = 0)
vertex.arrow = arrow
arrows.add(arrow)
word_strings = [
"Start at a house",
"Go to a utility",
"Go to another house",
"Go to another utility",
"Back to the start",
]
words = VGroup()
for word_string, arrow in zip(word_strings, arrows):
vect = arrow.get_vector()[1]*UP
word = TextMobject(word_string)
word.next_to(arrow.get_start(), -vect)
words.add(word)
count = Integer(-1)
count.fade(1)
count.to_edge(UP)
last_word = None
last_arrow = None
for line, word, arrow in zip(lines, words, arrows):
anims = []
for mob in last_word, last_arrow:
if mob:
anims.append(FadeOut(mob))
new_count = Integer(count.number + 1)
new_count.move_to(count)
anims += [
FadeIn(word),
GrowArrow(arrow),
ShowCreation(line),
FadeOut(count),
FadeIn(new_count),
]
self.play(*anims)
self.wait()
last_word = word
last_arrow = arrow
count = new_count
self.wait(2)
class FiveRegionsFourEdgesEachGraph(Scene):
CONFIG = {
"v_color" : WHITE,
"e_color" : YELLOW,
"f_colors" : (BLUE, RED_E, BLUE_E),
"n_edge_double_count_examples" : 6,
"random_seed" : 1,
}
def construct(self):
self.draw_squares()
self.transition_to_graph()
self.count_edges_per_region()
self.each_edges_has_two_regions()
self.ten_total_edges()
def draw_squares(self):
words = VGroup(
TextMobject("5", "regions"),
TextMobject("4", "edges each"),
)
words.arrange(DOWN)
words.to_edge(UP)
words[0][0].set_color(self.f_colors[0])
words[1][0].set_color(self.e_color)
squares = VGroup(*[Square() for x in range(5)])
squares.scale(0.5)
squares.set_stroke(width = 0)
squares.set_fill(opacity = 1)
squares.set_color_by_gradient(*self.f_colors)
squares.arrange(RIGHT, buff = MED_LARGE_BUFF)
squares.next_to(words, DOWN, LARGE_BUFF)
all_edges = VGroup()
all_vertices = VGroup()
for square in squares:
corners = square.get_anchors()[:4]
square.edges = VGroup(*[
Line(c1, c2, color = self.e_color)
for c1, c2 in adjacent_pairs(corners)
])
square.vertices = VGroup(*[
Dot(color = self.v_color).move_to(c)
for c in corners
])
all_edges.add(*square.edges)
all_vertices.add(*square.vertices)
self.play(
FadeIn(words[0]),
LaggedStartMap(FadeIn, squares, run_time = 1.5)
)
self.play(
FadeIn(words[1]),
LaggedStartMap(ShowCreation, all_edges),
LaggedStartMap(GrowFromCenter, all_vertices),
)
self.wait()
self.add_foreground_mobjects(words)
self.set_variables_as_attrs(words, squares)
def transition_to_graph(self):
squares = self.squares
words = self.words
points = np.array([
UP+LEFT,
UP+RIGHT,
DOWN+RIGHT,
DOWN+LEFT,
3*(UP+RIGHT),
3*(DOWN+LEFT),
3*(DOWN+RIGHT),
])
points *= 0.75
regions = VGroup(*[
Square().set_points_as_corners(points[indices])
for indices in [
[0, 1, 2, 3],
[0, 4, 2, 1],
[5, 0, 3, 2],
[5, 2, 4, 6],
[6, 4, 0, 5],
]
])
regions.set_stroke(width = 0)
regions.set_fill(opacity = 1)
regions.set_color_by_gradient(*self.f_colors)
all_edges = VGroup()
all_movers = VGroup()
for region, square in zip(regions, squares):
corners = region.get_anchors()[:4]
region.edges = VGroup(*[
Line(c1, c2, color = self.e_color)
for c1, c2 in adjacent_pairs(corners)
])
all_edges.add(*region.edges)
region.vertices = VGroup(*[
Dot(color = self.v_color).move_to(c)
for c in corners
])
mover = VGroup(
square, square.edges, square.vertices,
)
mover.target = VGroup(
region, region.edges, region.vertices
)
all_movers.add(mover)
back_region = FullScreenFadeRectangle()
back_region.set_fill(regions[-1].get_color(), 0.5)
regions[-1].set_fill(opacity = 0)
back_region.add(regions[-1].copy().set_fill(BLACK, 1))
back_region.edges = regions[-1].edges
self.play(
FadeIn(
back_region,
rate_func = squish_rate_func(smooth, 0.7, 1),
run_time = 3,
),
LaggedStartMap(
MoveToTarget, all_movers,
run_time = 3,
replace_mobject_with_target_in_scene = True,
),
)
self.wait(2)
self.set_variables_as_attrs(
regions, all_edges, back_region,
graph = VGroup(*[m.target for m in all_movers])
)
def count_edges_per_region(self):
all_edges = self.all_edges
back_region = self.back_region
regions = self.regions
graph = self.graph
all_vertices = VGroup(*[r.vertices for r in regions])
ghost_edges = all_edges.copy()
ghost_edges.set_stroke(LIGHT_GREY, 1)
count = Integer(0)
count.scale(2)
count.next_to(graph, RIGHT, buff = 2)
count.set_fill(YELLOW, opacity = 0)
last_region = VGroup(back_region, *regions[1:])
last_region.add(all_edges)
for region in list(regions[:-1]) + [back_region]:
self.play(
FadeIn(region),
Animation(ghost_edges),
FadeOut(last_region),
Animation(count),
Animation(all_vertices),
)
for edge in region.edges:
new_count = Integer(count.number + 1)
new_count.replace(count, dim_to_match = 1)
new_count.set_color(count.get_color())
self.play(
ShowCreation(edge),
FadeOut(count),
FadeIn(new_count),
run_time = 0.5
)
count = new_count
last_region = VGroup(region, region.edges)
self.wait()
self.add_foreground_mobjects(count)
self.play(
FadeOut(last_region),
Animation(ghost_edges),
Animation(all_vertices),
)
self.set_variables_as_attrs(count, ghost_edges, all_vertices)
def each_edges_has_two_regions(self):
regions = list(self.regions[:-1]) + [self.back_region]
back_region = self.back_region
self.add_foreground_mobjects(self.ghost_edges, self.all_vertices)
edge_region_pair_groups = []
for r1, r2 in it.combinations(regions, 2):
for e1 in r1.edges:
for e2 in r2.edges:
diff = e1.get_center()-e2.get_center()
if get_norm(diff) < 0.01:
edge_region_pair_groups.append(VGroup(
e1, r1, r2
))
for x in range(self.n_edge_double_count_examples):
edge, r1, r2 = random.choice(edge_region_pair_groups)
if r2 is back_region:
#Flip again, maybe you're still unlucky, maybe not
edge, r1, r2 = random.choice(edge_region_pair_groups)
self.play(ShowCreation(edge))
self.add_foreground_mobjects(edge)
self.play(FadeIn(r1), run_time = 0.5)
self.play(FadeIn(r2), Animation(r1), run_time = 0.5)
self.wait(0.5)
self.play(*list(map(FadeOut, [r2, r1, edge])), run_time = 0.5)
self.remove_foreground_mobjects(edge)
def ten_total_edges(self):
double_count = self.count
brace = Brace(double_count, UP)
words = brace.get_text("Double-counts \\\\ edges")
regions = self.regions
edges = VGroup(*it.chain(
regions[0].edges,
regions[-1].edges,
[regions[1].edges[1]],
[regions[2].edges[3]],
))
count = Integer(0)
count.scale(2)
count.set_fill(WHITE, 0)
count.next_to(self.graph, LEFT, LARGE_BUFF)
self.play(
GrowFromCenter(brace),
Write(words)
)
self.wait()
for edge in edges:
new_count = Integer(count.number + 1)
new_count.replace(count, dim_to_match = 1)
self.play(
ShowCreation(edge),
FadeOut(count),
FadeIn(new_count),
run_time = 0.5
)
count = new_count
self.wait()
class EulersFormulaForGeneralPlanarGraph(LightUpNodes, ThreeDScene):
CONFIG = {
"vertices_word" : "Vertices"
}
def construct(self):
self.setup_counters()
self.show_creation_of_graph()
self.show_formula()
self.transform_into_cube()
def show_creation_of_graph(self):
points = np.array([
UP+LEFT,
UP+RIGHT,
DOWN+RIGHT,
DOWN+LEFT,
3*(UP+LEFT),
3*(UP+RIGHT),
3*(DOWN+RIGHT),
3*(DOWN+LEFT),
])
points *= 0.75
points += DOWN
vertices = VGroup(*list(map(Dot, points)))
vertices.set_color(YELLOW)
edges = VGroup(*[
VGroup(*[
Line(p1, p2, color = WHITE)
for p2 in points
])
for p1 in points
])
regions = self.get_cube_faces(points)
regions.set_stroke(width = 0)
regions.set_fill(opacity = 1)
regions.set_color_by_gradient(GREEN, RED, BLUE_E)
regions[-1].set_fill(opacity = 0)
pairs = [
(edges[0][1], vertices[1]),
(edges[1][2], vertices[2]),
(edges[2][6], vertices[6]),
(edges[6][5], vertices[5]),
(edges[5][1], regions[2]),
(edges[0][4], vertices[4]),
(edges[4][5], regions[1]),
(edges[0][3], vertices[3]),
(edges[3][2], regions[0]),
(edges[4][7], vertices[7]),
(edges[7][3], regions[4]),
(edges[7][6], regions[3]),
]
self.add_foreground_mobjects(vertices[0])
self.wait()
for edge, obj in pairs:
anims = [ShowCreation(edge)]
if obj in vertices:
obj.save_state()
obj.move_to(edge.get_start())
anims.append(ApplyMethod(obj.restore))
anims += self.get_count_change_animations(1, 1, 0)
self.add_foreground_mobjects(obj)
else:
anims = [FadeIn(obj)] + anims
anims += self.get_count_change_animations(0, 1, 1)
self.play(*anims)
self.add_foreground_mobjects(edge)
self.wait()
self.set_variables_as_attrs(edges, vertices, regions)
def show_formula(self):
counts = VGroup(*self.counts)
count_titles = VGroup(*self.count_titles)
groups = [count_titles, counts]
for group in groups:
group.symbols = VGroup(*list(map(TexMobject, ["-", "+", "="])))
group.generate_target()
line = VGroup(*it.chain(*list(zip(group.target, group.symbols))))
line.arrange(RIGHT)
line.to_edge(UP, buff = MED_SMALL_BUFF)
VGroup(counts.target, counts.symbols).shift(0.75*DOWN)
for mob in count_titles.target:
mob[-1].fade(1)
count_titles.symbols.shift(0.5*SMALL_BUFF*UP)
twos = VGroup(*[
TexMobject("2").next_to(group.symbols, RIGHT)
for group in groups
])
twos.shift(0.5*SMALL_BUFF*UP)
words = TextMobject("``Euler's characteristic formula''")
words.next_to(counts.target, DOWN)
words.shift(MED_LARGE_BUFF*RIGHT)
words.set_color(YELLOW)
for group in groups:
self.play(
MoveToTarget(group),
Write(group.symbols)
)
self.wait()
self.play(Write(twos))
self.wait()
self.play(Write(words))
self.wait()
self.top_formula = VGroup(count_titles, count_titles.symbols, twos[0])
self.bottom_formula = VGroup(counts, counts.symbols, twos[1])
def transform_into_cube(self):
regions = self.regions
points = np.array([
UP+LEFT,
UP+RIGHT,
DOWN+RIGHT,
DOWN+LEFT,
UP+LEFT+2*IN,
UP+RIGHT+2*IN,
DOWN+RIGHT+2*IN,
DOWN+LEFT+2*IN,
])
cube = self.get_cube_faces(points)
cube.shift(OUT)
cube.rotate_in_place(np.pi/12, RIGHT)
cube.rotate_in_place(np.pi/6, UP)
cube.shift(MED_LARGE_BUFF*DOWN)
shade_in_3d(cube)
for face, region in zip(cube, regions):
face.set_fill(region.get_color(), opacity = 0.8)
self.remove(self.edges)
regions.set_stroke(WHITE, 3)
cube.set_stroke(WHITE, 3)
new_formula = TexMobject("V - E + F = 2")
new_formula.to_edge(UP, buff = MED_SMALL_BUFF)
new_formula.align_to(self.bottom_formula, RIGHT)
self.play(FadeOut(self.vertices))
self.play(ReplacementTransform(regions, cube, run_time = 2))
cube.sort(lambda p : -p[2])
always_rotate(cube, axis=UP, about_point=ORIGIN)
self.add(cube)
self.wait(3)
self.play(
FadeOut(self.top_formula),
FadeIn(new_formula)
)
self.wait(10)
###
def get_cube_faces(self, eight_points):
return VGroup(*[
Square().set_points_as_corners(eight_points[indices])
for indices in [
[0, 1, 2, 3],
[0, 4, 5, 1],
[1, 5, 6, 2],
[2, 6, 7, 3],
[3, 7, 4, 0],
[4, 5, 6, 7],
]
])
class YouGaveFriendsAnImpossiblePuzzle(TeacherStudentsScene):
def construct(self):
self.student_says(
"You gave friends \\\\ an impossible puzzle?",
target_mode = "sassy",
)
self.change_student_modes(
"angry", "sassy", "angry",
added_anims = [self.teacher.change, "happy"]
)
self.wait(2)
class FunnyStory(TeacherStudentsScene):
def construct(self):
self.teacher_says("Funny story", target_mode = "hooray")
self.wait()
self.change_student_modes(
*["happy"]*3,
added_anims = [RemovePiCreatureBubble(
self.teacher,
target_mode = "raise_right_hand"
)],
look_at_arg = UP+2*RIGHT
)
self.wait(5)
class QuestionWrapper(Scene):
def construct(self):
question = TextMobject(
"Where", "\\emph{specifically}", "does\\\\",
"this proof break down?",
)
question.to_edge(UP)
question.set_color_by_tex("specifically", YELLOW)
screen_rect = ScreenRectangle(height = 5.5)
screen_rect.next_to(question, DOWN)
self.play(ShowCreation(screen_rect))
self.wait()
for word in question:
self.play(LaggedStartMap(
FadeIn, word,
run_time = 0.05*len(word)
))
self.wait(0.05)
self.wait()
class Homework(TeacherStudentsScene):
def construct(self):
self.teacher_says("Consider this \\\\ homework")
self.change_student_modes(*["pondering"]*3)
self.wait(2)
self.student_says(
"$V-E+F=0$ on \\\\ a torus!",
target_mode = "hooray"
)
self.wait()
self.teacher_says("Not good enough!", target_mode = "surprised")
self.change_student_modes(*["confused"]*3)
self.wait(2)
class WantToLearnMore(Scene):
def construct(self):
text = TextMobject("Want to learn more?")
self.play(Write(text))
self.wait()
class PatreonThanks(PatreonEndScreen):
CONFIG = {
"specific_patrons" : [
"Randall Hunt",
"Desmos",
"Burt Humburg",
"CrypticSwarm",
"Juan Benet",
"David Kedmey",
"Ali Yahya",
"Mayank M. Mehrotra",
"Lukas Biewald",
"Yana Chernobilsky",
"Kaustuv DeBiswas",
"Kathryn Schmiedicke",
"Yu Jun",
"Dave Nicponski",
"Damion Kistler",
"Jordan Scales",
"Markus Persson",
"Egor Gumenuk",
"Yoni Nazarathy",
"Ryan Atallah",
"Joseph John Cox",
"Luc Ritchie",
"Onuralp Soylemez",
"John Bjorn Nelson",
"Yaw Etse",
"David Barbetta",
"Julio Cesar Campo Neto",
"Waleed Hamied",
"Oliver Steele",
"George Chiesa",
"supershabam",
"James Park",
"Samantha D. Suplee",
"Delton Ding",
"Thomas Tarler",
"Jonathan Eppele",
"Isak Hietala",
"1stViewMaths",
"Jacob Magnuson",
"Mark Govea",
"Dagan Harrington",
"Clark Gaebel",
"Eric Chow",
"Mathias Jansson",
"David Clark",
"Michael Gardner",
"Mads Elvheim",
"Erik Sundell",
"Awoo",
"Dr. David G. Stork",
"Tianyu Ge",
"Ted Suzman",
"Linh Tran",
"Andrew Busey",
"John Haley",
"Ankalagon",
"Eric Lavault",
"Boris Veselinovich",
"Julian Pulgarin",
"Jeff Linse",
"Cooper Jones",
"Ryan Dahl",
"Robert Teed",
"Jason Hise",
"Meshal Alshammari",
"Bernd Sing",
"James Thornton",
"Mustafa Mahdi",
"Mathew Bramson",
"Jerry Ling",
"Shimin Kuang",
"Rish Kundalia",
"Achille Brighton",
"Ripta Pasay",
]
}