#!/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", ] }