From 74406f239c2c066a44cb7b7a46fa2d88ffb7972e Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 21 Dec 2017 13:43:27 -0800 Subject: [PATCH] Beginning mug project --- mug.py | 1307 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1307 insertions(+) create mode 100644 mug.py diff --git a/mug.py b/mug.py new file mode 100644 index 00000000..5a7f3305 --- /dev/null +++ b/mug.py @@ -0,0 +1,1307 @@ +#!/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() + + + + + + + + + + + + + + + + + + + + + +