from manim_imports_ext import * import scipy.spatial # Helpers def project_to_xy_plane(p1, p2): """ Draw a line from source to p1 to p2. Where does it intersect the xy plane? """ x1, y1, z1 = p1 x2, y2, z2 = p2 if z2 < z1: z2 = z1 + 1e-2 # TODO, bad hack vect = p2 - p1 return p1 - (z2 / vect[2]) * vect def flat_project(point): # return [*point[:2], 0] return [*point[:2], 0.05 * point[2]] # TODO def get_pre_shadow(mobject, opacity): result = mobject.deepcopy() if isinstance(result, Group) and all((isinstance(sm, VMobject) for sm in mobject)): result = VGroup(*result) result.clear_updaters() for sm in result.family_members_with_points(): color = interpolate_color(sm.get_color(), BLACK, opacity) sm.set_color(color) sm.set_opacity(opacity) if isinstance(sm, VMobject): sm.set_stroke( interpolate_color(sm.get_stroke_color(), BLACK, opacity) ) sm.set_gloss(sm.get_gloss() * 0.5) sm.set_shadow(0) sm.set_reflectiveness(0) return result def update_shadow(shadow, mobject, light_source): lp = light_source.get_center() if light_source is not None else None def project(point): if lp is None: return flat_project(point) else: return project_to_xy_plane(lp, point) for sm, mm in zip(shadow.family_members_with_points(), mobject.family_members_with_points()): sm.set_points(np.apply_along_axis(project, 1, mm.get_points())) if isinstance(sm, VMobject) and sm.get_unit_normal()[2] < 0: sm.reverse_points() if isinstance(sm, VMobject): sm.set_fill(opacity=mm.get_fill_opacity()) else: sm.set_opacity(mm.get_opacity()) def get_shadow(mobject, light_source=None, opacity=0.7): shadow = get_pre_shadow(mobject, opacity) shadow.add_updater(lambda s: update_shadow(s, mobject, light_source)) return shadow def get_area(shadow): return 0.5 * sum( get_norm(sm.get_area_vector()) for sm in shadow.get_family() ) def get_convex_hull(mobject): points = mobject.get_all_points() hull = scipy.spatial.ConvexHull(points[:, :2]) return points[hull.vertices] def sort_to_camera(mobject, camera_frame): cl = camera_frame.get_implied_camera_location() mobject.sort(lambda p: -get_norm(p - cl)) return mobject def cube_sdf(point, cube): c = cube.get_center() vect = point - c face_vects = [face.get_center() - c for face in cube] return max(*( abs(np.dot(fv, vect) / np.dot(fv, fv)) for fv in face_vects )) - 1 def is_in_cube(point, cube): return cube_sdf(point, cube) < 0 def get_overline(mob): overline = Underline(mob).next_to(mob, UP, buff=0.05) overline.set_stroke(WHITE, 2) return overline def get_key_result(solid_name, color=BLUE): eq = OldTex( "\\text{Area}\\big(\\text{Shadow}(\\text{" + solid_name + "})\\big)", "=", "\\frac{1}{2}", "{c}", "\\cdot", "(\\text{Surface area})", tex_to_color_map={ "\\text{Shadow}": GREY_B, f"\\text{{{solid_name}}}": color, "\\text{Solid}": BLUE, "{c}": RED, } ) eq.add_to_back(get_overline(eq[:5])) return eq def get_surface_area(solid): return sum(get_norm(f.get_area_vector()) for f in solid) # Scenes class ShadowScene(ThreeDScene): object_center = [0, 0, 3] frame_center = [0, 0, 2] area_label_center = [0, -1.5, 0] surface_area = 6.0 num_reorientations = 10 plane_dims = (20, 20) plane_style = { "stroke_width": 0, "fill_color": GREY_A, "fill_opacity": 0.5, "gloss": 0.5, "shadow": 0.2, } limited_plane_extension = 0 object_style = { "stroke_color": WHITE, "stroke_width": 0.5, "fill_color": BLUE_E, "fill_opacity": 0.7, "reflectiveness": 0.3, "gloss": 0.1, "shadow": 0.5, } inf_light = False glow_radius = 10 glow_factor = 10 area_label_center = [-2, -1, 0] unit_size = 2 def setup(self): self.camera.frame.reorient(-30, 75) self.camera.frame.move_to(self.frame_center) self.add_plane() self.add_solid() self.add_shadow() self.setup_light_source() def add_plane(self): width, height = self.plane_dims grid = NumberPlane( x_range=(-width // 2, width // 2, 2), y_range=(-height // 2, height // 2, 2), background_line_style={ "stroke_color": GREY_B, "stroke_width": 1, }, faded_line_ratio=4, ) grid.shift(-grid.get_origin()) grid.set_width(width) grid.axes.match_style(grid.background_lines) grid.set_flat_stroke(True) grid.insert_n_curves(3) plane = Rectangle() plane.replace(grid, stretch=True) plane.set_style(**self.plane_style) plane.set_stroke(width=0) if self.limited_plane_extension > 0: plane.set_height(height // 2 + self.limited_plane_extension, about_edge=UP, stretch=True) self.plane = plane plane.add(grid) self.add(plane) def add_solid(self): self.solid = self.get_solid() self.solid.move_to(self.object_center) self.add(self.solid) def get_solid(self): cube = VCube() cube.deactivate_depth_test() cube.set_height(2) cube.set_style(**self.object_style) # Wrap in group so that strokes and fills # are rendered in separate passes cube = self.cube = Group(*cube) cube.add_updater(lambda m: self.sort_to_camera(m)) return cube def add_shadow(self): light_source = None if self.inf_light else self.camera.light_source shadow = get_shadow(self.solid, light_source) self.add(shadow, self.solid) self.shadow = shadow def setup_light_source(self): self.light = self.camera.light_source if self.inf_light: self.light.move_to(100 * OUT) else: glow = self.glow = TrueDot( radius=self.glow_radius, glow_factor=self.glow_factor, ) glow.set_color(interpolate_color(YELLOW, WHITE, 0.5)) glow.add_updater(lambda m: m.move_to(self.light)) self.add(glow) def sort_to_camera(self, mobject): return sort_to_camera(mobject, self.camera.frame) def get_shadow_area_label(self): text = OldTexText("Shadow area: ") decimal = DecimalNumber(100) label = VGroup(text, decimal) label.arrange(RIGHT) label.move_to(self.area_label_center - decimal.get_center()) label.fix_in_frame() label.set_backstroke() decimal.add_updater(lambda d: d.set_value( get_area(self.shadow) / (self.unit_size**2) ).set_backstroke()) return label def begin_ambient_rotation(self, mobject, speed=0.2, about_point=None, initial_axis=[1, 1, 1]): mobject.rot_axis = np.array(initial_axis) def update_mob(mob, dt): mob.rotate(speed * dt, mob.rot_axis, about_point=about_point) mob.rot_axis = rotate_vector(mob.rot_axis, speed * dt, OUT) return mob mobject.add_updater(update_mob) return mobject def get_shadow_outline(self, stroke_width=1): outline = VMobject() outline.set_stroke(WHITE, stroke_width) outline.add_updater(lambda m: m.set_points_as_corners(get_convex_hull(self.shadow)).close_path()) return outline def get_light_lines(self, outline=None, n_lines=100, only_vertices=False): if outline is None: outline = self.get_shadow_outline() def update_lines(lines): lp = self.light.get_center() if only_vertices: points = outline.get_vertices() else: points = [outline.pfp(a) for a in np.linspace(0, 1, n_lines)] for line, point in zip(lines, points): if self.inf_light: line.set_points_as_corners([point + 10 * OUT, point]) else: line.set_points_as_corners([lp, point]) line = Line(IN, OUT) light_lines = line.replicate(n_lines) light_lines.set_stroke(YELLOW, 0.5, 0.1) light_lines.add_updater(update_lines) return light_lines def random_toss(self, mobject=None, angle=TAU, about_point=None, meta_speed=5, **kwargs): if mobject is None: mobject = self.solid mobject.rot_axis = normalize(np.random.random(3)) mobject.rot_time = 0 def update(mob, time): dt = time - mob.rot_time mob.rot_time = time mob.rot_axis = rotate_vector(mob.rot_axis, meta_speed * dt, normalize(np.random.random(3))) mob.rotate(angle * dt, mob.rot_axis, about_point=about_point) self.play( UpdateFromAlphaFunc(mobject, update), **kwargs ) def randomly_reorient(self, solid=None, about_point=None): solid = self.solid if solid is None else solid solid.rotate( random.uniform(0, TAU), axis=normalize(np.random.uniform(-1, 1, 3)), about_point=about_point, ) return solid def init_frame_rotation(self, factor=0.0025, max_speed=0.01): frame = self.camera.frame frame.d_theta = 0 def update_frame(frame, dt): frame.d_theta += -factor * frame.get_theta() frame.increment_theta(clip( factor * frame.d_theta, -max_speed * dt, max_speed * dt )) frame.add_updater(update_frame) return frame class SimpleWriting(Scene): text = "" font = "Better Grade" color = WHITE font_size = 48 def construct(self): words = Text(self.text, font=self.font, font_size=self.font_size) words.set_color(self.color) self.play(Write(words)) self.wait() class AliceName(SimpleWriting): text = "Alice" font_size = 72 class BobName(SimpleWriting): text = "Bob" font = "Kalam" class BobWords(SimpleWriting): font = "Kalam" font_size = 24 words1 = "Embraces calculations" words2 = "Loves specifics" def construct(self): words = VGroup(*( Text(text, font=self.font, font_size=self.font_size) for text in (self.words1, self.words2) )) words.arrange(DOWN) for word in words: self.play(Write(word)) self.wait() class AliceWords(BobWords): font = "Better Grade" words1 = "Procrastinates calculations" words2 = "Seeks generality" font_size = 48 class AskAboutConditions(SimpleWriting): text = "Which properties matter?" class IntroduceShadow(ShadowScene): area_label_center = [-2.5, -2, 0] plane_dims = (28, 20) def construct(self): # Setup light = self.light light.move_to([0, 0, 20]) self.add(light) cube = self.solid cube.scale(0.945) # Hack to make the appropriate area 1 shadow = self.shadow outline = self.get_shadow_outline() frame = self.camera.frame frame.add_updater(lambda f, dt: f.increment_theta(0.01 * dt)) # Ambient rotation area_label = self.get_shadow_area_label() light_lines = self.get_light_lines(outline) # Question question = OldTexText( "Puzzle: Find the average\\\\area of a cube's shadow", font_size=48, ) question.to_corner(UL) question.fix_in_frame() subquestion = Text("(Averaged over all orientations)") subquestion.match_width(question) subquestion.next_to(question, DOWN, MED_LARGE_BUFF) subquestion.set_fill(BLUE_D) subquestion.fix_in_frame() subquestion.set_backstroke() # Introductory animations self.shadow.update() self.play( FadeIn(question, UP), *( LaggedStartMap(DrawBorderThenFill, mob, lag_ratio=0.1, run_time=3) for mob in (cube, shadow) ) ) self.random_toss(run_time=3, angle=TAU) # Change size and orientation outline.update() area_label.update() self.play( FadeIn(area_label), ShowCreation(outline), ) self.play( cube.animate.scale(0.5), run_time=2, rate_func=there_and_back, ) self.random_toss(run_time=2, angle=PI) self.wait() self.begin_ambient_rotation(cube) self.play(FadeIn(subquestion, 0.5 * DOWN)) self.wait(7) # Where is the light? light_comment = Text("Where is the light?") light_comment.set_color(YELLOW) light_comment.to_corner(UR) light_comment.set_backstroke() light_comment.fix_in_frame() cube.clear_updaters() cube.add_updater(lambda m: self.sort_to_camera(cube)) self.play( FadeIn(light_comment, 0.5 * UP), light.animate.next_to(cube, OUT, buff=1.5), run_time=2, ) light_lines.update() self.play( ShowCreation(light_lines, lag_ratio=0.01, run_time=3), ) self.play( light.animate.shift(1.0 * IN), rate_func=there_and_back, run_time=3 ) self.play( light.animate.shift(4 * RIGHT), run_time=5 ) self.play( Rotate(light, PI, about_point=light.get_z() * OUT), run_time=8, ) self.play(light.animate.shift(4 * RIGHT), run_time=5) self.wait() # Light straight above self.play( frame.animate.set_height(12).set_z(4), light.animate.set_z(10), run_time=3, ) self.wait() self.play(light.animate.move_to(75 * OUT), run_time=3) self.wait() self.play( frame.animate.set_height(8).set_z(2), LaggedStart(*map(FadeOut, (question, subquestion, light_comment))), run_time=2 ) # Flat projection verts = np.array([*cube[0].get_vertices(), *cube[5].get_vertices()]) vert_dots = DotCloud(verts) vert_dots.set_glow_factor(0.5) vert_dots.set_color(WHITE) proj_dots = vert_dots.copy() proj_dots.apply_function(flat_project) proj_dots.set_color(GREY_B) vert_proj_lines = VGroup(*( DashedLine(*pair) for pair in zip(verts, proj_dots.get_points()) )) vert_proj_lines.set_stroke(WHITE, 1, 0.5) point = verts[np.argmax(verts[:, 0])] xyz_label = OldTex("(x, y, z)") xy0_label = OldTex("(x, y, 0)") for label in xyz_label, xy0_label: label.rotate(PI / 2, RIGHT) label.set_backstroke() xyz_label.next_to(point, RIGHT) xy0_label.next_to(flat_project(point), RIGHT) vert_dots.save_state() vert_dots.set_glow_factor(5) vert_dots.set_radius(0.5) vert_dots.set_opacity(0) self.play( Restore(vert_dots), Write(xyz_label), ) self.wait() self.play( TransformFromCopy( cube.deepcopy().clear_updaters().set_opacity(0.5), shadow.deepcopy().clear_updaters().set_opacity(0), remover=True ), TransformFromCopy(vert_dots, proj_dots), TransformFromCopy(xyz_label, xy0_label), *map(ShowCreation, vert_proj_lines), ) self.wait(3) self.play(LaggedStart(*map(FadeOut, ( vert_dots, vert_proj_lines, proj_dots, xyz_label, xy0_label )))) # Square projection top_face = cube[np.argmax([f.get_z() for f in cube])] normal_vect = top_face.get_unit_normal() theta = np.arccos(normal_vect[2]) axis = normalize(rotate_vector([*normal_vect[:2], 0], PI / 2, OUT)) self.play(Rotate(cube, -theta, axis)) top_face = cube[np.argmax([f.get_z() for f in cube])] verts = top_face.get_vertices() vect = verts[3] - verts[2] angle = angle_of_vector(vect) self.play(Rotate(cube, -angle, OUT)) self.wait() corner = cube.get_corner(DL + OUT) edge_lines = VGroup( Line(corner, cube.get_corner(DR + OUT)), Line(corner, cube.get_corner(UL + OUT)), Line(corner, cube.get_corner(DL + IN)), ) edge_lines.set_stroke(RED, 2) s_labels = OldTex("s").replicate(3) s_labels.set_color(RED) s_labels.rotate(PI / 2, RIGHT) s_labels.set_stroke(BLACK, 3, background=True) for label, line, vect in zip(s_labels, edge_lines, [OUT, LEFT, LEFT]): label.next_to(line, vect, buff=SMALL_BUFF) s_labels[1].next_to(edge_lines[1], OUT) s_labels[2].next_to(edge_lines[2], LEFT) s_squared = OldTex("s^2") s_squared.match_style(s_labels[0]) s_squared.move_to(self.shadow) frame.generate_target() frame.target.reorient(10, 60) frame.target.set_height(6.5) self.play( LaggedStartMap(ShowCreation, edge_lines), LaggedStartMap(FadeIn, s_labels, scale=2), MoveToTarget(frame, run_time=3) ) self.wait() self.play( TransformFromCopy(s_labels[:2], s_squared), ) self.wait(2) rect = SurroundingRectangle(area_label) rect.fix_in_frame() rect.set_stroke(YELLOW, 3) s_eq = OldTex("s = 1") s_eq.next_to(area_label, DOWN) s_eq.set_color(RED) s_eq.set_stroke(BLACK, 3, background=True) s_eq.fix_in_frame() self.play(ShowCreation(rect)) self.play(FadeIn(s_eq, 0.5 * DOWN)) self.wait() self.play(LaggedStart(*map(FadeOut, ( rect, s_eq, *edge_lines, *s_labels, s_squared, )))) self.wait() # Hexagonal orientation axis = UL angle = np.arccos(1 / math.sqrt(3)) area_label.suspend_updating() self.play( Rotate(cube, -angle, axis), frame.animate.reorient(-10, 70), ChangeDecimalToValue(area_label[1], math.sqrt(3)), UpdateFromFunc(area_label[1], lambda m: m.fix_in_frame()), run_time=2 ) self.add(area_label) diagonal = Line(cube.get_nadir(), cube.get_zenith()) diagonal.set_stroke(WHITE, 2) diagonal.scale(2) diagonal.move_to(ORIGIN, IN) self.add(diagonal, cube) self.play(ShowCreation(diagonal)) self.wait(2) frame.save_state() cube_opacity = cube[0].get_fill_opacity() cube.save_state() angle = angle_of_vector(outline.get_anchors()[-1] - outline.get_anchors()[-2]) self.play( frame.animate.reorient(0, 0), cube.animate.rotate(-angle).set_opacity(0.2), run_time=3, ) frame.suspend_updating() outline_copy = outline.copy().clear_updaters() outline_copy.set_stroke(RED, 5) title = Text("Regular hexagon") title.set_color(RED) title.next_to(outline_copy, UP) title.set_backstroke() self.play( ShowCreationThenFadeOut(outline_copy), Write(title, run_time=1), ) self.play( FadeOut(title), Restore(frame), cube.animate.set_opacity(cube_opacity).rotate(angle), run_time=3, ) frame.resume_updating() hex_area_label = OldTex("\\sqrt{3} s^2") hex_area_label.set_color(RED) hex_area_label.move_to(self.shadow) hex_area_label.shift(0.35 * DOWN) self.play(Write(hex_area_label)) self.wait(10) area_label.resume_updating() self.play( Uncreate(diagonal), FadeOut(hex_area_label), Rotate(cube, 4, RIGHT) ) # Talk about averages light_lines.clear_updaters() self.begin_ambient_rotation(cube) self.play( FadeOut(light_lines), FadeIn(question, 0.5 * UP), ApplyMethod(frame.set_height, 8, run_time=2) ) self.play(FadeIn(subquestion, 0.5 * UP)) self.wait(7) cube.clear_updaters() cube.add_updater(lambda m: self.sort_to_camera(m)) samples = VGroup(VectorizedPoint()) samples.to_corner(UR) samples.shift(1.5 * LEFT) self.add(samples) for x in range(9): self.random_toss() sample = area_label[1].copy() sample.clear_updaters() sample.fix_in_frame() self.play( sample.animate.next_to(samples, DOWN), run_time=0.5 ) samples.add(sample) v_dots = OldTex("\\vdots") v_dots.next_to(samples, DOWN) v_dots.fix_in_frame() samples.add(v_dots) brace = Brace(samples, LEFT) brace.fix_in_frame() brace.next_to(samples, LEFT, SMALL_BUFF) text = OldTexText( "Take the mean.", "\\\\What does that\\\\approach?", font_size=30 ) text[0].shift(MED_SMALL_BUFF * UP) text.next_to(brace, LEFT) text.fix_in_frame() VGroup(text, brace).set_stroke(BLACK, 3, background=True) self.play( GrowFromCenter(brace), FadeIn(text), Write(v_dots), ) self.wait() for x in range(10): self.random_toss() self.wait() class AskAboutAveraging(TeacherStudentsScene): def construct(self): self.remove(self.background) sts = self.students tch = self.teacher self.play_student_changes( "maybe", "thinking", "erm", look_at=self.screen, added_anims=[self.teacher.change("raise_right_hand", self.screen)] ) self.wait(3) self.play( PiCreatureBubbleIntroduction( sts[2], OldTexText("What does that\\\\mean, exactly?"), target_mode="hesitant", look_at=self.screen, bubble_config={"direction": LEFT} ), LaggedStart( sts[0].change("confused", self.screen), sts[1].change("pondering", self.screen), tch.change("tease", sts[2].eyes), ) ) self.wait(4) self.student_says( "Can we do an experiment?", target_mode="raise_left_hand", index=1, ) self.wait(4) self.student_says( OldTexText("But what defines a\\\\``random'' toss?"), look_at=self.screen, target_mode="hesitant", index=2, added_anims=[ self.teacher.change("guilty"), self.students[0].change("erm"), ] ) self.wait(4) self.play(LaggedStart( self.students[0].change("pondering", self.screen), self.students[1].change("maybe", self.screen), self.teacher.change("tease", self.screen), )) self.wait(2) self.teacher_says(OldTexText("Hold off until\\\\the end")) self.wait(3) self.play_student_changes( "thinking", "tease", "pondering", look_at=self.screen, added_anims=[self.teacher.change("tease", self.students)] ) self.wait(4) class MeanCalculation(Scene): def construct(self): values = [1.55, 1.33, 1.46, 1.34, 1.50, 1.26, 1.42, 1.54, 1.51] nums = VGroup(*( DecimalNumber(x) for x in values )) nums.arrange(DOWN, aligned_edge=LEFT) nums.to_corner(UR, buff=LARGE_BUFF).shift(0.5 * LEFT) self.add(nums) mean_label = Text("Mean", font_size=36) mean_label.set_color(GREEN) mean_label.set_backstroke() mean_arrow = Vector(0.25 * UR) mean_arrow.match_color(mean_label) mean_arrow.next_to(mean_label, UR, SMALL_BUFF) mean_label.add(mean_arrow) for n in range(len(nums)): brace = Brace(nums[:n + 1], LEFT, buff=SMALL_BUFF) mean = DecimalNumber(np.mean(values[:n + 1])) mean.next_to(brace, LEFT) mean.match_color(mean_label) VGroup(brace, mean).set_backstroke() mean_label.next_to(mean, DL, SMALL_BUFF) self.add(brace, mean, mean_label) self.wait(0.5) self.remove(brace, mean) self.add(brace, mean) self.wait() # Embed self.embed() class DescribeSO3(ShadowScene): def construct(self): frame = self.camera.frame frame.set_z(1) frame.reorient(0) cube = self.solid cube.set_opacity(0.95) cube.move_to(ORIGIN) self.remove(self.plane) self.remove(self.shadow) x_point = VectorizedPoint(cube.get_right()) y_point = VectorizedPoint(cube.get_top()) z_point = VectorizedPoint(cube.get_zenith()) cube.add(x_point, y_point, z_point) def get_matrix(): return np.array([ x_point.get_center(), y_point.get_center(), z_point.get_center(), ]).T def get_mat_mob(): matrix = DecimalMatrix( get_matrix(), element_to_mobject_config=dict( num_decimal_places=2, edge_to_fix=LEFT, include_sign=True, ), h_buff=2.0, element_alignment_corner=LEFT, ) matrix.fix_in_frame() matrix.set_height(1.25) brackets = matrix.get_brackets() brackets[1].move_to(brackets[0].get_center() + 3.45 * RIGHT) matrix.to_corner(UL) return matrix matrix = always_redraw(get_mat_mob) self.add(matrix) # Space of orientations self.begin_ambient_rotation(cube, speed=0.4) self.wait(2) question = Text("What is the space of all orientations?") question.to_corner(UR) question.fix_in_frame() SO3 = OldTex("SO(3)") SO3.next_to(question, DOWN) SO3.set_color(BLUE) SO3.fix_in_frame() self.play(Write(question)) self.wait(2) self.play(FadeIn(SO3, DOWN)) self.wait(2) self.play(SO3.animate.next_to(matrix, DOWN, MED_LARGE_BUFF)) self.wait(5) new_question = Text( "What probability distribution are we placing\n" "on the space of all orientations?", t2c={"probability distribution": YELLOW}, t2s={"probability distribution": ITALIC}, ) new_question.match_width(question) new_question.move_to(question, UP) new_question.fix_in_frame() n = len("the space of all orientations?") self.play( FadeTransform(question[-n:], new_question[-n:]), FadeOut(question[:-n]), FadeIn(new_question[:-n]), ) self.wait() cube.clear_updaters() N = 15 cube_field = cube.get_grid(N, N) cube_field.set_height(10) for n, c in enumerate(cube_field): c.rotate(PI * (n // N) / N, axis=RIGHT) c.rotate(PI * (n % N) / N, axis=UP) for face in c: face.set_stroke(width=0) self.sort_to_camera(c) matrix.clear_updaters() self.play( FadeTransform(cube, cube_field[0]), LaggedStartMap(FadeIn, cube_field, run_time=15, lag_ratio=0.1) ) self.add(cube_field) self.wait() class PauseAndPonder(TeacherStudentsScene): def construct(self): self.remove(self.background) self.teacher_says( OldTexText("The goal is\\\\not speed."), added_anims=[self.change_students( "tease", "well", "pondering", look_at=self.screen )] ) self.wait(2) self.play( RemovePiCreatureBubble(self.teacher, target_mode="tease"), PiCreatureBubbleIntroduction( self.students[2], Lightbulb(), bubble_type=ThoughtBubble, bubble_creation_class=lambda m: FadeIn(m, lag_ratio=0.1), bubble_config=dict( height=3, width=3, direction=LEFT, ), target_mode="thinking", look_at=self.screen, ) ) self.wait(3) self.teacher_says( "Pause and ponder!", target_mode="well", added_anims=[self.change_students( "pondering", "tease", "thinking" )], run_time=1 ) self.wait(5) self.embed() class StartSimple(Scene): def construct(self): # Words title = Text("Universal problem-solving advice") title.set_width(FRAME_WIDTH - 4) title.to_edge(UP) title.set_color(BLUE) title.set_backstroke() line = Underline(title, buff=-0.035) line.set_width(FRAME_WIDTH - 1) line.set_color(BLUE_B) line.set_stroke(width=[0, 3, 3, 3, 0]) line.insert_n_curves(101) words = Text( "Start with the simplest non-trivial\n" "variant of the problem you can." ) words.next_to(line, DOWN, MED_SMALL_BUFF) rect = BackgroundRectangle(words, fill_opacity=1, buff=SMALL_BUFF) words.set_backstroke(width=5) # Shapes cube = VCube() cube.deactivate_depth_test() cube.set_color(BLUE_E) cube.set_opacity(0.75) cube.set_stroke(WHITE, 0.5, 0.5) cube.set_height(2) cube.rotate(PI / 4, [1, 2, 0]) cube.sort(lambda p: p[2]) cube = Group(*cube) cube.set_gloss(1) arrow = Arrow(LEFT, RIGHT) face = cube[np.argmax([f.get_z() for f in cube])].copy() group = Group(cube, arrow, face) group.arrange(RIGHT, buff=MED_LARGE_BUFF) group.next_to(words, DOWN, LARGE_BUFF) group.set_width(2) group.to_edge(RIGHT) group.set_y(0) self.camera.light_source.set_x(-4) self.play( ShowCreation(line), Write(title, run_time=1), ) self.wait() self.play( FadeIn(rect), FadeIn(words, lag_ratio=0.1), run_time=2 ) self.wait() self.play(FlashAround(words.get_part_by_text("non-trivial"), run_time=2)) self.wait() self.play( LaggedStart(*map(DrawBorderThenFill, cube)), ShowCreation(arrow), TransformFromCopy(cube[-1], face) ) self.wait(3) class FocusOnOneFace(ShadowScene): inf_light = True limited_plane_extension = 10 def construct(self): # Some random tumbling cube = self.solid shadow = self.shadow frame = self.camera.frame words = VGroup( Text("Just one orientation"), Text("Just one face"), ) words.fix_in_frame() words.arrange(DOWN, buff=MED_LARGE_BUFF, aligned_edge=LEFT) words.to_corner(UL) average_words = Text("Average over all orientations") average_words.move_to(words[0], LEFT) average_words.fix_in_frame() self.add(average_words) self.random_toss(run_time=3, rate_func=linear) self.play( FadeIn(words[0], 0.75 * UP), FadeOut(average_words, 0.75 * UP), run_time=0.5, ) self.wait() # Just one face cube.update() index = np.argmax([f.get_z() for f in cube]) face = cube[index] prev_opacity = face.get_fill_opacity() cube.generate_target(use_deepcopy=True) cube.target.clear_updaters() cube.target.space_out_submobjects(2, about_point=face.get_center()) cube.target.set_opacity(0) cube.target[index].set_opacity(prev_opacity) self.shadow.set_stroke(width=0) self.play( MoveToTarget(cube), FadeIn(words[1]), ) self.play( frame.animate.reorient(-10, 65), FlashAround(words[1], rate_func=squish_rate_func(smooth, 0.2, 0.5)), FlashAround(words[0], rate_func=squish_rate_func(smooth, 0.5, 0.8)), run_time=5, ) frame.add_updater(lambda f, dt: f.increment_theta(0.01 * dt)) self.solid = face self.remove(shadow) self.add_shadow() shadow = self.shadow # Ask about area area_q = Text("Area?") area_q.add_updater(lambda m: m.move_to(shadow)) self.play(Write(area_q)) self.wait() # Orient straight up unit_normal = face.get_unit_normal() axis = rotate_vector(normalize([*unit_normal[:2], 0]), PI / 2, OUT) angle = np.arccos(unit_normal[2]) face.generate_target() face.target.rotate(-angle, axis) face.target.move_to(3 * OUT) face.target.rotate(-PI / 4, OUT) self.play(MoveToTarget(face)) light_lines = self.get_light_lines(n_lines=4, outline=shadow, only_vertices=True) light_lines.set_stroke(YELLOW, 1, 0.5) self.play( frame.animate.set_phi(70 * DEGREES), FadeIn(light_lines, lag_ratio=0.5), TransformFromCopy(face, face.deepcopy().set_opacity(0).set_z(0), remover=True), run_time=3, ) self.wait(3) self.play( Rotate(face, PI / 2, UP), FadeOut(area_q, scale=0), run_time=3, ) self.wait(3) self.play( Rotate(face, -PI / 3, UP), UpdateFromAlphaFunc(light_lines, lambda m, a: m.set_opacity(0.5 * (1 - a)), remover=True), run_time=2, ) # Show normal vector z_axis = VGroup( Line(ORIGIN, face.get_center()), Line(face.get_center(), 10 * OUT), ) z_axis.set_stroke(WHITE, 1) normal_vect = Vector() get_fc = face.get_center def get_un(): return face.get_unit_normal(recompute=True) def get_theta(): return np.arccos(get_un()[2]) normal_vect.add_updater(lambda v: v.put_start_and_end_on( get_fc(), get_fc() + get_un(), )) arc = always_redraw(lambda: Arc( start_angle=PI / 2, angle=-get_theta(), radius=0.5, stroke_width=2, ).rotate(PI / 2, RIGHT, about_point=ORIGIN).shift(get_fc())) theta = OldTex("\\theta", font_size=30) theta.set_backstroke() theta.rotate(PI / 2, RIGHT) theta.add_updater(lambda m: m.move_to( get_fc() + 1.3 * (arc.pfp(0.5) - get_fc()) )) theta.add_updater(lambda m: m.set_width(min(0.123, max(0.01, arc.get_width())))) self.play(ShowCreation(normal_vect)) self.wait() self.add(z_axis[0], face, z_axis[1], normal_vect) self.play(*map(FadeIn, z_axis)) self.play( FadeIn(theta, 0.5 * OUT), ShowCreation(arc), ) # Vary Theta frame.reorient(2) face.rotate(-35 * DEGREES, get_un(), about_point=face.get_center()) self.play( Rotate(face, 50 * DEGREES, UP), rate_func=there_and_back, run_time=8, ) # Show shadow area in the corner axes = Axes( (0, 180, 22.5), (0, 1, 0.25), width=5, height=2, axis_config={ "include_tip": False, "tick_size": 0.05, "numbers_to_exclude": [], }, ) axes.to_corner(UR, buff=MED_SMALL_BUFF) axes.x_axis.add_numbers([0, 45, 90, 135, 180], unit="^\\circ") y_label = OldTexText("Shadow's area", font_size=24) y_label.next_to(axes.y_axis.get_top(), RIGHT, MED_SMALL_BUFF) y_label.set_backstroke() ly_label = OldTex("s^2", font_size=24) ly_label.next_to(axes.y_axis.get_top(), LEFT, SMALL_BUFF) ly_label.shift(0.05 * UP) axes.add(y_label, ly_label) axes.fix_in_frame() graph = axes.get_graph( lambda x: math.cos(x * DEGREES), x_range=(0, 90), ) graph.set_stroke(RED, 3) graph.fix_in_frame() question = Text("Can you guess?", font_size=36) question.to_corner(UR) question.set_color(RED) dot = Dot(color=RED) dot.scale(0.5) dot.move_to(axes.c2p(0, 1)) dot.fix_in_frame() self.play( FadeIn(axes), Rotate(face, -get_theta(), UP, run_time=2), ) self.play(FadeIn(dot, shift=2 * UP + RIGHT)) self.wait(2) self.add(graph, axes) self.play( UpdateFromFunc(dot, lambda d: d.move_to(graph.get_end())), ShowCreation(graph), Rotate(face, PI / 2, UP), run_time=5 ) self.play(frame.animate.reorient(45), run_time=2) self.play(frame.animate.reorient(5), run_time=4) # Show vertical plane plane = Rectangle(width=self.plane.get_width(), height=5) plane.insert_n_curves(100) plane.set_fill(WHITE, 0.25) plane.set_stroke(width=0) plane.apply_depth_test() plane.rotate(PI / 2, RIGHT) plane.move_to(ORIGIN, IN) plane.save_state() plane.stretch(0, 2, about_edge=IN) face.apply_depth_test() z_axis.apply_depth_test() self.shadow.apply_depth_test() self.play( LaggedStartMap(FadeOut, VGroup(*words, graph, axes, dot)), Restore(plane, run_time=3) ) self.play(Rotate(face, -60 * DEGREES, UP, run_time=2)) # Slice up face face_copy = face.deepcopy() face_copy.rotate(-get_theta(), UP) face_copy.move_to(ORIGIN) n_slices = 25 rects = Rectangle().replicate(n_slices) rects.arrange(DOWN, buff=0) rects.replace(face_copy, stretch=True) slices = VGroup(*(Intersection(face_copy, rect) for rect in rects)) slices.match_style(face_copy) slices.set_stroke(width=0) slices.rotate(get_theta(), UP) slices.move_to(face) slices.apply_depth_test() slices.save_state() slice_outlines = slices.copy() slice_outlines.set_stroke(RED, 1) slice_outlines.set_fill(opacity=0) slice_outlines.deactivate_depth_test() frame.clear_updaters() self.play( frame.animate.set_euler_angles(PI / 2, get_theta()), FadeOut(VGroup(theta, arc)), run_time=2 ) self.play(ShowCreation(slice_outlines, lag_ratio=0.05)) self.remove(face) self.add(slices) self.remove(self.shadow) self.solid = slices self.add_shadow() self.shadow.set_stroke(width=0) self.add(normal_vect, plane, slice_outlines) slices.insert_n_curves(10) slices.generate_target() for sm in slices.target: sm.stretch(0.5, 1) self.play( MoveToTarget(slices), FadeOut(slice_outlines), run_time=2 ) self.wait(2) # Focus on one slice long_slice = slices[len(slices) // 2].deepcopy() line = Line(long_slice.get_corner(LEFT + OUT), long_slice.get_corner(RIGHT + IN)) line.scale(0.97) line.set_stroke(BLUE, 3) frame.generate_target() frame.target.reorient(0, 90) frame.target.set_height(6) frame.target.move_to(2.5 * OUT) self.shadow.clear_updaters() self.play( MoveToTarget(frame), *map(FadeIn, (theta, arc)), FadeOut(plane), FadeOut(slices), FadeOut(self.shadow), FadeIn(line), run_time=2, ) self.wait() # Analyze slice shadow = line.copy() shadow.stretch(0, 2, about_edge=IN) shadow.set_stroke(BLUE_E) vert_line = Line(line.get_start(), shadow.get_start()) vert_line.set_stroke(GREY_B, 3) shadow_label = Text("Shadow") shadow_label.set_fill(BLUE_E) shadow_label.set_backstroke() shadow_label.rotate(PI / 2, RIGHT) shadow_label.next_to(shadow, IN, SMALL_BUFF) self.play( TransformFromCopy(line, shadow), FadeIn(shadow_label, 0.5 * IN), ) self.wait() self.play(ShowCreation(vert_line)) self.wait() top_theta_group = VGroup( z_axis[1].copy(), arc.copy().clear_updaters(), theta.copy().clear_updaters(), Line(*normal_vect.get_start_and_end()).match_style(z_axis[1].copy()), ) self.play( top_theta_group.animate.move_to(line.get_start(), LEFT + IN) ) elbow = Elbow(angle=-get_theta()) elbow.set_stroke(WHITE, 2) ul_arc = Arc( radius=0.4, start_angle=-get_theta(), angle=-(PI / 2 - get_theta()) ) ul_arc.match_style(elbow) supl = OldTex("90^\\circ - \\theta", font_size=24) supl.next_to(ul_arc, DOWN, SMALL_BUFF, aligned_edge=LEFT) supl.set_backstroke() supl[0][:3].shift(SMALL_BUFF * RIGHT / 2) ul_angle_group = VGroup(elbow, ul_arc, supl) ul_angle_group.rotate(PI / 2, RIGHT, about_point=ORIGIN) ul_angle_group.shift(line.get_start()) dr_arc = Arc( radius=0.4, start_angle=PI, angle=-get_theta(), ) dr_arc.match_style(ul_arc) dr_arc.rotate(PI / 2, RIGHT, about_point=ORIGIN) dr_arc.shift(line.get_end()) dr_theta = OldTex("\\theta", font_size=24) dr_theta.rotate(PI / 2, RIGHT) dr_theta.next_to(dr_arc, LEFT, SMALL_BUFF) dr_theta.shift(SMALL_BUFF * OUT / 2) self.play(ShowCreation(elbow)) self.play( ShowCreation(ul_arc), FadeTransform(top_theta_group[2].copy(), supl), ) self.play( TransformFromCopy(ul_arc, dr_arc), TransformFromCopy(supl[0][4].copy().set_stroke(width=0), dr_theta[0][0]), ) self.wait() # Highlight lower right rect = Rectangle(0.8, 0.5) rect.set_stroke(YELLOW, 2) rect.rotate(PI / 2, RIGHT) rect.move_to(dr_theta, LEFT).shift(SMALL_BUFF * LEFT) self.play( ShowCreation(rect), top_theta_group.animate.fade(0.8), ul_angle_group.animate.fade(0.8), ) self.wait() # Show cosine cos_formula = OldTex( "\\cos(\\theta)", "=", "{\\text{Length of }", "\\text{shadow}", "\\over", "\\text{Length of }", "\\text{slice}" "}", ) cos_formula[2:].scale(0.75, about_edge=LEFT) cos_formula.to_corner(UR) cos_formula.fix_in_frame() lower_formula = OldTex( "\\text{shadow}", "=", "\\cos(\\theta)", "\\cdot", "\\text{slice}" ) lower_formula.match_width(cos_formula) lower_formula.next_to(cos_formula, DOWN, MED_LARGE_BUFF) lower_formula.fix_in_frame() for tex in cos_formula, lower_formula: tex.set_color_by_tex("shadow", BLUE_D) tex.set_color_by_tex("slice", BLUE_B) self.play(Write(cos_formula)) self.wait() self.play(TransformMatchingTex( VGroup(*(cos_formula[i].copy() for i in [0, 1, 3, 6])), lower_formula, path_arc=PI / 4, )) self.wait() # Bring full face back frame.generate_target() frame.target.reorient(20, 75) frame.target.set_height(6) frame.target.set_z(2) line_shadow = get_shadow(line) line_shadow.set_stroke(BLUE_E, opacity=0.5) self.solid = face self.add_shadow() self.add(z_axis[0], face, z_axis[1], line, normal_vect, theta, arc) self.play( MoveToTarget(frame, run_time=5), FadeIn(face, run_time=3), FadeIn(self.shadow, run_time=3), FadeIn(line_shadow, run_time=3), LaggedStart(*map(FadeOut, [ top_theta_group, ul_angle_group, rect, dr_theta, dr_arc, vert_line, shadow, shadow_label, ]), run_time=4), ) frame.add_updater(lambda f, dt: f.increment_theta(0.01 * dt)) self.wait(2) # Show perpendicular perp = Line( face.pfp(binary_search( lambda a: face.pfp(a)[2], face.get_center()[2], 0, 0.5, )), face.pfp(binary_search( lambda a: face.pfp(a)[2], face.get_center()[2], 0.5, 1.0, )), ) perp.set_stroke(RED, 3) perp_shadow = get_shadow(perp) perp_shadow.set_stroke(RED_E, 3, opacity=0.2) self.add(perp, normal_vect, arc) self.play( ShowCreation(perp), ShowCreation(perp_shadow), ) face.add(line) self.play(Rotate(face, 45 * DEGREES, UP), run_time=3) self.play(Rotate(face, -55 * DEGREES, UP), run_time=3) self.play(Rotate(face, 20 * DEGREES, UP), run_time=2) # Give final area formula final_formula = OldTex( "\\text{Area}(", "\\text{shadow}", ")", "=", "|", "\\cos(\\theta)", "|", "s^2" ) final_formula.set_color_by_tex("shadow", BLUE_D) final_formula.match_width(lower_formula) final_formula.next_to(lower_formula, DOWN, MED_LARGE_BUFF) final_formula.fix_in_frame() final_formula.get_parts_by_tex("|").set_opacity(0) final_formula.set_stroke(BLACK, 3, background=True) rect = SurroundingRectangle(final_formula) rect.set_stroke(YELLOW, 2) rect.fix_in_frame() self.play(Write(final_formula)) self.play(ShowCreation(rect)) final_formula.add(rect) self.wait(10) # Absolute value face.remove(line) self.play( frame.animate.shift(0.5 * DOWN + RIGHT).reorient(10), LaggedStart(*map(FadeOut, [cos_formula, lower_formula])), FadeIn(graph), FadeIn(axes), FadeOut(line), FadeOut(line_shadow), FadeOut(perp), FadeOut(perp_shadow), final_formula.animate.shift(2 * DOWN), run_time=2 ) self.play( Rotate(face, PI / 2 - get_theta(), UP), run_time=2 ) new_graph = axes.get_graph( lambda x: math.cos(x * DEGREES), (90, 180), ) new_graph.match_style(graph) new_graph.fix_in_frame() self.play( Rotate(face, PI / 2, UP), ShowCreation(new_graph), run_time=5, ) self.play( Rotate(face, -PI / 4, UP), run_time=2, ) self.wait(3) alt_normal = normal_vect.copy() alt_normal.clear_updaters() alt_normal.rotate(PI, UP, about_point=face.get_center()) alt_normal.set_color(YELLOW) self.add(alt_normal, face, normal_vect, arc, theta) self.play(ShowCreation(alt_normal)) self.wait() self.play(FadeOut(alt_normal)) new_graph.generate_target() new_graph.target.flip(RIGHT) new_graph.target.move_to(graph.get_end(), DL) self.play( MoveToTarget(new_graph), final_formula.get_parts_by_tex("|").animate.set_opacity(1), ) self.play( final_formula.animate.next_to(axes, DOWN) ) self.wait() self.play(Rotate(face, -PI / 2, UP), run_time=5) self.wait(10) class NotQuiteRight(TeacherStudentsScene): def construct(self): self.remove(self.background) self.teacher_says( "Not quite right...", target_mode="hesitant", bubble_config={"height": 3, "width": 4}, added_anims=[ self.change_students( "pondering", "thinking", "erm", look_at=self.screen, ) ] ) self.wait(4) class DiscussLinearity(Scene): def construct(self): # Set background background = FullScreenRectangle() self.add(background) panels = Rectangle(4, 4).replicate(3) panels.set_fill(BLACK, 1) panels.set_stroke(WHITE, 2) panels.set_height(FRAME_HEIGHT - 1) panels.arrange(RIGHT, buff=LARGE_BUFF) panels.set_width(FRAME_WIDTH - 1) panels.center() self.add(panels) # Arrows arrows = VGroup(*( Arrow( p1.get_top(), p2.get_top(), path_arc=-0.6 * PI ).scale(0.75, about_edge=DOWN) for p1, p2 in zip(panels, panels[1:]) )) arrows.space_out_submobjects(0.8) arrows.rotate(PI, RIGHT, about_point=panels.get_center()) arrow_labels = VGroup( Text("Rotation", font_size=30), Text("Flat projection", font_size=30), ) arrow_labels.set_backstroke() for arrow, label in zip(arrows, arrow_labels): label.next_to(arrow.pfp(0.5), UP, buff=0.35) shape_labels = VGroup( Text("Some shape"), Text("Any shape"), ) shape_labels.next_to(panels[0].get_top(), UP, SMALL_BUFF) # self.play(Write(shape_labels[0], run_time=1)) # self.wait() for arrow, label in zip(arrows, arrow_labels): self.play( ShowCreation(arrow), FadeIn(label, lag_ratio=0.1) ) self.wait() # Linear! lin_text = Text( "Both are linear transformations!", t2c={"linear": YELLOW} ) lin_text.next_to(panels, UP, MED_SMALL_BUFF) self.play(FadeIn(lin_text, lag_ratio=0.1)) self.wait() # Stretch words uniform_words = Text("Uniform stretching here", font_size=36).replicate(2) for words, panel in zip(uniform_words, panels[0::2]): words.next_to(panel.get_top(), DOWN, SMALL_BUFF) words.set_color(YELLOW) words.set_backstroke() self.play( FadeIn(words, lag_ratio=0.1), ) self.wait() # Transition lin_part = lin_text.get_part_by_text("linear") lin_copies = lin_part.copy().replicate(2) lin_copies.scale(0.6) for lin_copy, arrow in zip(lin_copies, arrows): lin_copy.next_to(arrow.pfp(0.5), DOWN, buff=0.15) self.play( TransformFromCopy(lin_part.replicate(2), lin_copies), LaggedStart( FadeOut(lin_text, lag_ratio=0.1), *map(FadeOut, uniform_words) ) ) # Areas area_labels = VGroup( Text("Area(shape)", t2c={"shape": BLUE}), Text("Area(shadow)", t2c={"shadow": BLUE_E}), ) area_exprs = VGroup( OldTex("A").set_color(BLUE), OldTex("(\\text{some factor})", "\\cdot ", "A"), ) area_exprs[1][2].set_color(BLUE) area_exprs[1][0].set_color(GREY_C) equals = VGroup() for label, expr, panel in zip(area_labels, area_exprs, panels[0::2]): label.match_x(panel) label.to_edge(UP, buff=MED_SMALL_BUFF) eq = OldTex("=") eq.rotate(PI / 2) eq.next_to(label, DOWN, buff=0.15) equals.add(eq) expr.next_to(eq, DOWN, buff=0.15) self.play( *map(Write, area_labels), run_time=1 ) self.play( *(FadeIn(eq, 0.5 * DOWN) for eq in equals), *(FadeIn(expr, DOWN) for expr in area_exprs), ) self.wait() f_rot = OldTex("f(\\text{Rot})") f_rot.set_color(GREY_B) times_A = area_exprs[1][1:] f_rot.next_to(times_A, LEFT, buff=0.2) times_A.generate_target() VGroup(f_rot, times_A.target).match_x(panels[2]) self.play( FadeTransform(area_exprs[1][0], f_rot), MoveToTarget(times_A) ) self.play(ShowCreationThenFadeAround(f_rot, run_time=2)) self.wait(1) # Determinant factor = area_exprs[1].get_part_by_tex('factor') rect = SurroundingRectangle(factor, buff=SMALL_BUFF) rect.set_stroke(YELLOW, 2) rot = Matrix([["v_1", "w_1"], ["v_2", "w_2"], ["v_3", "w_3"]], h_buff=1.0) rot.set_column_colors(GREEN, RED) proj = Matrix([["1", "0", "0"], ["0", "1", "0"]], h_buff=0.6) prod = VGroup(proj, rot) prod.arrange(RIGHT, buff=SMALL_BUFF) prod.set_height(0.8) det = OldTex( "\\text{det}", "\\Big(", "\\Big)", tex_to_color_map={ "\\text{det}": YELLOW, # "rot": BLUE_D, # "proj": BLUE_B, }, font_size=36 ) det[1:].match_height(prod, stretch=True) det.to_edge(UP) prod.next_to(det[1], RIGHT, SMALL_BUFF) det[2].next_to(prod, RIGHT, SMALL_BUFF) det.add(prod) det.center().to_edge(UP, buff=0.25) det_rect = SurroundingRectangle(det, buff=SMALL_BUFF) det_rect.set_stroke(YELLOW, 1) rot_brace = Brace(rot, DOWN, buff=SMALL_BUFF) details = Text("Need to work out rotation matrix...", font_size=20) details.next_to(rot_brace, DOWN, SMALL_BUFF) details.set_color(GREY_A) arrow = Arrow(rect.get_corner(UL), det.get_right()) arrow.set_color(YELLOW) self.play(ShowCreation(rect)) self.play( FadeTransform(rect.copy(), det_rect), FadeTransform(factor.copy(), det), ShowCreation(arrow) ) self.wait() self.play( FadeOut(det_rect), GrowFromCenter(rot_brace), FadeIn(details), ) self.wait() self.play(LaggedStart(*map(FadeOut, ( *det, rot_brace, details )), lag_ratio=0.3, run_time=2)) # Any shape ind_words = Text("Independent of the shape!", font_size=30) ind_words.move_to(det) ind_words.set_color(GREEN) self.play( arrow.animate.match_points(Arrow(factor.get_corner(UL), ind_words.get_corner(DR))), Write(ind_words, run_time=1), ) self.wait() self.play(LaggedStart(*map(FadeOut, (ind_words, arrow, rect)))) self.wait() # Cross out right cross = Cross(VGroup(equals[1], f_rot, times_A)) cross.insert_n_curves(20) self.play(ShowCreation(cross)) self.wait(3) class Matrices(Scene): def construct(self): self.add(FullScreenRectangle()) kw = { "v_buff": 0.7, "bracket_v_buff": 0.15, "bracket_h_buff": 0.15, } matrices = VGroup( Matrix([["v_1", "w_1"], ["v_2", "w_2"], ["v_3", "w_3"]], h_buff=1.0, **kw), Matrix([["1", "0", "0"], ["0", "1", "0"]], h_buff=0.6, **kw), ) matrices.set_color(GREY_A) matrices[0].set_column_colors(GREEN, RED) matrices.arrange(LEFT, buff=SMALL_BUFF) matrices.scale(0.5) mat_product = matrices[:2].copy() vectors = VGroup( Matrix([["x_0"], ["y_0"]], **kw), Matrix([["x_1"], ["y_1"], ["z_1"]], **kw), Matrix([["x_2"], ["y_2"]], **kw), ) for vect, x in zip(vectors, [-6, 0, 6]): vect.set_x(x) vect.set_y(2.5) arrows = VGroup( Arrow(vectors[0], vectors[1]), Arrow(vectors[1], vectors[2]), Arrow(vectors[0], vectors[2]), ) for mat, arrow in zip((*matrices[:2], mat_product), arrows): mat.next_to(arrow, UP, SMALL_BUFF) # Animations self.add(vectors[0]) for i in range(2): self.play( FadeTransform(vectors[i].copy(), vectors[i + 1]), ShowCreation(arrows[i]), FadeIn(matrices[i], 0.5 * RIGHT) ) self.wait() self.play( Transform(arrows[0], arrows[2]), Transform(arrows[1], arrows[2]), Transform(matrices, mat_product), FadeOut(vectors[1], scale=0), ) class DefineDeterminant(Scene): def construct(self): # Planes plane = NumberPlane((-2, 2), (-3, 3)) plane.set_height(FRAME_HEIGHT) planes = VGroup(plane, plane.deepcopy()) planes[0].to_edge(LEFT, buff=0) planes[1].to_edge(RIGHT, buff=0) planes[1].set_stroke(GREY_A, 1, 0.5) planes[1].faded_lines.set_opacity(0) titles = VGroup( Text("Input"), Text("Output"), ) for title, plane in zip(titles, planes): title.next_to(plane.get_top(), DOWN) title.add_background_rectangle() self.add(planes) # Area square = Square() square.set_stroke(YELLOW, 2) square.set_fill(YELLOW, 0.5) square.replace(Line(planes[0].c2p(-1, -1), planes[0].c2p(1, 1))) area_label = OldTexText("Area", "=", "$A$") area_label.set_color_by_tex("$A$", YELLOW) area_label.next_to(square, UP) area_label.add_background_rectangle() self.play( DrawBorderThenFill(square), FadeIn(area_label, 0.25 * UP, rate_func=squish_rate_func(smooth, 0.5, 1)) ) self.wait() # Arrow arrow = Arrow(*planes) arrow_label = Text("Linear transformation", font_size=30) arrow_label.next_to(arrow, UP) mat_mob = Matrix([["a", "b"], ["c", "d"]], h_buff=0.7, v_buff=0.7) mat_mob.set_height(0.7) mat_mob.next_to(arrow, DOWN) # Apply matrix matrix = [ [0.5, 0.4], [0.25, 0.75], ] for mob in planes[0], square: mob.output = mob.deepcopy() mob.output.apply_matrix(matrix, about_point=planes[0].c2p(0, 0)) mob.output.move_to(planes[1].get_center()) planes[0].output.set_stroke(width=1, opacity=1) planes[0].output.faded_lines.set_opacity(0) self.play( ReplacementTransform(planes[0].copy().fade(1), planes[0].output, run_time=2), ReplacementTransform(square.copy().fade(1), square.output, run_time=2), ShowCreation(arrow), FadeIn(arrow_label, 0.25 * RIGHT), FadeIn(mat_mob, 0.25 * RIGHT), ) self.wait() # New area new_area_label = OldTex( "\\text{Area} = ", "{c}", "\\cdot", "{A}", tex_to_color_map={ "{c}": RED, "{A}": YELLOW, } ) new_area_label.add_background_rectangle() new_area_label.next_to(square.output, UP) new_area_label.shift(0.5 * RIGHT) mmc = mat_mob.copy() mmc.scale(1.5) det = VGroup(get_det_text(mmc), mmc) det.set_height(new_area_label.get_height() * 1.2) det.move_to(new_area_label.get_part_by_tex("c"), RIGHT) det.match_y(new_area_label[-1]) det_name = OldTexText("``Determinant''", font_size=36) det_name.next_to(det, UP, MED_LARGE_BUFF) det_name.set_color(RED) det_name.add_background_rectangle() self.play(FadeTransform(area_label.copy(), new_area_label)) self.wait() self.play( FadeTransform(mat_mob.copy(), det), FadeTransform(new_area_label.get_part_by_tex("c"), det_name), new_area_label[1].animate.next_to(det, LEFT, SMALL_BUFF).match_y(new_area_label[1]), ) self.wait() class AmbientShapeRotationPreimage(ShadowScene): inf_light = False display_mode = "preimage_only" # Or "full_3d" or "shadow_only" rotate_in_3d = True only_show_shadow = False def construct(self): # Setup display_mode = self.display_mode frame = self.camera.frame frame.set_height(6) light = self.light light.move_to(75 * OUT) shape = self.solid fc = 2.5 * OUT shape.move_to(fc) self.solid.rotate(-0.5 * PI) self.solid.insert_n_curves(20) preimage = self.solid.deepcopy() preimage.move_to(ORIGIN) rotated = self.solid self.remove(self.shadow) shadow = rotated.deepcopy() shadow.set_fill(interpolate_color(BLUE_E, BLACK, 0.5), 0.7) shadow.set_stroke(BLACK, 1) def update_shadow(shadow): shadow.set_points( np.apply_along_axis( lambda p: project_to_xy_plane(self.light.get_center(), p), 1, rotated.get_points() ) ) shadow.refresh_triangulation() return shadow shadow.add_updater(update_shadow) rotated.axis_tracker = VectorizedPoint(RIGHT) rotated.angle_tracker = ValueTracker(0) rotated.rot_speed_tracker = ValueTracker(0.15) def update_rotated(mob, dt): mob.set_points(preimage.get_points()) mob.shift(fc) mob.refresh_triangulation() axis = mob.axis_tracker.get_location() angle = mob.angle_tracker.get_value() speed = mob.rot_speed_tracker.get_value() mob.axis_tracker.rotate(speed * dt, axis=OUT, about_point=ORIGIN) mob.angle_tracker.increment_value(speed * dt) mob.rotate(angle, axis, about_point=fc) return rotated rotated.add_updater(update_rotated) # Conditionals if display_mode == "full_3d": preimage.set_opacity(0) self.add(shadow) self.add(rotated) z_axis = VGroup( Line(ORIGIN, fc), Line(fc, 10 * OUT), ) z_axis.set_stroke(WHITE, 1) self.add(z_axis[0], rotated, z_axis[1]) orientation_arrows = VGroup( Vector(RIGHT, stroke_color=RED_D), Vector(UP, stroke_color=GREEN_D), Vector(OUT, stroke_color=BLUE_D), ) orientation_arrows.set_stroke(opacity=0.85) orientation_arrows.shift(fc) orientation_arrows.save_state() orientation_arrows.add_updater(lambda m: m.restore().rotate( rotated.angle_tracker.get_value(), rotated.axis_tracker.get_location(), )) orientation_arrows.add_updater(lambda m: m.shift(fc - m[0].get_start())) orientation_arrows.apply_depth_test() self.add(orientation_arrows) proj_lines = always_redraw(lambda: VGroup(*( Line( rotated.pfp(a), flat_project(rotated.pfp(a)) ).set_stroke(WHITE, 0.5, 0.2) for a in np.linspace(0, 1, 100) ))) self.add(proj_lines) frame.reorient(20, 70) self.init_frame_rotation() # frame_speed = -0.02 # frame.add_updater(lambda f, dt: f.increment_theta(frame_speed * dt)) elif display_mode == "shadow_only": frame.reorient(0, 0) frame.set_height(3) rotated.set_opacity(0) preimage.set_opacity(0) self.glow.set_opacity(0.2) self.add(rotated) self.add(shadow) elif display_mode == "preimage_only": self.glow.set_opacity(0) self.remove(self.plane) self.add(preimage) frame.reorient(0, 0) frame.set_height(3) rotated.set_opacity(0) # Just hang around self.wait(15) # Change to cat cat = SVGMobject("cat_outline").family_members_with_points()[0] dog = SVGMobject("dog_outline").family_members_with_points()[0] dog.insert_n_curves(87) for mob in cat, dog: mob.match_style(preimage) mob.replace(preimage, dim_to_match=0) pass # Stretch self.play(rotated.rot_speed_tracker.animate.set_value(0)) rotated.rot_speed = 0 for axis, diag in zip((0, 1, 0, 1), (False, False, True, True)): preimage.generate_target() if diag: preimage.target.rotate(PI / 4) preimage.target.stretch(2, axis) if diag: preimage.target.rotate(-PI / 4) self.play( MoveToTarget(preimage), rate_func=there_and_back, run_time=4 ) self.wait(5) self.play(rotated.rot_speed_tracker.animate.set_value(0.1)) # Change shape cat = SVGMobject("cat_outline").family_members_with_points()[0] dog = SVGMobject("dog_outline").family_members_with_points()[0] dog.insert_n_curves(87) for mob in cat, dog: mob.match_style(preimage) mob.replace(preimage, dim_to_match=0) self.play(Transform(preimage, cat, run_time=4)) cat.insert_n_curves(87) preimage.become(cat) self.wait(2) # More shape changes self.play( preimage.animate.scale(2), rate_func=there_and_back, run_time=3, ) self.play( preimage.animate.become(dog), path_arc=PI, rate_func=there_and_back_with_pause, # Or rather, with paws... run_time=5, ) self.wait(6) # Bring light source closer self.play(rotated.rot_speed_tracker.animate.set_value(0)) anims = [ light.animate.move_to(4 * OUT) ] angle = rotated.angle_tracker.get_value() angle_anim = rotated.angle_tracker.animate.set_value(np.round(angle / TAU, 0) * TAU) if self.display_mode == "full_3d": light_lines = self.get_light_lines(shadow) lso = light_lines[0].get_stroke_opacity() pso = proj_lines[0].get_stroke_opacity() proj_lines.clear_updaters() anims += [ UpdateFromAlphaFunc(proj_lines, lambda m, a: m.set_stroke(opacity=pso * (1 - a))), UpdateFromAlphaFunc(light_lines, lambda m, a: m.set_stroke(opacity=lso * a)), angle_anim, frame.animate.reorient(20, 70).set_height(8), ] frame.clear_updaters() if self.display_mode == "shadow_only": anims += [ frame.animate.set_height(10), angle_anim, ] self.play(*anims, run_time=4) self.wait() rotated.axis_tracker.move_to(UP) self.play( rotated.angle_tracker.animate.set_value(70 * DEGREES + TAU), run_time=2 ) self.play( preimage.animate.stretch(1.5, 0), rate_func=there_and_back, run_time=5, ) anims = [rotated.axis_tracker.animate.move_to(RIGHT)] if self.display_mode == "full_3d": anims.append(frame.animate.reorient(-20, 70)) self.play(*anims, run_time=2) self.play( preimage.animate.stretch(2, 1), rate_func=there_and_back, run_time=7, ) # More ambient motion self.play(rotated.rot_speed_tracker.animate.set_value(0.1)) self.wait(30) def get_solid(self): face = Square(side_length=2) face.set_fill(BLUE, 0.5) face.set_stroke(WHITE, 1) return face class AmbientShapeRotationFull3d(AmbientShapeRotationPreimage): display_mode = "full_3d" class AmbientShapeRotationShadowOnly(AmbientShapeRotationPreimage): display_mode = "shadow_only" class IsntThatObvious(TeacherStudentsScene): def construct(self): self.remove(self.background) self.student_says( OldTexText("Isn't that obvious?"), bubble_config={ "height": 3, "width": 4, "direction": LEFT, }, target_mode="angry", look_at=self.screen, added_anims=[LaggedStart( self.teacher.change("guilty"), self.students[0].change("pondering", self.screen), self.students[1].change("erm", self.screen), )] ) self.wait(2) self.play( self.students[0].change("hesitant"), ) self.wait(2) class StretchLabel(Scene): def construct(self): label = VGroup( Vector(0.5 * LEFT), OldTex("1.5 \\times"), Vector(0.5 * RIGHT) ) label.set_color(YELLOW) label.arrange(RIGHT, buff=SMALL_BUFF) self.play( *map(ShowCreation, label[::2]), Write(label[1]), ) self.wait() class WonderAboutAverage(Scene): def construct(self): randy = Randolph() randy.to_edge(DOWN) randy.look(RIGHT) self.play(PiCreatureBubbleIntroduction( randy, OldTexText("How do you think\\\\about this average"), target_mode="confused", run_time=2 )) for x in range(2): self.play(Blink(randy)) self.wait(2) class SingleFaceRandomRotation(ShadowScene): initial_wait_time = 0 inf_light = True n_rotations = 1 total_time = 60 plane_dims = (8, 8) frame_rot_speed = 0.02 theta0 = -20 * DEGREES CONFIG = {"random_seed": 0} def setup(self): super().setup() np.random.seed(self.random_seed) frame = self.camera.frame frame.set_height(5.0) frame.set_z(1.75) frame.set_theta(self.theta0) face = self.solid face.shift(0.25 * IN) fc = face.get_center() z_axis = self.z_axis = VGroup(Line(ORIGIN, fc), Line(fc, 10 * OUT)) z_axis.set_stroke(WHITE, 0.5) self.add(z_axis[0], face, z_axis[1]) arrows = VGroup( Line(ORIGIN, RIGHT, color=RED_D), Line(ORIGIN, UP, color=GREEN_D), VGroup( Vector(OUT, stroke_width=4, stroke_color=BLACK), Vector(OUT, stroke_width=3, stroke_color=BLUE_D), ) ) arrows[:2].set_stroke(width=2) arrows.set_stroke(opacity=0.8) arrows.shift(fc) arrows.set_stroke(opacity=0.8) face.add(arrows[:2]) face = Group(face, arrows[2]) face.add_updater(lambda m: self.sort_to_camera(face)) self.face = self.solid = face arrow_shadow = get_shadow(arrows) arrow_shadow.set_stroke(width=1) arrow_shadow[2].set_stroke(width=[1, 1, 4, 0]) self.add(arrow_shadow) self.add(z_axis[0], face, z_axis[1]) def construct(self): frame = self.camera.frame face = self.face frame.add_updater(lambda f, dt: f.increment_theta(self.frame_rot_speed * dt)) self.wait(self.initial_wait_time) for x in range(self.n_rotations): self.random_toss( face, about_point=fc, angle=3 * PI, # run_time=1.5, run_time=8, rate_func=smooth, ) self.wait() self.wait(self.total_time - 2 - self.initial_wait_time) def get_solid(self): face = Square(side_length=2) face.set_fill(BLUE_E, 0.75) face.set_stroke(WHITE, 0.5) return face class RandomRotations1(SingleFaceRandomRotation): initial_wait_time = 1 theta0 = -30 * DEGREES CONFIG = {"random_seed": 10} class RandomRotations2(SingleFaceRandomRotation): initial_wait_time = 1.5 theta0 = -25 * DEGREES CONFIG = {"random_seed": 4} class RandomRotations3(SingleFaceRandomRotation): initial_wait_time = 2 theta0 = -20 * DEGREES CONFIG = {"random_seed": 5} class RandomRotations4(SingleFaceRandomRotation): initial_wait_time = 2.5 theta0 = -15 * DEGREES CONFIG = {"random_seed": 6} class AverageFaceShadow(SingleFaceRandomRotation): inf_light = True plane_dims = (16, 8) n_samples = 50 def construct(self): # Random shadows self.camera.frame.set_height(6) face = self.face shadow = self.shadow shadow.add_updater(lambda m: m.set_fill(BLACK, 0.25)) shadow.update() point = face[0].get_center() shadows = VGroup() n_samples = self.n_samples self.remove(self.z_axis) self.init_frame_rotation() self.add(shadows) for n in range(n_samples): self.randomly_reorient(face, about_point=point) if n == n_samples - 1: normal = next( sm.get_unit_normal() for sm in face.family_members_with_points() if isinstance(sm, VMobject) and sm.get_fill_opacity() > 0 ) mat = z_to_vector(normal) # face.apply_matrix(np.linalg.inv(mat), about_point=point) shadow.update() sc = shadow.copy() sc.clear_updaters() shadows.add(sc) shadows.set_fill(BLACK, 1.5 / len(shadows)) shadows.set_stroke(opacity=10 / len(shadows)) self.wait(0.1) # Fade out shadow self.remove(shadow) sc = shadow.copy().clear_updaters() self.play(FadeOut(sc)) self.wait() # Scaling self.play( face.animate.scale(0.5, about_point=point), shadows.animate.scale(0.5, about_point=ORIGIN), run_time=3, rate_func=there_and_back, ) for axis in [0, 1]: self.play( face.animate.stretch(2, axis, about_point=point), shadows.animate.stretch(2, axis, about_point=ORIGIN), run_time=3, rate_func=there_and_back, ) self.wait() # Ambient rotations 106 plays self.play( self.camera.frame.animate.reorient(-10).shift(2 * LEFT), ) self.add(shadow) for n in range(100): self.randomly_reorient(face, about_point=point) self.wait(0.2) class AverageCatShadow(AverageFaceShadow): n_samples = 50 def setup(self): super().setup() self.replace_face() def replace_face(self): face = self.face shape = self.get_shape().family_members_with_points()[0] shape.match_style(face[0]) shape.replace(face[0]) face[0].set_points(shape.get_points()) face[0].set_gloss(0.25) face[0][0].set_gloss(0) self.solid = face self.remove(self.shadow) self.add_shadow() def get_shape(self): return SVGMobject("cat_outline") class AveragePentagonShadow(AverageCatShadow): def get_shape(self): return RegularPolygon(5) class AverageShadowAnnotation(Scene): def construct(self): # Many shadows many_shadows = Text("Many shadows") many_shadows.move_to(3 * DOWN) self.play(Write(many_shadows)) self.wait(2) # Formula # shape_name = "2d shape" shape_name = "Square" t2c = { "Shadow": GREY_B, shape_name: BLUE, "$c$": RED, } formula = VGroup( OldTexText( f"Area(Shadow({shape_name}))", tex_to_color_map=t2c, ), OldTex("=").rotate(PI / 2), OldTexText( "$c$", " $\\cdot$", f"(Area({shape_name}))", tex_to_color_map=t2c ) ) overline = get_overline(formula[0]) formula[0].add(overline) formula.arrange(DOWN) formula.to_corner(UL) self.play(FadeTransform(many_shadows, formula[0])) self.wait() self.play( VShowPassingFlash( overline.copy().insert_n_curves(100).set_stroke(YELLOW, 5), time_width=0.75, run_time=2, ) ) self.wait() self.play( Write(formula[1]), FadeIn(formula[2], DOWN) ) self.wait() # Append half half = OldTex("\\frac{1}{2}") half.set_color(RED) c = formula[2].get_part_by_tex("$c$") half.move_to(c, RIGHT) self.play( FadeOut(c, 0.5 * UP), FadeIn(half, 0.5 * UP), ) self.wait() class AlicesFaceAverage(Scene): def construct(self): # Background background = FullScreenRectangle() self.add(background) panels = Rectangle(2, 2.5).replicate(5) panels.set_stroke(WHITE, 1) panels.set_fill(BLACK, 1) dots = OldTex("\\dots") panels.replace_submobject(3, dots) panels.arrange(RIGHT, buff=0.25) panels.set_width(FRAME_WIDTH - 1) panels.move_to(2 * DOWN, DOWN) self.add(panels) panels = VGroup(*panels[:-2], panels[-1]) # Label the rotations indices = ["1", "2", "3", "n"] rot_labels = VGroup(*( OldTex(f"R_{i}") for i in indices )) for label, panel in zip(rot_labels, panels): label.set_height(0.3) label.next_to(panel, DOWN) rot_words = Text("Sequence of random rotations") rot_words.next_to(rot_labels, DOWN, MED_LARGE_BUFF) self.play(Write(rot_words, run_time=2)) self.wait(2) self.play(LaggedStartMap( FadeIn, rot_labels, shift=0.25 * DOWN, lag_ratio=0.5 )) self.wait() # Show the shadow areas font_size = 30 fra_labels = VGroup(*( OldTex( f"f(R_{i})", "\\cdot ", "A", tex_to_color_map={"A": BLUE}, font_size=font_size ) for i in indices )) DARK_BLUE = interpolate_color(BLUE_D, BLUE_E, 0.5) area_shadow_labels = VGroup(*( OldTex( "\\text{Area}(", "\\text{Shadow}_" + i, ")", tex_to_color_map={"\\text{Shadow}_" + i: DARK_BLUE}, font_size=font_size ) for i in indices )) s_labels = VGroup(*( OldTex( f"S_{i}", "=", tex_to_color_map={f"S_{i}": DARK_BLUE}, font_size=font_size ) for i in indices )) label_arrows = VGroup() for fra, area, s_label, panel in zip(fra_labels, area_shadow_labels, s_labels, panels): fra.next_to(panel, UP, SMALL_BUFF) area.next_to(fra, UP) area.to_edge(UP, buff=LARGE_BUFF) label_arrows.add(Arrow(area, fra, buff=0.2, stroke_width=3)) fra.generate_target() eq = VGroup(s_label, fra.target) eq.arrange(RIGHT, buff=SMALL_BUFF) eq.move_to(fra, DOWN) self.add(area_shadow_labels) self.add(fra_labels) self.add(label_arrows) lr = 0.2 self.play( LaggedStartMap(FadeIn, area_shadow_labels, lag_ratio=lr), LaggedStartMap(ShowCreation, label_arrows, lag_ratio=lr), LaggedStartMap(FadeIn, fra_labels, shift=DOWN, lag_ratio=lr), ) self.wait() self.play( LaggedStart(*( FadeTransform(area, area_s) for area, area_s in zip(area_shadow_labels, s_labels) ), lag_ratio=lr), LaggedStartMap(MoveToTarget, fra_labels, lag_ratio=lr), LaggedStartMap(Uncreate, label_arrows, lag_ratio=lr), ) # Show average sample_average = OldTex( "\\text{Sample average}", "=", "\\frac{1}{n}", "\\left(", "f(R_1)", "\\cdot ", "A", "+", "f(R_2)", "\\cdot ", "A", "+", "f(R_3)", "\\cdot ", "A", "+", "\\cdots ", "f(R_n)", "\\cdot ", "A", "\\right)", font_size=font_size ) sample_average.set_color_by_tex("A", BLUE) sample_average.to_edge(UP, buff=MED_SMALL_BUFF) for tex in ["\\left", "\\right"]: part = sample_average.get_part_by_tex(tex) part.scale(1.5) part.stretch(1.5, 1) self.play(FadeIn(sample_average[:2])) self.play( TransformMatchingShapes( fra_labels.copy(), sample_average[4:-1] ) ) self.wait() self.play( Write(VGroup(*sample_average[2:4], sample_average[-1])) ) self.wait() # Factor out A sample_average.generate_target() cdots = sample_average.target.get_parts_by_tex("\\cdot", substring=False) As = sample_average.target.get_parts_by_tex("A", substring=False) new_pieces = VGroup(*( sm for sm in sample_average.target if sm.get_tex() not in ["A", "\\cdot"] )) new_A = As[0].copy() new_cdot = cdots[0].copy() new_pieces.insert_submobject(2, new_cdot) new_pieces.insert_submobject(2, new_A) new_pieces.arrange(RIGHT, buff=SMALL_BUFF) new_pieces.move_to(sample_average) for group, target in (As, new_A), (cdots, new_cdot): for sm in group: sm.replace(target) group[1:].set_opacity(0) self.play(LaggedStart( *( FlashAround(mob, time_width=3) for mob in sample_average.get_parts_by_tex("A") ), lag_ratio=0.1, run_time=2 )) self.play(MoveToTarget(sample_average, path_arc=-PI / 5)) self.wait() # True average brace = Brace(new_pieces[4:], DOWN, buff=SMALL_BUFF, font_size=30) lim = OldTex("n \\to \\infty", font_size=30) lim.next_to(brace, DOWN) VGroup(brace, lim).set_color(YELLOW) sample = sample_average[0][:len("Sample")] cross = Cross(sample) cross.insert_n_curves(20) cross.scale(1.5) self.play( FlashAround(sample_average[2:], run_time=3, time_width=1.5) ) self.play( FlashUnder(sample_average[:2], color=RED), run_time=2 ) self.play( GrowFromCenter(brace), FadeIn(lim, 0.25 * DOWN), ShowCreation(cross) ) self.play( LaggedStart(*map(FadeOut, [ *fra_labels, *s_labels, *panels, dots, *rot_labels, rot_words ])) ) self.wait() # Some constant rect = SurroundingRectangle( VGroup(new_pieces[4:], brace, lim), buff=SMALL_BUFF, ) rect.set_stroke(YELLOW, 1) rect.stretch(0.98, 0) words = Text("Some constant") words.next_to(rect, DOWN) subwords = Text("Independent of the size and shape of the 2d piece") subwords.scale(0.5) subwords.next_to(words, DOWN) subwords.set_fill(GREY_A) self.play( ShowCreation(rect), FadeIn(words, 0.25 * DOWN) ) self.wait() self.play(Write(subwords)) self.wait() class ManyShadows(SingleFaceRandomRotation): plane_dims = (4, 4) limited_plane_extension = 2 def construct(self): self.clear() self.camera.frame.reorient(0, 0) plane = self.plane face = self.solid shadow = self.shadow n_rows = 3 n_cols = 10 planes = plane.replicate(n_rows * n_cols) for n, plane in zip(it.count(1), planes): face.rotate(angle=random.uniform(0, TAU), axis=normalize(np.random.uniform(-1, 1, 3))) shadow.update() sc = shadow.deepcopy() sc.clear_updaters() sc.set_fill(interpolate_color(BLUE_E, BLACK, 0.5), 0.75) plane.set_gloss(0) plane.add_to_back(sc) area = DecimalNumber(get_norm(sc.get_area_vector() / 4.0), font_size=56) label = VGroup(OldTex(f"f(R_{n}) = "), area) label.arrange(RIGHT) label.set_width(0.8 * plane.get_width()) label.next_to(plane, UP, SMALL_BUFF) label.set_color(WHITE) plane.add(label) planes.arrange_in_grid(n_rows, n_cols, buff=LARGE_BUFF) planes.set_width(15) planes.to_edge(DOWN) planes.update() self.play( LaggedStart( *( FadeIn(plane, scale=1.1) for plane in planes ), lag_ratio=0.6, run_time=10 ) ) self.wait() self.embed() class ComingUp(VideoWrapper): title = "Bob will compute this directly" wait_time = 10 animate_boundary = False class AllPossibleOrientations(ShadowScene): inf_light = True limited_plane_extension = 6 plane_dims = (12, 8) def construct(self): # Setup frame = self.camera.frame frame.reorient(-20, 80) frame.set_height(5) frame.d_theta = 0 def update_frame(frame, dt): frame.d_theta += -0.0025 * frame.get_theta() frame.increment_theta(clip(0.0025 * frame.d_theta, -0.01 * dt, 0.01 * dt)) frame.add_updater(update_frame) face = self.solid square, normal_vect = face normal_vect.set_flat_stroke() self.solid = square self.remove(self.shadow) self.add_shadow() self.shadow.deactivate_depth_test() self.solid = face fc = square.get_center().copy() # Sphere points sphere = Sphere(radius=1) sphere.set_color(GREY_E, 0.7) sphere.move_to(fc) sphere.always_sort_to_camera(self.camera) n_lat_lines = 40 theta_step = PI / n_lat_lines sphere_points = np.array([ sphere.uv_func(phi, theta + theta_step * (phi / TAU)) for theta in np.arange(0, PI, theta_step) for phi in np.linspace( 0, TAU, int(2 * n_lat_lines * math.sin(theta)) + 1 ) ]) sphere_points[:, 2] *= -1 original_sphere_points = sphere_points.copy() sphere_points += fc sphere_dots = DotCloud(sphere_points) sphere_dots.set_radius(0.0125) sphere_dots.set_glow_factor(0.5) sphere_dots.make_3d() sphere_dots.apply_depth_test() sphere_dots.add_updater(lambda m: m) sphere_lines = VGroup(*( Line(sphere.get_center(), p) for p in sphere_dots.get_points() )) sphere_lines.set_stroke(WHITE, 1, 0.05) sphere_words = OldTexText("All normal vectors = Sphere") uniform_words = OldTexText("All points equally likely") for words in [sphere_words, uniform_words]: words.fix_in_frame() words.to_edge(UP) # Trace sphere N = len(original_sphere_points) self.play(FadeIn(sphere_words)) self.play( ShowCreation(sphere_dots), ShowIncreasingSubsets(sphere_lines), UpdateFromAlphaFunc( face, lambda m, a: m.apply_matrix( rotation_between_vectors( normal_vect.get_vector(), original_sphere_points[int(a * (N - 1))], ), about_point=fc ) ), run_time=30, rate_func=smooth, ) self.play( FadeOut(sphere_words, UP), FadeIn(uniform_words, UP), ) last_dot = Mobject() for x in range(20): point = random.choice(sphere_points) dot = TrueDot( point, radius=1, glow_factor=10, color=YELLOW, ) self.add(dot) self.play( face.animate.apply_matrix(rotation_between_vectors( normal_vect.get_vector(), point - fc ), about_point=fc), FadeOut(last_dot, run_time=0.25), FadeIn(dot), run_time=0.5, ) self.wait(0.25) last_dot = dot self.play(FadeOut(last_dot)) self.wait() # Sphere itself sphere_mesh = SurfaceMesh(sphere, resolution=(21, 11)) sphere_mesh.set_stroke(BLUE_E, 1, 1) for sm in sphere_mesh.get_family(): sm.uniforms["anti_alias_width"] = 0 v1 = normal_vect.get_vector() normal_vect.scale(0.99, about_point=fc) v2 = DR + OUT frame.reorient(-5) self.play( Rotate( face, angle_between_vectors(v1, v2), axis=normalize(cross(v1, v2)) ), UpdateFromAlphaFunc( self.plane, lambda m, a: square.scale(0.9).set_opacity(0.5 - a * 0.5) ), ) self.play( ShowCreation(sphere_mesh, lag_ratio=0.5), FadeIn(sphere), sphere_dots.animate.set_radius(0), FadeOut(sphere_lines), frame.animate.reorient(0), run_time=3, ) self.remove(sphere_dots) # Show patch def get_patch(u, v, delta_u=0.05, delta_v=0.1): patch = ParametricSurface( sphere.uv_func, u_range=(u * TAU, (u + delta_u) * TAU), v_range=(v * PI, (v + delta_v) * PI), ) patch.shift(fc) patch.set_color(YELLOW, 0.75) patch.always_sort_to_camera(self.camera) return patch patch = get_patch(0.85, 0.6) self.add(patch, sphere) self.play( ShowCreation(patch), frame.animate.reorient(10, 75), run_time=2, ) # Probability expression patch_copy = patch.deepcopy() sphere_copy = sphere.deepcopy() sphere_copy.set_color(GREY_D, 0.7) for mob in patch_copy, sphere_copy: mob.apply_matrix(frame.get_inverse_camera_rotation_matrix()) mob.fix_in_frame() mob.center() patch_copy2 = patch_copy.copy() prob = Group(*Tex( "P(", "0.", ")", "=", "{Num ", "\\over ", "Den}", font_size=60 )) prob.fix_in_frame() prob.to_corner(UR) prob.shift(DOWN) for i, mob in [(1, patch_copy), (4, patch_copy2), (6, sphere_copy)]: mob.replace(prob[i], dim_to_match=1) prob.replace_submobject(i, mob) sphere_copy.scale(3, about_edge=UP) self.play(FadeIn(prob, lag_ratio=0.1)) self.wait() for i in (4, 6): self.play(ShowCreationThenFadeOut( SurroundingRectangle(prob[i], stroke_width=2).fix_in_frame() )) self.wait() # Many patches patches = Group( get_patch(0.65, 0.5), get_patch(0.55, 0.8), get_patch(0.85, 0.8), get_patch(0.75, 0.4, 0.1, 0.2), ) patch.deactivate_depth_test() self.add(sphere, patch) for new_patch in patches: self.play( Transform(patch, new_patch), ) self.wait() # Non-specified orientation self.play( LaggedStart(*map(FadeOut, (sphere, sphere_mesh, patch, *prob, uniform_words))) ) self.play( square.animate.set_fill(opacity=0.5), frame.animate.reorient(-30), run_time=3, ) self.play( Rotate(square, TAU, normal_vect.get_vector()), run_time=8, ) self.wait() # Show theta def get_normal(): return normal_vect.get_vector() def get_theta(): return np.arccos(get_normal()[2] / get_norm(get_normal())) def get_arc(): result = Arc(PI / 2, -get_theta(), radius=0.25) result.rotate(PI / 2, RIGHT, about_point=ORIGIN) result.rotate(angle_of_vector([*get_normal()[:2], 0]), OUT, about_point=ORIGIN) result.shift(fc) result.set_stroke(WHITE, 1) result.apply_depth_test() return result arc = always_redraw(get_arc) theta = OldTex("\\theta", font_size=20) theta.rotate(PI / 2, RIGHT) theta.set_backstroke(width=2) theta.add_updater(lambda m: m.next_to(arc.pfp(0.5), OUT + RIGHT, buff=0.05)) z_axis = Line(ORIGIN, 10 * OUT) z_axis.set_stroke(WHITE, 1) z_axis.apply_depth_test() self.add(z_axis, face, theta, arc) self.play( ShowCreation(z_axis), ShowCreation(arc), FadeIn(theta, 0.5 * OUT), ) self.wait() # Show shadow area shadow_area = OldTexText("Shadow area =", "$|\\cos(\\theta)|s^2$") shadow_area.fix_in_frame() shadow_area.to_edge(RIGHT) shadow_area.set_y(-3) shadow_area.set_backstroke() self.play( Write(shadow_area, run_time=3), Rotate(face, TAU, normal_vect.get_vector(), run_time=10), ) self.wait(4) shadow_area[1].generate_target() shadow_area[1].target.to_corner(UR, buff=MED_LARGE_BUFF) shadow_area[1].target.shift(LEFT) brace = Brace(shadow_area[1].target, DOWN) brace_text = OldTexText("How do you average this\\\\over the sphere?", font_size=36) brace_text.next_to(brace, DOWN, SMALL_BUFF) brace.fix_in_frame() brace_text.fix_in_frame() self.play( GrowFromCenter(brace), MoveToTarget(shadow_area[1]), FadeOut(shadow_area[0]), square.animate.set_fill(opacity=0), ) face.generate_target() face.target[1].set_length(0.98, about_point=fc) sphere.set_opacity(0.35) sphere_mesh.set_stroke(width=0.5) self.play( MoveToTarget(face), FadeIn(brace_text, 0.5 * DOWN), Write(sphere_mesh, run_time=2, stroke_width=1), FadeIn(sphere), ) # Sum expression def update_theta_ring(ring): theta = get_theta() phi = angle_of_vector([*get_normal()[:2], 0]) ring.set_width(max(2 * 1.01 * math.sin(theta), 1e-3)) ring.rotate(phi - angle_of_vector([*ring.get_start()[:2], 0])) ring.move_to(fc + math.cos(theta) * OUT) return ring theta_ring = Circle() theta_ring.set_stroke(YELLOW, 2) theta_ring.apply_depth_test() theta_ring.uniforms["anti_alias_width"] = 0 loose_sum = OldTex( "\\sum_{\\theta \\in [0, \\pi]}", "P(\\theta)", "\\cdot ", "|\\cos(\\theta)|s^2" ) loose_sum.fix_in_frame() loose_sum.next_to(brace_text, DOWN, LARGE_BUFF) loose_sum.to_edge(RIGHT) prob_words = OldTexText("How likely is a given value of $\\theta$?", font_size=36) prob_words.fix_in_frame() prob_words.next_to(loose_sum[1], DOWN) prob_words.to_edge(RIGHT, buff=MED_SMALL_BUFF) finite_words = Text("If finite...") finite_words.next_to(brace_text, DOWN, LARGE_BUFF).fix_in_frame() self.add(finite_words) face.rotate(-angle_of_vector([*get_normal()[:2], 0])) face.shift(fc - normal_vect.get_start()) for d_theta in (*[-0.2] * 10, *[0.2] * 10): face.rotate(d_theta, np.cross(get_normal(), OUT), about_point=fc) self.wait(0.25) self.play( Write(loose_sum.get_part_by_tex("P(\\theta)")), FadeIn(prob_words, 0.5 * DOWN), FadeOut(finite_words), ApplyMethod(frame.set_x, 1, run_time=2) ) update_theta_ring(theta_ring) self.add(theta_ring, sphere) self.play( Rotate(face, TAU, OUT, about_point=fc, run_time=4), ShowCreation(theta_ring, run_time=4), ) theta_ring.add_updater(update_theta_ring) self.wait() self.play( FadeTransform(shadow_area[1].copy(), loose_sum.get_part_by_tex("cos")), Write(loose_sum.get_part_by_tex("\\cdot")), FadeOut(prob_words, 0.5 * DOWN) ) self.wait(2) self.play( Write(loose_sum[0], run_time=2), run_time=3, ) face.rotate(get_theta(), axis=np.cross(get_normal(), OUT), about_point=fc) for x in np.arange(0.2, PI, 0.2): face.rotate(0.2, UP, about_point=fc) self.wait(0.5) self.wait(5) # Continuous sum_brace = Brace(loose_sum[0], DOWN, buff=SMALL_BUFF) continuum = OldTexText("Continuum\\\\(uncountably infinite)", font_size=36) continuum.next_to(sum_brace, DOWN, SMALL_BUFF) zero = OldTex('0') zero.next_to(loose_sum[1], DOWN, buff=1.5) zero.shift(1.5 * RIGHT) zero_arrow = Arrow(loose_sum[1], zero, buff=SMALL_BUFF) nonsense_brace = Brace(loose_sum, UP) nonsense = nonsense_brace.get_text("Not really a sensible expression", font_size=36) for mob in [sum_brace, continuum, zero, zero_arrow, nonsense_brace, nonsense]: mob.fix_in_frame() mob.set_color(YELLOW) VGroup(nonsense_brace, nonsense).set_color(RED) face.start_time = self.time face.clear_updaters() face.add_updater(lambda f, dt: f.rotate( angle=0.25 * dt * math.cos(0.1 * (self.time - f.start_time)), axis=np.cross(get_normal(), OUT), about_point=fc, ).shift(fc - f[1].get_start())) self.play( GrowFromCenter(sum_brace), FadeIn(continuum, 0.5 * DOWN) ) self.wait(4) self.play( ShowCreation(zero_arrow), GrowFromPoint(zero, zero_arrow.get_start()), ) self.wait(2) inf_sum_group = VGroup( nonsense_brace, nonsense, sum_brace, continuum, zero_arrow, zero, loose_sum, ) top_part = inf_sum_group[:2] top_part.set_opacity(0) self.play( inf_sum_group.animate.to_corner(UR), FadeOut(VGroup(brace, brace_text, shadow_area[1])), run_time=2, ) top_part.set_fill(opacity=1) self.play( GrowFromCenter(nonsense_brace), Write(nonsense), ) self.wait(10) # Swap for an integral integral = OldTex( "\\int_0^\\pi ", "p(\\theta)", "\\cdot ", "|\\cos(\\theta)| s^2", "d\\theta", ) integral.shift(loose_sum[-1].get_right() - integral[-1].get_right()) integral.fix_in_frame() self.play(LaggedStart(*map(FadeOut, inf_sum_group[:-1]))) self.play( TransformMatchingShapes( loose_sum[0], integral[0], fade_transform_mismatches=True, ) ) self.play( FadeTransformPieces(loose_sum[1:4], integral[1:4]), Write(integral[4]) ) self.wait(5) face.clear_updaters() self.wait(5) # Show 2d slice back_half_sphere = Sphere(u_range=(0, PI)) back_half_sphere.match_color(sphere) back_half_sphere.set_opacity(sphere.get_opacity()) back_half_sphere.shift(fc) back_half_mesh = SurfaceMesh(back_half_sphere, resolution=(11, 11)) back_half_mesh.set_stroke(BLUE_D, 1, 0.75) circle = Circle() circle.set_stroke(TEAL, 1) circle.rotate(PI / 2, RIGHT) circle.move_to(fc) frame.clear_updaters() theta_ring.deactivate_depth_test() theta_ring.uniforms.pop("anti_alias_width") theta_ring.set_stroke(width=1) self.play( FadeOut(sphere), sphere_mesh.animate.set_stroke(opacity=0.25), FadeIn(circle), theta_ring.animate.set_stroke(width=1), frame.animate.reorient(-6, 87).set_height(4), integral.animate.set_height(0.5).set_opacity(0).to_corner(UR), run_time=2, ) self.remove(integral) # Finite sample def get_tick_marks(theta_samples, tl=0.05): return VGroup(*( Line((1 - tl / 2) * p, (1 + tl / 2) * p).shift(fc) for theta in theta_samples for p in [np.array([math.sin(theta), 0, math.cos(theta)])] )).set_stroke(YELLOW, 1) factor = 1 theta_samples = np.linspace(0, PI, factor * sphere_mesh.resolution[0]) dtheta = theta_samples[1] - theta_samples[0] tick_marks = get_tick_marks(theta_samples) def set_theta(face, theta): face.apply_matrix(rotation_between_vectors( normal_vect.get_vector(), OUT ), about_point=fc) face.rotate(theta, UP, about_point=fc) self.play( ShowIncreasingSubsets(tick_marks[:-1]), UpdateFromAlphaFunc( face, lambda f, a: set_theta(face, theta_samples[int(a * (len(theta_samples) - 2))]) ), run_time=4 ) self.add(tick_marks) self.wait(2) tsi = factor * 6 # theta sample index dt_line = Line(tick_marks[tsi].get_center(), tick_marks[tsi + 1].get_center()) dt_brace = Brace( Line(ORIGIN, RIGHT), UP ) dt_brace.scale(0.5) dt_brace.set_width(dt_line.get_length(), stretch=True) dt_brace.rotate(PI / 2, RIGHT) dt_brace.rotate(theta_samples[tsi], UP) dt_brace.move_to(dt_line) dt_brace.shift(SMALL_BUFF * normalize(dt_line.get_center() - fc)) dt_label = OldTex("\\Delta\\theta", font_size=24) dt_label.rotate(PI / 2, RIGHT) dt_label.next_to(dt_brace, OUT + RIGHT, buff=0.05) self.play( Write(dt_brace), Write(dt_label), run_time=1, ) sphere.set_opacity(0.1) self.play( frame.animate.reorient(10, 70), Rotate(face, -get_theta() + theta_samples[tsi], UP, about_point=fc), sphere_mesh.animate.set_stroke(opacity=0.5), FadeIn(sphere), run_time=3 ) frame.add_updater(update_frame) self.wait() # Latitude band def get_band(index): band = Sphere( u_range=(0, TAU), v_range=theta_samples[index:index + 2], prefered_creation_axis=1, ) band.set_color(YELLOW, 0.5) band.stretch(-1, 2, about_point=ORIGIN) band.shift(fc) return band band = get_band(tsi) self.add(band, sphere_mesh, sphere) self.play( ShowCreation(band), Rotate(face, dtheta, UP, about_point=fc), run_time=3, ) self.play(Rotate(face, -dtheta, UP, about_point=fc), run_time=3) self.wait(2) area_question = Text("Area of this band?") area_question.set_color(YELLOW) area_question.fix_in_frame() area_question.set_y(1.75) area_question.to_edge(RIGHT, buff=2.5) self.play(Write(area_question)) self.wait() random_points = [sphere.pfp(random.random()) - fc for x in range(30)] random_points.append(normal_vect.get_end() - fc) glow_dots = Group(*(TrueDot(p) for p in random_points)) for dot in glow_dots: dot.shift(fc) dot.set_radius(0.2) dot.set_color(BLUE) dot.set_glow_factor(2) theta_ring.suspend_updating() last_dot = VectorizedPoint() for dot in glow_dots: face.apply_matrix(rotation_between_vectors( get_normal(), dot.get_center() - fc, ), about_point=fc) self.add(dot) self.play(FadeOut(last_dot), run_time=0.25) last_dot = dot self.play(FadeOut(last_dot)) self.wait() # Find the area of the band frame.clear_updaters() self.play( frame.animate.reorient(-7.5, 78), sphere_mesh.animate.set_stroke(opacity=0.2), band.animate.set_opacity(0.2), ) one = OldTex("1", font_size=24) one.rotate(PI / 2, RIGHT) one.next_to(normal_vect.get_center(), IN + RIGHT, buff=0.05) radial_line = Line( [0, 0, normal_vect.get_end()[2]], normal_vect.get_end() ) radial_line.set_stroke(BLUE, 2) r_label = OldTex("r", font_size=20) sin_label = OldTex("\\sin(\\theta)", font_size=16) for label in r_label, sin_label: label.rotate(PI / 2, RIGHT) label.next_to(radial_line, OUT, buff=0.05) label.set_color(BLUE) label.set_backstroke() self.play(Write(one)) self.wait() self.play( TransformFromCopy(normal_vect, radial_line), FadeTransform(one.copy(), r_label) ) self.wait() self.play(FadeTransform(r_label, sin_label)) self.wait() band_area = OldTex("2\\pi \\sin(\\theta)", "\\Delta\\theta") band_area.next_to(area_question, DOWN, LARGE_BUFF) band_area.set_backstroke() band_area.fix_in_frame() circ_label, dt_copy = band_area circ_brace = Brace(circ_label, DOWN, buff=SMALL_BUFF) circ_words = circ_brace.get_text("Circumference") approx = OldTex("\\approx") approx.rotate(PI / 2) approx.move_to(midpoint(band_area.get_top(), area_question.get_bottom())) VGroup(circ_brace, circ_words, approx).set_backstroke().fix_in_frame() self.play( frame.animate.reorient(10, 60), ) theta_ring.update() self.play( ShowCreation(theta_ring), Rotate(face, TAU, OUT, about_point=fc), FadeIn(circ_label, 0.5 * DOWN, rate_func=squish_rate_func(smooth, 0, 0.5)), GrowFromCenter(circ_brace), Write(circ_words), run_time=3, ) self.wait() self.play(frame.animate.reorient(-5, 75)) self.play(FadeTransform(area_question[-1], approx)) area_question.remove(area_question[-1]) self.play(Write(dt_copy)) self.wait(3) # Probability of falling in band prob = OldTex( "P(\\text{Vector} \\text{ in } \\text{Band})", "=", "{2\\pi \\sin(\\theta) \\Delta\\theta", "\\over", " 4\\pi}", tex_to_color_map={ "\\text{Vector}": GREY_B, "\\text{Band}": YELLOW, } ) prob.fix_in_frame() prob.to_edge(RIGHT) prob.set_y(1) prob.set_backstroke() numer = prob.get_part_by_tex("\\sin") numer_rect = SurroundingRectangle(numer, buff=0.05) numer_rect.set_stroke(YELLOW, 1) numer_rect.fix_in_frame() area_question.generate_target() area_question.target.match_width(numer_rect) area_question.target.next_to(numer_rect, UP, SMALL_BUFF) denom_rect = SurroundingRectangle(prob.get_part_by_tex("4\\pi"), buff=0.05) denom_rect.set_stroke(BLUE, 2) denom_rect.fix_in_frame() denom_label = OldTexText("Surface area of\\\\a unit sphere") denom_label.scale(area_question.target[0].get_height() / denom_label[0][0].get_height()) denom_label.set_color(BLUE) denom_label.next_to(denom_rect, DOWN, SMALL_BUFF) denom_label.fix_in_frame() i = prob.index_of_part_by_tex("sin") self.play( FadeTransform(band_area, prob.get_part_by_tex("sin"), remover=True), MoveToTarget(area_question), FadeIn(prob[:i]), FadeIn(prob[i + 1:]), FadeIn(numer_rect), *map(FadeOut, [approx, circ_brace, circ_words]), frame.animate.set_x(1.5), ) self.add(prob) self.remove(band_area) self.wait() self.play( ShowCreation(denom_rect), FadeIn(denom_label, 0.5 * DOWN), ) sc = sphere.copy().flip(UP).scale(1.01).set_color(BLUE, 0.5) self.add(sc, sphere_mesh) self.play(ShowCreation(sc), run_time=3) self.play(FadeOut(sc)) self.wait() # Expression for average sphere_group = Group( sphere, sphere_mesh, theta_ring, band, circle, radial_line, sin_label, one, tick_marks, dt_brace, dt_label, ) average_eq = OldTex( "\\text{Average shadow} \\\\", "\\sum_{\\theta}", "{2\\pi", "\\sin(\\theta)", " \\Delta\\theta", "\\over", " 4\\pi}", "\\cdot", "|\\cos(\\theta)|", "s^2" ) average_eq.fix_in_frame() average_eq.move_to(prob).to_edge(UP) average_eq[0].scale(1.25) average_eq[0].shift(MED_SMALL_BUFF * UP) average_eq[0].match_x(average_eq[1:]) new_prob = average_eq[2:7] prob_rect = SurroundingRectangle(new_prob) prob_rect.set_stroke(YELLOW, 2) prob_rect.fix_in_frame() self.play( FadeIn(average_eq[:1]), FadeIn(prob_rect), prob[:5].animate.match_width(prob_rect).next_to(prob_rect, DOWN, buff=0.15), FadeTransform(prob[-3:], new_prob), *map(FadeOut, [prob[5], numer_rect, denom_rect, area_question, denom_label]) ) self.wait() self.play( FadeOut(sphere_group), FadeIn(average_eq[-3:]), UpdateFromAlphaFunc(face, lambda f, a: f[0].set_fill(opacity=0.5 * a)) ) self.wait() band.set_opacity(0.5) bands = Group(*(get_band(i) for i in range(len(theta_samples) - 1))) sphere_mesh.set_stroke(opacity=0.5) self.add(sphere_mesh, sphere, bands) self.play( FadeIn(average_eq[1]), UpdateFromAlphaFunc(face, lambda f, a: f[0].set_fill(opacity=0.5 * (1 - a))), FadeIn(sphere), FadeIn(tick_marks), FadeIn(sphere_mesh), LaggedStartMap( FadeIn, bands, rate_func=there_and_back, lag_ratio=0.5, run_time=8, remover=True ), ) # Simplify average2 = OldTex( "{2\\pi", "\\over", "4\\pi}", "s^2", "\\sum_{\\theta}", "\\sin(\\theta)", "\\Delta\\theta", "\\cdot", "|\\cos(\\theta)|" ) average2.fix_in_frame() average2.move_to(average_eq[1:], RIGHT) half = OldTex("1 \\over 2") pre_half = average2[:3] half.move_to(pre_half, RIGHT) half_rect = SurroundingRectangle(pre_half, buff=SMALL_BUFF) half_rect.set_stroke(RED, 1) VGroup(half, half_rect).fix_in_frame() self.play( FadeOut(prob_rect), FadeOut(prob[:5]), *( FadeTransform(average_eq[i], average2[j], path_arc=10 * DEGREES) for i, j in [ (1, 4), (2, 0), (3, 5), (4, 6), (5, 1), (6, 2), (7, 7), (8, 8), (9, 3), ] ), run_time=2, ) self.play(ShowCreation(half_rect)) self.play( FadeTransform(pre_half, half), FadeOut(half_rect), ) sin, dt, dot, cos = average2[5:] tail = VGroup(cos, dot, sin, dt) tail.generate_target() tail.target.arrange(RIGHT, buff=SMALL_BUFF) tail.target.move_to(tail, LEFT) tail.target[-1].align_to(sin[0], DOWN) self.play( MoveToTarget(tail, path_arc=PI / 2), ) self.wait(2) integral = OldTex("\\int_0^\\pi ") integral.next_to(tail, LEFT, SMALL_BUFF) integral.fix_in_frame() dtheta = OldTex("d\\theta").fix_in_frame() dtheta.move_to(tail[-1], LEFT) average_copy = VGroup(half, average2[3:]).copy() average_copy.set_backstroke() self.play( VGroup(half, average2[3]).animate.next_to(integral, LEFT, SMALL_BUFF), FadeTransform(average2[4], integral), FadeTransform(tail[-1], dtheta), average_copy.animate.shift(2.5 * DOWN), frame.animate.set_phi(80 * DEGREES), ) self.wait() self.play(LaggedStart( ShowCreationThenFadeOut(SurroundingRectangle(average_copy[1][-3]).fix_in_frame()), ShowCreationThenFadeOut(SurroundingRectangle(dtheta).fix_in_frame()), lag_ratio=0.5 )) self.wait() # The limit brace = Brace(average_copy, UP, buff=SMALL_BUFF) brace_text = brace.get_text( "What does this approach for finer subdivisions?", font_size=30 ) arrow = Arrow(integral.get_bottom(), brace_text) VGroup(brace, brace_text, arrow).set_color(YELLOW).fix_in_frame() brace_text.set_backstroke() self.play( GrowFromCenter(brace), ShowCreation(arrow), FadeIn(brace_text, lag_ratio=0.1) ) for n in range(1, 4): new_ticks = get_tick_marks( np.linspace(0, PI, sphere_mesh.resolution[0] * 2**n), tl=0.05 / n ) self.play( ShowCreation(new_ticks), FadeOut(tick_marks), run_time=2, ) self.wait() tick_marks = new_ticks # Make room for computation face[0].set_fill(BLUE_D, opacity=0.75) face[0].set_stroke(WHITE, 0.5, 1) rect = Rectangle(fill_color=BLACK, fill_opacity=1, stroke_width=0) rect.replace(self.plane, stretch=True) rect.stretch(4 / 12, dim=0, about_edge=RIGHT) rect.scale(1.01) top_line = VGroup(half, average2[3], integral, tail[:-1], dtheta) self.add(face[0], sphere) self.play( LaggedStart(*map(FadeOut, [arrow, brace_text, brace, average_copy])), # UpdateFromAlphaFunc(face, lambda f, a: f[0].set_fill(opacity=0.5 * a)), GrowFromCenter(face[0], remover=True), frame.animate.set_height(6).set_x(3.5), FadeIn(rect), FadeOut(tick_marks), top_line.animate.set_width(4).to_edge(UP).to_edge(RIGHT, buff=LARGE_BUFF), FadeOut(average_eq[0], UP), run_time=2, ) self.add(face, sphere) self.begin_ambient_rotation(face, about_point=fc, speed=0.1) # Computation new_lines = VGroup( OldTex("{1 \\over 2} s^2 \\cdot 2 \\int_0^{\\pi / 2} \\cos(\\theta)\\sin(\\theta)\\,d\\theta"), OldTex("{1 \\over 2} s^2 \\cdot \\int_0^{\\pi / 2} \\sin(2\\theta)\\,d\\theta"), OldTex("{1 \\over 2} s^2 \\cdot \\left[ -\\frac{1}{2} \\cos(2\\theta) \\right]_0^{\\pi / 2}"), OldTex("{1 \\over 2} s^2 \\cdot \\left(-\\left(-\\frac{1}{2}\\right) - \\left(-\\frac{1}{2}\\right)\\right)"), OldTex("{1 \\over 2} s^2"), ) new_lines.scale(top_line.get_height() / new_lines[0].get_height()) kw = {"buff": 0.35, "aligned_edge": LEFT} new_lines.arrange(DOWN, **kw) new_lines.next_to(top_line, DOWN, **kw) new_lines.fix_in_frame() annotations = VGroup( OldTexText("To avoid the annoying absolute value, just\\\\cover the northern hemisphere and double it."), OldTexText("Trig identity: $\\sin(2\\theta) = 2\\cos(\\theta)\\sin(\\theta)$"), OldTexText("Antiderivative"), OldTexText("Try not to get lost in\\\\the sea of negatives..."), OldTexText("Whoa, that turned out nice!"), ) annotations.fix_in_frame() annotations.set_color(YELLOW) annotations.scale(0.5) rect = SurroundingRectangle(new_lines[-1], buff=SMALL_BUFF) rect.set_stroke(YELLOW, 2).fix_in_frame() for note, line in zip(annotations, new_lines): note.next_to(line, LEFT, MED_LARGE_BUFF) self.play( LaggedStartMap(FadeIn, new_lines, lag_ratio=0.7), LaggedStartMap(FadeIn, annotations, lag_ratio=0.7), run_time=5, ) self.wait(20) self.play( new_lines[:-1].animate.set_opacity(0.5), annotations[:-1].animate.set_opacity(0.5), ShowCreation(rect), ) self.wait(10) def get_solid(self): face = Square(side_length=2) face.set_fill(BLUE, 0.5) face.set_stroke(width=0) normal = Vector(OUT) normal.shift(2e-2 * OUT) face = VGroup(face, normal) face.set_stroke(background=True) face.apply_depth_test() return face class AskAboutAverageCosValue(AllPossibleOrientations): def construct(self): self.remove(self.solid) self.remove(self.shadow) frame = self.camera.frame frame.set_height(5) frame.reorient(-5, 80) frame.shift(2 * RIGHT) self.init_frame_rotation() # Copy pasting from above...not great fc = 3 * OUT sphere = Sphere(radius=1) sphere.set_color(GREY_E, 0.25) sphere.move_to(fc) sphere.always_sort_to_camera(self.camera) sphere_mesh = SurfaceMesh(sphere, resolution=(21, 11)) sphere_mesh.set_stroke(BLUE_E, 1, 0.5) for sm in sphere_mesh.get_family(): sm.uniforms["anti_alias_width"] = 0 self.add(sphere, sphere_mesh) normal_vect = Arrow(sphere.get_center(), sphere.pfp(0.2), buff=0) def randomly_place_vect(): theta = random.uniform(0.1, PI - 0.1) phi = random.uniform(-PI / 4, PI / 4) + random.choice([0, PI]) point = fc + np.array([ math.sin(theta) * math.cos(phi), math.sin(theta) * math.sin(phi), math.cos(theta), ]) normal_vect.put_start_and_end_on(sphere.get_center(), point) def get_normal(): return normal_vect.get_vector() def get_theta(): return np.arccos(get_normal()[2] / get_norm(get_normal())) def get_arc(): result = Arc(PI / 2, -get_theta(), radius=0.25) result.rotate(PI / 2, RIGHT, about_point=ORIGIN) result.rotate(angle_of_vector([*get_normal()[:2], 0]), OUT, about_point=ORIGIN) result.shift(fc) result.set_stroke(WHITE, 1) result.apply_depth_test() return result arc = always_redraw(get_arc) theta = OldTex("\\theta", font_size=20) theta.rotate(PI / 2, RIGHT) theta.set_backstroke(width=2) theta.add_updater(lambda m: m.next_to( arc.pfp(0.5), arc.pfp(0.5) - fc, buff=0.05) ) z_axis = Line(ORIGIN, 10 * OUT) z_axis.set_stroke(WHITE, 1) z_axis.apply_depth_test() self.add(z_axis, normal_vect, arc, theta) self.add(sphere_mesh, sphere) # Show random samples question = OldTexText("What's the mean?") question.to_corner(UR) question.to_edge(UP, buff=MED_SMALL_BUFF) question.fix_in_frame() arrow = Arrow(question, question.get_center() + DOWN) arrow.fix_in_frame() values = VGroup() lhss = VGroup() self.add(question, arrow) for n in range(15): randomly_place_vect() lhs = OldTex("|\\cos(\\theta_{" + str(n + 1) + "})| = ", font_size=30) value = DecimalNumber(abs(math.cos(get_theta())), font_size=30) value.next_to(values, DOWN) for mob in lhs, value: mob.fix_in_frame() mob.set_backstroke() values.add(value) values.next_to(arrow, DOWN) lhs.next_to(value, LEFT, buff=SMALL_BUFF) lhss.add(lhs) self.add(values, lhss) self.wait(0.5) class ThreeCamps(TeacherStudentsScene): def construct(self): self.remove(self.background) # Setup teacher = self.teacher students = self.students image = ImageMobject("Shadows_Integral_Intro") image.center().set_height(FRAME_HEIGHT) image.generate_target() image.target.replace(self.screen) self.screen.set_stroke(WHITE, 1) self.screen.save_state() self.screen.replace(image).set_stroke(width=0) self.play( LaggedStart(*( student.change("pondering", image.target) for student in students ), run_time=2, lag_ratio=0.2), teacher.change("tease") ) self.wait() # Reactions phrases = [ Text("How fun!", font_size=40), Text("Wait, what?", font_size=40), Text("Okay give\nme a sec...", font_size=35), ] modes = ["hooray", "erm", "confused"] heights = np.linspace(2.0, 2.5, 3) for student, phrase, mode, height in zip(reversed(students), phrases, modes, heights): self.play( PiCreatureSays( student, phrase, target_mode=mode, look_at=image, bubble_config={ "direction": LEFT, "width": 3, "height": height, }, bubble_type=ThoughtBubble, run_time=2 ) ) self.wait(4) # Let's go over the definition integral = OldTex("\\int_0^\\pi \\dots d\\theta") integral.move_to(self.hold_up_spot, DOWN) brace = Brace(integral, UP) words = OldTexText("Let's go over the definition", font_size=36) words.next_to(brace, UP, SMALL_BUFF) words2 = OldTexText("It can't hurt, right?", font_size=36) words2.move_to(words) VGroup(brace, words, words2).set_color(YELLOW) self.play( LaggedStart(*( FadeOut(VGroup(student.bubble, student.bubble.content)) for student in reversed(students) )), LaggedStart(*( student.change("pondering", integral) for student in students )), FadeIn(integral, UP), teacher.change("raise_right_hand", integral), ) self.play( GrowFromCenter(brace), Write(words) ) self.wait(2) self.play( words.animate.shift(0.75 * UP).set_opacity(0.5), FadeIn(words2, 0.2 * UP), LaggedStart( self.teacher.change("shruggie"), self.students[0].change("sassy", words2), self.students[1].change("thinking", words2), self.students[2].change("well", words2), ) ) self.wait(2) self.play(self.teacher.change("speaking", words2)) self.wait(3) class ParticularValuesUnhelpfulOverlay(Scene): def construct(self): # Particular value expr = OldTex("P(\\theta =", "\\pi / 4", ")", "=", "0") expr.set_color_by_tex("\\pi / 4", YELLOW) brace = Brace(expr.get_part_by_tex("\\pi / 4"), UP, buff=SMALL_BUFF) brace.stretch(0.5, 1, about_edge=DOWN) words = Text("Some specific value", font_size=24) words.next_to(brace, UP, SMALL_BUFF) VGroup(brace, words).set_color(YELLOW) VGroup(expr, brace, words).to_corner(UR) self.play(FadeIn(expr, lag_ratio=1)) self.play( GrowFromCenter(brace), FadeIn(words, shift=0.2 * UP), ) self.wait() # Unhelpful question = OldTexText("What are you going\\\\to do with that?", font_size=24) question.next_to(expr, DOWN, LARGE_BUFF) arrow = Arrow(question, expr.get_part_by_tex("0"), buff=SMALL_BUFF) self.play( FadeIn(question), ShowCreation(arrow), ) self.wait() # New expr range_expr = OldTex( "P(\\pi / 4 < \\theta < \\pi / 4 + \\Delta\\theta) > 0", tex_to_color_map={ "\\pi / 4": YELLOW, "\\Delta\\theta": GREY_A, }, font_size=40 ) range_expr.move_to(expr, RIGHT) new_brace = Brace(range_expr.slice_by_tex("\\pi / 4", ")"), UP, buff=SMALL_BUFF) new_words = Text("Range of values", font_size=24) new_words.next_to(new_brace, UP, SMALL_BUFF) VGroup(new_brace, new_words).set_color(YELLOW) self.play( FadeOut(question), FadeOut(arrow), TransformMatchingShapes(expr, range_expr), FadeTransform(brace, new_brace), FadeTransform(words, new_words), ) self.wait() class SurfaceAreaOfSphere(Scene): def construct(self): sphere = Sphere(radius=3) sphere.set_color(BLUE_E, 1) sphere.always_sort_to_camera(self.camera) sphere.rotate(5 * DEGREES, OUT) sphere.rotate(80 * DEGREES, LEFT) sphere.move_to(0.5 * DOWN) sphere_mesh = SurfaceMesh(sphere) sphere_mesh.set_stroke(WHITE, 0.5, 0.5) equation = OldTex( "\\text{Surface area} = 4\\pi R^2", tex_to_color_map={ "R": YELLOW, "\\text{Surface area}": BLUE, }, ) equation.to_edge(UP) self.add(equation, sphere, sphere_mesh) self.play( Write(sphere_mesh, lag_ratio=0.02, stroke_width=1), ShowCreation(sphere, rate_func=squish_rate_func(smooth, 0.25, 1)), run_time=5, ) self.wait() class IntegralOverlay(Scene): def construct(self): integral = OldTex("\\int_0^\\pi") integral.set_color(YELLOW) self.play(Write(integral, run_time=2)) self.play(integral.animate.set_color(WHITE)) self.play(LaggedStart(*( FlashAround(sm, time_width=3) for sm in integral[0][:0:-1] ), lag_ratio=0.5, run_time=3)) self.wait() class AlicesInsights(Scene): def construct(self): title = OldTexText("Alice's insights", font_size=72) title.to_edge(UP) title.set_backstroke() underline = Underline(title, buff=-0.05) underline.scale(1.5) underline.insert_n_curves(50) underline.set_stroke(WHITE, width=[0, *4 * [3], 0], opacity=1) self.add(underline, title) kw = dict( t2c={ "double cover": YELLOW, "mean": RED, "means": RED, "sum": BLUE, "Sum": BLUE, "Average": RED, } ) insights = VGroup( Text("1. The face shadows double cover the cube shadow", **kw), # Text("2. The mean of the sum is the sum of the means", **kw), Text("2. Average(Sum(Face shadows)) = Sum(Average(Face shadow))", **kw), Text("3. Use a sphere to deduce the unknown constant", **kw), ) insights.arrange(DOWN, buff=LARGE_BUFF, aligned_edge=LEFT) insights.next_to(underline, DOWN, LARGE_BUFF) insights.to_edge(LEFT) self.play(LaggedStart(*( FadeIn(insight[:2], 0.25 * DOWN) for insight in insights ))) self.wait() for insight in insights: self.play(FadeIn(insight[2:], lag_ratio=0.1)) self.wait() class HalfBathedInLight(ShadowScene): def construct(self): frame = self.camera.frame frame.set_height(12) frame.add_updater(lambda m, dt: m.increment_theta(0.05 * dt)) cube = self.solid light = self.light light.next_to(cube, OUT, 2) self.add(light) self.remove(self.plane) self.shadow.add_updater(lambda m: m.set_opacity(0)) cube.move_to(OUT) cube.set_opacity(0.95) cube.rotate(PI / 2, DL) # cube.add_updater(lambda m: self.sort_to_camera(cube)) cube.update() cube.clear_updaters() light_lines = self.get_light_lines() light_lines.add_updater(lambda m: m.set_stroke(YELLOW, 2)) self.add(light_lines, light) self.wait(2) for s, color in zip([slice(3, None), slice(0, 3)], [WHITE, GREY_D]): cube.generate_target() sorted_cube = Group(*cube.target) sorted_cube.sort(lambda p: p[2]) sorted_cube[s].space_out_submobjects(2) sorted_cube[s].set_color(color) self.play( MoveToTarget(cube), rate_func=there_and_back_with_pause, run_time=3, ) self.wait(5) # Embed self.embed() class TwoToOneCover(ShadowScene): inf_light = True plane_dims = (20, 12) limited_plane_extension = 8 highlighted_face_color = YELLOW def construct(self): # Setup frame = self.camera.frame frame.reorient(-20, 75) frame.set_z(3) self.init_frame_rotation() cube = self.solid for face in cube: face.set_fill(opacity=0.9) face.set_reflectiveness(0.1) face.set_gloss(0.2) cube.add_updater(lambda m: self.sort_to_camera(m)) cube.rotate(PI / 3, normalize([3, 4, 5])) shadow = self.shadow outline = self.get_shadow_outline() # Inequality ineq = self.get_top_expression("$<$") ineq.fix_in_frame() lhs = ineq.slice_by_tex(None, "<") lt = ineq.get_part_by_tex("<") rhs = ineq.slice_by_tex("sum", None) af_label = ineq[-7:] lhs.save_state() lhs.set_x(0) # Shadow of the cube wireframe = cube.copy() for face in wireframe: face.set_fill(opacity=0) face.set_stroke(WHITE, 1) wireframe_shadow = wireframe.copy() wireframe_shadow.apply_function(flat_project) wireframe_shadow.set_gloss(0) wireframe_shadow.set_reflectiveness(0) wireframe_shadow.set_shadow(0) for face in wireframe_shadow: face.set_stroke(GREY_D, 1) self.play( ShowCreation(wireframe, lag_ratio=0.1), Write(lhs[2:-1]) ) self.play(TransformFromCopy(wireframe, wireframe_shadow)) self.play(*map(FadeOut, (wireframe, wireframe_shadow))) self.wait() self.play( FadeIn(lhs[:2]), FadeIn(lhs[-1]), Write(outline), VShowPassingFlash( outline.copy().set_stroke(YELLOW, 4), time_width=1.5 ), ) self.wait() # Show faces and shadows cube.save_state() faces, face_shadows = self.get_faces_and_face_shadows() faces[:3].set_opacity(0.1) face_shadow_lines = VGroup(*( VGroup(*( Line(v1, v2) for v1, v2 in zip(f.get_vertices(), fs.get_vertices()) )) for f, fs in zip(faces, face_shadows) )) face_shadow_lines.set_stroke(YELLOW, 0.5, 0.5) self.play( Restore(lhs), FadeIn(af_label, shift=0.5 * RIGHT) ) self.play( *( LaggedStart(*( VFadeInThenOut(sm) for sm in reversed(mobject) ), lag_ratio=0.5, run_time=6) for mobject in [faces, face_shadows, face_shadow_lines] ), ) self.play( ApplyMethod(cube.space_out_submobjects, 1.7, rate_func=there_and_back_with_pause, run_time=8), ApplyMethod(frame.reorient, 20, run_time=8), Write(lt), Write(rhs[0]), ) self.wait(2) # Show a given pair of faces face_pair = Group(faces[3], faces[5]).copy() face_pair[1].set_color(RED) face_pair.save_state() fp_shadow = get_shadow(face_pair) self.add(fp_shadow) self.play( FadeOut(cube), *map(VFadeOut, shadow), FadeOut(outline), *map(Write, face_pair), ) self.wait(1) self.play(Rotate( face_pair, PI / 2, DOWN, about_point=cube.get_center(), run_time=3, )) self.wait() self.random_toss(face_pair, about_point=cube.get_center(), run_time=6) fp_shadow.clear_updaters() self.play( FadeIn(cube), *map(VFadeIn, shadow), FadeOut(face_pair, scale=0), FadeOut(fp_shadow, scale=0), ) self.add(shadow) # Half of sum new_expression = self.get_top_expression(" = ", "$\\displaystyle \\frac{1}{2}$") new_expression.fix_in_frame() eq_half = VGroup( new_expression.get_part_by_tex("="), new_expression.get_part_by_tex("frac"), ) cube.save_state() cube.generate_target(use_deepcopy=True) cube.target.clear_updaters() z_sorted_faces = Group(*sorted(list(cube.target), key=lambda f: f.get_z())) z_sorted_faces[:3].shift(2 * LEFT) z_sorted_faces[3:].shift(2 * RIGHT) cube.clear_updaters() self.play( MoveToTarget(cube), ApplyMethod(frame.reorient, 0, run_time=2) ) self.play( FadeOut(lt, UP), Write(eq_half), ReplacementTransform(rhs, new_expression[-len(rhs):]), ReplacementTransform(lhs, new_expression[:len(lhs)]), ) self.remove(ineq) self.add(new_expression) self.wait(2) anims = [] for part in z_sorted_faces: pc = part.copy() pc.set_fill(YELLOW, 0.25) pc_shadow = get_shadow(pc) pc_shadow.clear_updaters() pc_shadow.match_style(pc) lines = VGroup(*( Line(v, flat_project(v)) for v in pc.get_vertices() )) lines.set_stroke(YELLOW, 1, 0.1) anims.append(AnimationGroup( VFadeInThenOut(pc), VFadeInThenOut(pc_shadow), VFadeInThenOut(lines), )) self.play(LaggedStart(*anims, lag_ratio=0.4, run_time=6)) self.play(Restore(cube)) # Show the double cover shadow_point = shadow.get_bottom() + [0.5, 0.75, 0] dot = GlowDot(shadow_point) line = DashedLine( shadow_point + 5 * OUT, shadow_point, dash_length=0.025 ) line.set_stroke(YELLOW, 1) def update_line(line): line.move_to(dot.get_center(), IN) for dash in line: dist = cube_sdf(dash.get_center(), cube) dash.set_stroke( opacity=interpolate(0.1, 1.0, clip(10 * dist, -0.5, 0.5) + 0.5) ) dash.inside = (dist < 0) line.add_updater(update_line) entry_point = next(dash for dash in line if dash.inside).get_center() exit_point = next(dash for dash in reversed(line) if dash.inside).get_center() arrows = VGroup(*( Arrow(point + RIGHT, point, buff=0.1) for point in (entry_point, exit_point) )) self.play(ShowCreation(line, rate_func=rush_into)) self.play(FadeIn(dot, scale=10, rate_func=rush_from, run_time=0.5)) self.wait() self.play( Rotate( dot, TAU, about_point=ORIGIN, run_time=6, ), ) self.wait() for arrow in arrows: self.play(ShowCreation(arrow)) self.wait() self.play(FadeOut(arrows)) cube.add_updater(lambda m: self.sort_to_camera(m)) self.random_toss(cube, angle=1.5 * TAU, run_time=8) # Just show wireframe cube.save_state() cube.generate_target() for sm in cube.target: sm.set_fill(opacity=0) sm.set_stroke(WHITE, 2) outline = self.get_shadow_outline() outline.rotate(PI) self.play( MoveToTarget(cube), dot.animate.move_to(outline.get_start()) ) self.play(MoveAlongPath(dot, outline, run_time=8)) self.wait(2) self.play(Restore(cube)) self.play(dot.animate.move_to(outline.get_center()), run_time=2) # Make room for equation animations # Start here for new scene area_label = self.get_shadow_area_label() area_label.shift(1.75 * DOWN + 2.25 * RIGHT) area_label[0].scale(0, about_edge=RIGHT) area_label.scale(0.7) outline.update() line.clear_updaters() self.play( FadeOut(dot), FadeOut(line), ShowCreation(outline), VFadeIn(area_label), ) # Single out a face self.remove(new_expression) self.wait(2) face = cube[np.argmax([f.get_z() for f in cube])].copy() face.set_color(YELLOW) face_shadow = get_shadow(face) face_shadow_area = DecimalNumber(get_norm(face_shadow.get_area_vector()) / (self.unit_size**2)) face_shadow_area.scale(0.65) face_shadow_area.move_to(area_label) face_shadow_area.shift(flat_project(face.get_center() - cube.get_center())) face_shadow_area.shift(SMALL_BUFF * UR) face_shadow_area.fix_in_frame() cube.save_state() self.remove(cube) self.play( *( f.animate.set_fill(opacity=0) for f in cube ), FadeOut(outline), FadeOut(area_label), Write(face), FadeIn(face_shadow), FadeIn(face_shadow_area), ) self.wait(2) self.play( Restore(cube), *map(FadeOut, (face, face_shadow, face_shadow_area)), *map(FadeIn, (outline, area_label)), ) # Show simple rotations for x in range(2): self.random_toss(cube) self.wait() self.wait() # Many random orientations for x in range(40): self.randomly_reorient(cube) self.wait(0.25) # Show sum of faces again (play 78) self.random_toss(cube, 2 * TAU, run_time=8, meta_speed=10) self.wait() cube.save_state() sff = 1.5 self.play( VFadeOut(outline), VFadeOut(area_label), cube.animate.space_out_submobjects(sff) ) for x in range(3): self.random_toss(cube, angle=PI) self.wait() self.play(cube.animate.space_out_submobjects(1 / sff)) # Mean shadow of a single face cube_style = cube[0].get_style() def isolate_face_anims(i, color=YELLOW): return ( shadow.animate.set_fill( interpolate_color(color, BLACK, 0.75) ), *( f.animate.set_fill( color if f is cube[i] else BLUE, 0.75 if f is cube[i] else 0, ) for f in cube ) ) def tour_orientations(): self.random_toss(cube, 2 * TAU, run_time=5, meta_speed=10) self.play(*isolate_face_anims(5)) tour_orientations() for i, color in ((4, GREEN), (3, RED)): self.play( *isolate_face_anims(i, color), ) tour_orientations() cube.update() self.play( *( f.animate.set_style(**cube_style) for f in cube ), shadow.animate.set_fill(interpolate_color(BLUE, BLACK, 0.85)), VFadeIn(outline), VFadeIn(area_label), ) self.add(cube) # Ambient rotation self.add(cube) self.begin_ambient_rotation(cube, speed=0.4) self.wait(20) def get_top_expression(self, *mid_tex, n_faces=6): t2c = { "Shadow": GREY_B, "Cube": BLUE_D, "Face$_j$": YELLOW, } ineq = OldTexText( "Area(Shadow(Cube))", *mid_tex, " $\\displaystyle \\sum_{j=1}^" + f"{{{n_faces}}}" + "$ ", "Area(Shadow(Face$_j$))", tex_to_color_map=t2c, isolate=["(", ")"], ) ineq.to_edge(UP, buff=MED_SMALL_BUFF) return ineq def get_faces_and_face_shadows(self): faces = self.solid.deepcopy() VGroup(*faces).set_fill(self.highlighted_face_color) shadows = get_pre_shadow(faces, opacity=0.7) shadows.apply_function(flat_project) return faces, shadows class ConvexityPrelude(Scene): def construct(self): square = Square(side_length=3) square.rotate(-PI / 4) square.flip() square.set_stroke(BLUE, 2) points = [square.pfp(1 / 8), square.pfp(7 / 8)] beam = VGroup( Line(points[0] + 3 * UP, points[0], stroke_width=3), Line(points[0], points[1], stroke_width=2), Line(points[1], points[1] + 3 * DOWN, stroke_width=1), ) beam.set_stroke(YELLOW) words = OldTexText("2 intersections\\\\", "(almost always)") words[1].scale(0.7, about_edge=UP).set_color(GREY_B) words.to_edge(LEFT) arrows = VGroup(*( Arrow(words[0].get_right(), point) for point in points )) dots = GlowDot() dots.set_points(points) dots.set_color(WHITE) dots.set_radius(0.2) dots.set_glow_factor(3) self.add(square) self.add(words) self.play( ShowCreation(beam), FadeIn(arrows, lag_ratio=0.7), FadeIn(dots, lag_ratio=0.7), ) self.wait() question1 = Text("Why is this true?") question2 = Text("Is this true for all shapes?") question2.next_to(question1, DOWN, aligned_edge=LEFT) VGroup(question1, question2).to_corner(UR) self.play(Write(question1, run_time=1)) self.wait() self.play(FadeIn(question2, 0.25 * DOWN)) self.wait() # Convexity square.generate_target() convex_shapes = VGroup( square.target, RegularPolygon(5, color=TEAL), Rectangle(2, 1, color=TEAL_E), RegularPolygon(6, color=GREEN), Circle(color=GREEN_B), ) convex_shapes[2].apply_matrix([[1, 0.5], [0, 1]]) convex_shapes[3].shift(2 * RIGHT).apply_complex_function(np.exp) convex_shapes[3].make_jagged() for shape in convex_shapes: shape.set_height(1) shape.set_stroke(width=2) convex_shapes.arrange(DOWN) convex_shapes.set_height(6) v_line = Line(UP, DOWN).set_height(FRAME_HEIGHT) h_line = Line(LEFT, RIGHT).set_width(FRAME_WIDTH) h_line.set_y(3) VGroup(v_line, h_line).set_stroke(WHITE, 2) convex_title = Text("Convex") non_convex_title = Text("Non-convex") for title, vect in zip([convex_title, non_convex_title], [LEFT, RIGHT]): title.scale(1.5) title.next_to(h_line, UP) title.shift(vect * FRAME_WIDTH / 4) convex_shapes.next_to(h_line, DOWN) convex_shapes.match_x(convex_title) self.play( MoveToTarget(square), FadeIn(convex_shapes[1:], lag_ratio=0.5), FadeTransform(beam, v_line), ShowCreation(h_line), FadeIn(convex_title), LaggedStart(*map(FadeOut, ( dots, arrows, words, question1, question2, ))), ) self.wait() # Non-convex shapes pi = OldTex("\\pi").family_members_with_points()[0] pent = RegularPolygon(5) pent.set_points_as_corners([ORIGIN, *pent.get_vertices()[1:], ORIGIN]) n_mob = OldTex("N").family_members_with_points()[0] nc_shapes = VGroup(pi, pent, n_mob) nc_shapes.set_fill(opacity=0) nc_shapes.set_stroke(width=2) nc_shapes.set_submobject_colors_by_gradient(RED, PINK) for shape in nc_shapes: shape.set_height(1) nc_shapes.arrange(DOWN) nc_shapes.replace(convex_shapes, dim_to_match=1) nc_shapes.match_x(non_convex_title) self.play( Write(non_convex_title, run_time=1), LaggedStartMap(FadeIn, nc_shapes), ) self.wait() # Embed self.embed() class DefineConvexity(Scene): def construct(self): # Shape definition = "A set is convex if the line connecting any\n"\ "two points within it is contained in the set" set_color = BLUE line_color = GREEN title = Text( definition, t2c={ "convex": set_color, "line connecting any\ntwo points": line_color, }, t2s={"convex": ITALIC}, ) title.to_edge(UP) title.set_opacity(0.2) shape = Square(4.5) shape.set_fill(set_color, 0.25) shape.set_stroke(set_color, 2) shape.next_to(title, DOWN, LARGE_BUFF) self.add(title) self.play( title.get_part_by_text("A set is convex").animate.set_opacity(1), Write(shape) ) self.wait() # Show two points line = Line(shape.pfp(0.1), shape.pfp(0.5)) line.scale(0.7) line.set_stroke(line_color, 2) dots = DotCloud(line.get_start_and_end()) dots.set_color(line_color) dots.make_3d(0.5) dots.save_state() dots.set_radius(0) dots.set_opacity(0) self.play( title[definition.index("if"):definition.index("is", 16)].animate.set_opacity(1), Restore(dots), ) self.play(ShowCreation(line)) self.wait() self.play( title[definition.index("is", 16):].animate.set_opacity(1), ) # Alternate places dots.add_updater(lambda m: m.set_points(line.get_start_and_end())) def show_sample_lines(n=5): for x in range(5): self.play( line.animate.put_start_and_end_on( shape.pfp(random.random()), shape.pfp(random.random()), ).scale(random.random(), about_point=shape.get_center()) ) self.wait(0.5) show_sample_lines() # Letter π def tex_to_shape(tex): result = OldTex(tex).family_members_with_points()[0] result.match_style(shape) result.match_height(shape) result.move_to(shape) result_points = result.get_points().copy() index = np.argmax([np.dot(p, UR) for p in result_points]) index = 3 * (index // 3) result.set_points([*result_points[index:], *result_points[:index]]) return result pi = tex_to_shape("\\pi") letter_c = tex_to_shape("\\textbf{C}") letter_c.insert_n_curves(50) line.generate_target() line.target.put_start_and_end_on(*( pi.get_boundary_point(v) for v in (DL, DR) )) line.target.scale(0.9) not_convex_label = Text("Not convex!", color=RED) not_convex_label.next_to(pi, LEFT) shape.insert_n_curves(100) self.play( Transform(shape, pi, path_arc=PI / 2), MoveToTarget(line), run_time=2, ) self.play( FadeIn(not_convex_label, scale=2) ) self.wait() shape.insert_n_curves(80) self.play( Transform(shape, letter_c), line.animate.put_start_and_end_on(*( letter_c.pfp(a) for a in (0, 0.5) )), run_time=2, ) self.play(UpdateFromAlphaFunc( line, lambda l, a: l.put_start_and_end_on( letter_c.pfp(0.4 * a), l.get_end() ), run_time=6, )) self.wait() # Polygon convex_label = OldTexText("Convex \\ding{51}") convex_label.set_color(YELLOW) convex_label.move_to(not_convex_label) polygon = RegularPolygon(7) polygon.match_height(shape) polygon.move_to(shape) polygon.match_style(shape) self.remove(not_convex_label) self.play( FadeTransform(shape, polygon), line.animate.put_start_and_end_on( polygon.get_vertices()[1], polygon.get_vertices()[-1], ), TransformMatchingShapes(not_convex_label.copy(), convex_label), run_time=2 ) self.wait() polygon.generate_target() new_tip = 2 * line.get_center() - polygon.get_start() polygon.target.set_points_as_corners([ new_tip, *polygon.get_vertices()[1:], new_tip ]) self.play( MoveToTarget(polygon), TransformMatchingShapes(convex_label, not_convex_label), ) self.wait() # Show light beam beam = DashedLine(ORIGIN, FRAME_WIDTH * RIGHT) beam.set_stroke(YELLOW, 1) beam.next_to(line, DOWN, MED_SMALL_BUFF) flash_line = Line(beam.get_start(), beam.get_end()) flash_line.set_stroke(YELLOW, 5) flash_line.insert_n_curves(100) self.play( ShowCreation(beam), VShowPassingFlash(flash_line), run_time=1, ) self.wait() class NonConvexDoughnut(ShadowScene): def construct(self): # Setup frame = self.camera.frame self.remove(self.solid, self.shadow) torus = Torus() torus.set_width(4) torus.set_z(3) torus.set_color(BLUE_D) torus.set_opacity(0.7) torus.set_reflectiveness(0) torus.set_shadow(0.5) torus.always_sort_to_camera(self.camera) shadow = get_shadow(torus) shadow.always_sort_to_camera(self.camera) self.add(torus) self.add(shadow) self.play(ShowCreation(torus), run_time=2) self.play(Rotate(torus, PI / 2, LEFT), run_time=2) self.wait() # Light beam dot = GlowDot(0.25 * LEFT) line = DashedLine(dot.get_center(), dot.get_center() + 10 * OUT) line.set_stroke(YELLOW, 1) line_shadow = line.copy().set_stroke(opacity=0.1) self.add(line, torus, line_shadow) self.play( FadeIn(dot), ShowCreation(line), ShowCreation(line_shadow), ApplyMethod(frame.reorient, 20, run_time=7) ) self.wait() class ShowGridSum(TwoToOneCover): def construct(self): # Setup self.clear() shape_name = "Cube" n_faces = 6 # shape_name = "Dodec." # n_faces = 12 frame = self.camera.frame frame.reorient(0, 0) frame.move_to(ORIGIN) equation = self.get_top_expression(" = ", "$\\displaystyle \\frac{1}{2}$", n_faces=n_faces) self.add(equation) lhs = equation.slice_by_tex(None, "=") summand = equation.slice_by_tex("sum", None)[1:] # Abbreviate t2c = { f"\\text{{{shape_name}}}": BLUE, "\\text{F}_j": YELLOW, "\\text{Face}": YELLOW, "\\text{Total}": GREEN, "S": GREY_B, "{c}": RED, } kw = { "tex_to_color_map": t2c } lhs.alt1 = OldTex(f"S(\\text{{{shape_name}}})", **kw) summand.alt1 = OldTex("S(\\text{F}_j)", **kw) def get_s_cube_term(i="i"): return OldTex(f"S\\big(R_{{{i}}}", f"(\\text{{{shape_name}}})\\big)", **kw) def get_s_f_term(i="i", j="j"): result = OldTex( f"S\\big(R_{{{i}}}", "(", "\\text{F}_{" + str(j) + "}", ")", "\\big)", **kw ) result[3].set_color(YELLOW) return result lhs.alt2 = get_s_cube_term(1) summand.alt2 = get_s_f_term(1) for mob, vect in (lhs, RIGHT), (summand, LEFT): mob.brace = Brace(mob, DOWN, buff=SMALL_BUFF) mob.alt1.next_to(mob.brace, DOWN) self.play( GrowFromCenter(mob.brace), FadeIn(mob.alt1, shift=0.5 * DOWN), ) self.wait() self.play( FadeOut(mob.brace, scale=0.5), FadeOut(mob, shift=0.5 * UP), mob.alt1.animate.move_to(mob, vect), ) mob.alt2.move_to(mob, vect) self.wait() for mob in lhs, summand: self.play(TransformMatchingShapes(mob.alt1, mob.alt2)) self.wait() # Add up many rotations lhss = VGroup( get_s_cube_term(1), get_s_cube_term(2), get_s_cube_term(3), OldTex("\\vdots"), get_s_cube_term("n"), ) buff = 0.6 lhss.arrange(DOWN, buff=buff) lhss.move_to(lhs.alt2, UP) self.remove(lhs.alt2) self.play( LaggedStart(*( ReplacementTransform(lhs.alt2.copy(), target) for target in lhss )), frame.animate.set_height(10, about_edge=UP), run_time=2, ) # Show empirical mean h_line = Line(LEFT, RIGHT) h_line.set_width(lhss.get_width() + 0.75) h_line.next_to(lhss, DOWN, MED_SMALL_BUFF, aligned_edge=RIGHT) plus = OldTex("+") plus.align_to(h_line, LEFT).shift(0.1 * RIGHT) plus.match_y(lhss[-1]) total = Text("Total", font_size=60) total.set_color(GREEN) total.next_to(h_line, DOWN, buff=0.35) total.match_x(lhss) mean_sa = OldTex( f"S(\\text{{{shape_name}}})", "=", "\\frac{1}{n}", "\\sum_{i=1}^n S\\big(R_i(" + f"\\text{{{shape_name}}})\\big)", **kw, ) mean_sa.add_to_back(get_overline(mean_sa.slice_by_tex(None, "="))) mean_sa.next_to(total, DOWN, LARGE_BUFF, aligned_edge=RIGHT) corner_rect = SurroundingRectangle(mean_sa, buff=0.25) corner_rect.set_stroke(WHITE, 2) corner_rect.set_fill(GREY_E, 1) corner_rect.move_to(frame, DL) corner_rect.shift(0.025 * UR) mean_sa.move_to(corner_rect) sum_part = mean_sa.slice_by_tex("sum") sigma = sum_part[0] sigma.save_state() lhss_rect = SurroundingRectangle(lhss) lhss_rect.set_stroke(BLUE, 2) sigma.next_to(lhss_rect, LEFT) sum_group = VGroup(lhss, lhss_rect) self.play( Write(lhss_rect), Write(sigma), ) self.wait() self.add(corner_rect, sigma) self.play( FadeIn(corner_rect), *( FadeTransform(term.copy(), sum_part[1:]) for term in lhss ), Restore(sigma), ) self.play(Write(mean_sa.get_part_by_tex("frac"))) self.wait() self.play( FadeIn(mean_sa.slice_by_tex(None, "frac")), ) self.wait() # Create grid sf = get_s_f_term grid_terms = [ [sf(1, 1), sf(1, 2), OldTex("\\dots"), sf(1, n_faces)], [sf(2, 1), sf(2, 2), OldTex("\\dots"), sf(2, n_faces)], [sf(3, 1), sf(3, 2), OldTex("\\dots"), sf(3, n_faces)], [Tex("\\vdots"), OldTex("\\vdots"), OldTex("\\ddots"), OldTex("\\vdots")], [sf("n", 1), sf("n", 2), OldTex("\\dots"), sf("n", n_faces)], ] grid = VGroup(*(VGroup(*row) for row in grid_terms)) for lhs, row in zip(lhss, grid): for i in range(len(row) - 1, 0, -1): is_dots = "dots" in row[0].get_tex() sym = VectorizedPoint() if is_dots else OldTex("+") row.insert_submobject(i, sym) row.arrange(RIGHT, buff=MED_SMALL_BUFF) for m1, m2 in zip(row, grid[0]): m1.match_x(m2) m1.match_y(lhs) if not is_dots: parens = OldTex("[]", font_size=72)[0] parens.set_stroke(width=2) parens.set_color(BLUE_B) parens[0].next_to(row, LEFT, buff=SMALL_BUFF) parens[1].next_to(row, RIGHT, buff=SMALL_BUFF) row.add(*parens) eq_half = OldTex("=", "\\frac{1}{2}") eq_half[1].match_height(parens) eq_half.next_to(parens[0], LEFT, MED_SMALL_BUFF) row.add(*eq_half) grid.set_x(frame.get_right()[0] - 1.5, RIGHT) self.remove(summand.alt2) self.play( sum_group.animate.set_x(grid.get_left()[0] - MED_SMALL_BUFF, RIGHT), TransformMatchingShapes( VGroup( equation.get_part_by_tex("frac"), equation.get_part_by_tex("="), ), grid[0][-4:], ), LaggedStart(*( FadeTransform(summand.alt2.copy(), part) for part in grid[0][0:7:2] )), FadeOut(equation.get_part_by_tex("sum"), scale=0.25), Write(grid[0][1:7:2]), # Plus signs ) self.wait(2) self.play(FadeTransform(grid[0].copy(), grid[1])) self.play(FadeTransform(grid[1].copy(), grid[2])) self.play(FadeTransform(grid[2].copy(), grid[4]), FadeIn(grid[3])) self.wait(2) # Average along columns cols = VGroup(*( VGroup(*(row[i] for row in grid)).copy() for i in [0, 2, 6] )) col_rects = VGroup(*( SurroundingRectangle(col, buff=SMALL_BUFF) for col in cols )) col_rects.set_stroke(YELLOW, 1) mean_face = OldTex("S(\\text{Face})", **kw) mean_face.add_to_back(get_overline(mean_face)) mean_face.next_to(grid, DOWN, buff=2) mean_face_words = OldTexText("Average shadow\\\\of one face") mean_face_words.move_to(mean_face, UP) arrows = VGroup(*( Arrow(rect.get_bottom(), mean_face) for rect in col_rects )) arrow_labels = OldTex("\\frac{1}{n} \\sum \\cdots", font_size=30).replicate(3) for arrow, label in zip(arrows, arrow_labels): vect = rotate_vector(normalize(arrow.get_vector()), PI / 2) label.next_to(arrow.pfp(0.5), vect, SMALL_BUFF) self.add(cols[0]) self.play( grid.animate.set_opacity(0.4), ShowCreation(col_rects[0]) ) self.wait() self.play( ShowCreation(arrows[0]), FadeIn(arrow_labels[0]), FadeIn(mean_face_words, DR), ) self.wait() self.play( mean_face_words.animate.scale(0.7).next_to(mean_face, DOWN, MED_LARGE_BUFF), FadeIn(mean_face, scale=2), ) self.wait() for i in [1, 2]: self.play( FadeOut(cols[i - 1]), FadeIn(cols[i]), *( ReplacementTransform(group[i - 1], group[i]) for group in (col_rects, arrows, arrow_labels) ) ) self.wait() self.wait() # Reposition frame.generate_target() frame.target.align_to(total, DOWN) frame.target.shift(0.5 * DOWN) frame.target.scale(1.15) frame.target.align_to(lhss, LEFT).shift(0.25 * LEFT) self.play(LaggedStart( VGroup(corner_rect, mean_sa).animate.scale(1.25).move_to( frame.target, UL ).shift(0.1 * DR), MoveToTarget(frame), FadeOut(mean_face_words), FadeOut(mean_face), grid.animate.set_opacity(1), *( FadeOut(group[-1]) for group in (cols, col_rects, arrows, arrow_labels) ), run_time=3 )) mean_sa.refresh_bounding_box() # ?? # Show final result rhss = VGroup( OldTex("=", "\\frac{1}{2}", "\\sum_{j=1}^" + f"{{{n_faces}}}", " S(\\text{F}_j})", **kw), OldTex("=", "\\frac{1}{2}", "\\sum_{j=1}^" + f"{{{n_faces}}}", " {c}", "\\cdot ", "A(\\text{F}_j)", **kw), OldTex("=", "\\frac{1}{2}", "{c}", "\\cdot ", "(\\text{Surface area})", **kw), ) rhss[0].add(get_overline(rhss[0].slice_by_tex("S"))) rhss[2][-2].set_color(WHITE) rhss.arrange(RIGHT) rhss.next_to(mean_sa, RIGHT) corner_rect.generate_target() corner_rect.target.set_width( frame.get_width() - 0.2, stretch=True, about_edge=LEFT, ) grid_rect = SurroundingRectangle(grid, buff=SMALL_BUFF) grid_rect.set_stroke(YELLOW, 1) grid_rect.set_fill(YELLOW, 0.25) rects = VGroup( SurroundingRectangle(mean_sa.slice_by_tex("frac")), SurroundingRectangle(rhss[0][1:]) ) for rect in rects: rect.match_height(corner_rect.target, stretch=True) rect.match_y(corner_rect.target) rects[0].set_color(BLUE) rects[1].set_color(YELLOW) rects.set_stroke(width=2) rects.set_fill(opacity=0.25) rows_first = Text("Rows first") rows_first.next_to(rects[0], DOWN) rows_first.match_color(rects[0]) cols_first = Text("Columns first") cols_first.next_to(rects[1], DOWN) cols_first.match_color(rects[1]) self.add(corner_rect, mean_sa) self.play( MoveToTarget(corner_rect), Write(rhss[0]) ) self.add(grid_rect, grid) self.play(VFadeInThenOut(grid_rect, run_time=2)) self.wait() self.play( Write(rects[0]), Write(rows_first), ) self.wait() self.play( Write(rects[1]), Write(cols_first), ) self.wait() self.play(LaggedStart(*map(FadeOut, ( *rects, rows_first, cols_first )))) self.play( TransformMatchingShapes(rhss[0].copy(), rhss[1]) ) self.wait() self.play( FadeTransform(rhss[1].copy(), rhss[2]), ) self.wait() key_part = rhss[2][1:] final_rect = SurroundingRectangle(key_part) final_rect.set_stroke(YELLOW, 1) self.play( corner_rect.animate.set_stroke(width=0).scale(1.1), FlashAround(key_part, time_width=1.5), FadeIn(final_rect), run_time=2, ) class LimitBrace(Scene): def construct(self): brace = Brace(Line().set_width(3), UP) tex = brace.get_tex("n \\to \\infty") VGroup(brace, tex).set_color(TEAL) VGroup(brace, tex).set_backstroke() self.play( GrowFromCenter(brace), FadeIn(tex, shift=0.25 * UP) ) self.wait() class FromRowsToColumns(Scene): def construct(self): n = 5 grid = Dot().get_grid(n, n, buff=MED_LARGE_BUFF) grids = grid.get_grid(1, 2, buff=3) buff = 0.2 row_rects = VGroup(*( SurroundingRectangle(grids[0][k:k + n], buff=buff) for k in range(0, n * n, n) )) col_rects = VGroup(*( SurroundingRectangle(grids[1][k::n], buff=buff) for k in range(n) )) rects = VGroup(row_rects, col_rects) rects.set_fill(opacity=0.25) rects.set_stroke(width=2) row_rects.set_color(BLUE) col_rects.set_color(YELLOW) # plus_template = OldTex("+") # plus_template.match_height(grids[0][0]) # for grid in grids: # plusses = VGroup() # for k, dot in enumerate(grid): # if k % n != n - 1: # pc = plus_template.copy() # pc.move_to(midpoint(dot.get_center(), grid[k + 1].get_center())) # plusses.add(pc) # grid.add(plusses) arrow = Arrow(grids[0], grids[1], buff=0.5) self.add(grids[0]) self.play(FadeIn(row_rects, lag_ratio=0.2)) self.wait() self.play( TransformFromCopy(grids[0], grids[1]), ShowCreation(arrow) ) self.play(FadeIn(col_rects, lag_ratio=0.2)) self.wait() class ComplainAboutProgress(TeacherStudentsScene): def construct(self): self.student_says( OldTexText("Wait, is that all\\\\we've accomplished?"), target_mode="angry", ) self.play_student_changes( "guilty", "erm", look_at=self.students[2].eyes, added_anims=[self.teacher.change("guilty")], ) self.wait(4) class SupposedlyObviousProportionality(ShadowScene): solid_name = "Cube" def construct(self): # Setup cube = self.solid shadow = self.shadow frame = self.camera.frame frame.set_z(3) frame.reorient(-20, 80) self.init_frame_rotation() light = self.light light.next_to(cube, OUT, 50) equation = get_key_result(self.solid_name) equation.fix_in_frame() equation.to_edge(UP) self.add(equation) # Rotation self.begin_ambient_rotation(cube) self.wait() # Ask about constant question = OldTexText( "What is $c$?!", font_size=72, tex_to_color_map={"$c$": RED} ) question.fix_in_frame() question.next_to(equation, DOWN, LARGE_BUFF) question.to_edge(RIGHT, buff=LARGE_BUFF) c_arrow = Arrow( equation.get_part_by_tex("{c}"), question.get_corner(UL), ) c_arrow.set_color(RED) c_arrow.fix_in_frame() self.play( ShowCreation(c_arrow), Write(question) ) self.wait(4) # "Obvious" obvious_words = OldTexText("Isn't this obvious?", font_size=36) obvious_words.set_color(GREY_A) obvious_words.match_y(question) obvious_words.to_edge(LEFT) obvious_arrow = Arrow( obvious_words, equation.get_corner(DL) + RIGHT ) VGroup(obvious_words, obvious_arrow).fix_in_frame() self.play( TransformMatchingShapes(question, obvious_words), ReplacementTransform(c_arrow, obvious_arrow), ) self.wait() # 2d quantities cube.clear_updaters() shadow_label = OldTexText("2D") shadow_label.move_to(shadow) face_labels = VGroup() for face in cube[3:]: lc = shadow_label.copy() normal = face.get_unit_normal() lc.rotate(angle_of_vector(flat_project(normal)) + PI / 2) lc.apply_matrix( rotation_between_vectors(OUT, normal) ) lc.move_to(face) face.label = lc face_labels.add(lc) self.play( Write(shadow_label), Write(face_labels), run_time=2, ) self.wait() scalars = [cube, face_labels, shadow_label] self.play( *( mob.animate.scale(0.5) for mob in scalars ), rate_func=there_and_back, run_time=3 ) self.play( *( mob.animate.stretch(2, 0) for mob in scalars ), rate_func=there_and_back, run_time=3 ) self.wait() # No not really no_words = Text("No, not really", font_size=30) no_words.set_color(RED) no_words.fix_in_frame() no_words.next_to(obvious_words, DOWN, MED_LARGE_BUFF) self.play( FadeIn(no_words, 0.25 * DOWN), FadeOut(shadow_label), FadeOut(face_labels), ) # Move light self.play( light.animate.next_to(cube, OUT + RIGHT, 2), run_time=4, rate_func=rush_from, ) for s in (1.5, 0.25, 2 / 0.75): self.play( cube.animate.scale(s), run_time=2, ) # To finish cube.add_updater(lambda m: self.sort_to_camera(m)) self.begin_ambient_rotation(cube) self.play( light.animate.next_to(cube, OUT, 50), LaggedStart(*map(FadeOut, ( no_words, obvious_words, obvious_arrow, ))), run_time=3, ) self.wait(33) class LurkingAssumption(VideoWrapper): title = "There's a subtle hidden assumption..." wait_time = 4 animate_boundary = False class WhatIsC(Scene): def construct(self): words = OldTexText( "What is $c$?!", tex_to_color_map={"$c$": RED}, font_size=72, ) self.play(Write(words)) self.play(FlashUnder(words, color=RED)) self.wait() class BobsFinalAnswer(Scene): def construct(self): answer = OldTex( "S(\\text{Cube})", "=", "\\frac{1}{2}", "\\cdot", "{\\frac{1}{2}}", "(\\text{Surface area})", "=", "\\frac{1}{4} \\big(6s^2\\big)", "=", "\\frac{3}{2} s^2", tex_to_color_map={ "\\text{Cube}": BLUE, "{\\frac{1}{2}}": RED, } ) answer.add_to_back(get_overline(answer[:3])) equals = answer.get_parts_by_tex("=") eq_indices = list(map(answer.index_of_part, equals)) eq1 = answer[:eq_indices[1]].deepcopy() eq2 = answer[:eq_indices[2]].deepcopy() eq3 = answer.deepcopy() for eq in eq1, eq2, eq3: eq.to_edge(RIGHT) eq.shift(1.25 * UP) self.play(FadeIn(eq1, DOWN)) self.wait() for m1, m2 in (eq1, eq2), (eq2, eq3): self.play( FadeIn(m2[len(m1):]), m1.animate.move_to(m2, LEFT), ) self.remove(m1) self.add(m2) self.wait() rect = SurroundingRectangle(eq3[-1]) rect.set_stroke(YELLOW, 2) self.play( ShowCreation(rect), FlashAround(eq3[-1], stroke_width=5, time_width=1.5, run_time=1.5) ) self.wait() class ShowSeveralConvexShapes(Scene): def construct(self): frame = self.camera.frame frame.reorient(0, 70) dodec = Dodecahedron() dodec.set_fill(BLUE_D) spike = VGroup() hexagon = RegularPolygon(6) spike.add(hexagon) for v1, v2 in adjacent_pairs(hexagon.get_vertices()): spike.add(Polygon(v1, v2, 2 * OUT)) spike.set_fill(BLUE_E) blob = Group(Sphere()) blob.stretch(0.5, 0) blob.stretch(0.5, 1) blob.set_color(BLUE_E) blob.apply_function( lambda p: [*(2 - p[2]) * p[:2], p[2]] ) blob.set_color(BLUE_E) cylinder = Group(Cylinder()) cylinder.set_color(GREY_BROWN) cylinder.rotate(PI / 4, UP) examples = Group(*( Group(*mob) for mob in (dodec, spike, blob, cylinder) )) for ex in examples: ex.set_depth(2) ex.deactivate_depth_test() ex.set_gloss(0.5) ex.set_shadow(0.5) ex.set_reflectiveness(0.2) ex.rotate(20 * DEGREES, OUT) sort_to_camera(ex, self.camera.frame) for sm in ex: if isinstance(sm, VMobject): sm.set_stroke(WHITE, 1) sm.set_fill(opacity=0.9) else: sm.always_sort_to_camera(self.camera) examples.arrange(RIGHT, buff=LARGE_BUFF) self.play(LaggedStart(*( LaggedStartMap(Write, ex, run_time=1) if isinstance(ex[0], VMobject) else ShowCreation(ex[0]) for ex in examples ), lag_ratio=0.5, run_time=8)) self.wait() class KeyResult(Scene): def construct(self): eq1, eq2 = [ get_key_result(word) for word in ("Cube", "Solid") ] VGroup(eq1, eq2).to_edge(UP) self.play(FadeIn(eq1, lag_ratio=0.1)) self.wait() cube_underline = Underline(eq2.get_part_by_tex("Solid"), buff=0.05) cube_underline.set_stroke(BLUE, 1) general_words = Text("Assume convex", font_size=36) general_words.set_color(BLUE) general_words.next_to(cube_underline, DOWN, buff=1.0) general_words.shift(LEFT) general_arrow = Arrow(general_words, cube_underline.get_center(), buff=0.1) self.play( ShowCreation(cube_underline), FadeIn(general_words), ShowCreation(general_arrow), FadeTransformPieces(eq1, eq2), ) self.wait() const_rect = SurroundingRectangle(VGroup( eq.get_part_by_tex("frac"), eq.get_part_by_tex("{c}") ), buff=SMALL_BUFF) const_rect.set_stroke(RED, 1) const_words = Text("Universal constant!", font_size=36) const_words.set_color(RED) const_words.match_y(general_words) const_words.set_x(const_rect.get_x() + 1) const_arrow = Arrow(const_words, const_rect, buff=0.1) self.play( ShowCreation(const_rect), FadeIn(const_words), ShowCreation(const_arrow), ) self.wait() class ShadowsOfDodecahedron(ShadowScene): inf_light = True def construct(self): # Setup self.camera.frame.set_height(7) self.camera.frame.shift(OUT) dodec = self.solid dodec.scale(5 / dodec[0].get_arc_length()) outline = self.get_shadow_outline() area = DecimalNumber(font_size=36) area.move_to(outline) area.add_updater(lambda m: m.set_value(get_norm(outline.get_area_vector()) / (self.unit_size**2))) area.add_updater(lambda m: m.fix_in_frame()) area.move_to(3.15 * DOWN) self.init_frame_rotation() ssf = 1.5 self.wait() self.play(dodec.animate.space_out_submobjects(ssf)) self.play(Rotate(dodec, PI, axis=RIGHT, run_time=6)) self.play(dodec.animate.space_out_submobjects(1 / ssf)) self.begin_ambient_rotation(dodec) self.play( VFadeIn(outline), VFadeIn(area), ) # Add dot and line dot = GlowDot(0.5 * DR) line = DashedLine(10 * OUT, ORIGIN) line.set_stroke(YELLOW, 1) def dodec_sdf(point): return max(*( np.dot(point - pent.get_center(), pent.get_unit_normal()) for pent in dodec )) def update_line(line): line.move_to(dot.get_center(), IN) for dash in line: dist = dodec_sdf(dash.get_center()) dash.set_stroke( opacity=interpolate(0.1, 1.0, clip(10 * dist, -0.5, 0.5) + 0.5) ) dash.inside = (dist < 0) line.add_updater(update_line) self.play(ShowCreation(line, rate_func=rush_into)) self.play(FadeIn(dot, rate_func=rush_from)) # Just wait for n in range(8): self.play(dot.animate.move_to(midpoint( outline.get_center(), outline.pfp(random.random()), )), run_time=5) def get_solid(self): solid = self.get_solid_no_style() solid.set_stroke(WHITE, 1) solid.set_fill(self.solid_fill_color, 0.8) solid.set_gloss(0.1) solid.set_shadow(0.4) solid.set_reflectiveness(0.4) group = Group(*solid) group.deactivate_depth_test() group.add_updater(lambda m: self.sort_to_camera(m)) return group def get_solid_no_style(self): dodec = Dodecahedron() dodec.scale((32 / get_surface_area(dodec))**0.5) return dodec class AmbientDodecahedronShadow(ShadowsOfDodecahedron): solid_name = "Dodecahedron" solid_fill_color = BLUE_E name_color = BLUE_D def construct(self): self.camera.frame.reorient(20, 80) self.camera.frame.set_z(3) eq = get_key_result(self.solid_name, color=self.name_color) eq.to_edge(UP) eq.fix_in_frame() self.add(eq) self.init_frame_rotation() self.play(LaggedStart(*map(Write, self.solid))) self.add(self.solid) outline = self.get_shadow_outline() area_label = self.get_shadow_area_label() # area_label.scale(0.7) area_label.move_to(2.75 * DOWN).to_edge(LEFT) area_label.add_updater(lambda m: m.fix_in_frame()) surface_area = get_surface_area(self.solid) surface_area /= (self.unit_size**2) sa_label = VGroup(Text("Surface area: "), DecimalNumber(surface_area)) sa_label.arrange(RIGHT) sa_label.match_y(area_label) sa_label.to_edge(RIGHT) sa_label.set_backstroke() sa_label.fix_in_frame() self.play( *map(VFadeIn, (outline, area_label, sa_label)) ) self.add(outline) self.add(area_label) self.begin_ambient_rotation(self.solid, about_point=self.solid.get_center()) self.wait(30) class AmbientTriPrismSum(AmbientDodecahedronShadow): solid_name = "Triangular Prism" solid_fill_color = interpolate_color(TEAL_E, BLACK, 0.25) name_color = TEAL_D def get_solid_no_style(self): triangle = RegularPolygon(3) tri1, tri2 = triangle.replicate(2) tri2.shift(3 * OUT) sides = [] verts1 = tri1.get_anchors() verts2 = tri2.get_anchors() for (a, b), (c, d) in zip(adjacent_pairs(verts1), adjacent_pairs(verts2)): sides.append(Polygon(a, b, d, c)) result = VGroup(tri1, *sides, tri2) result.scale((16 / get_surface_area(result))**0.5) return result class AmbientPyramidSum(AmbientDodecahedronShadow): solid_name = "Pyramid" solid_fill_color = GREY_BROWN name_color = interpolate_color(GREY_BROWN, WHITE, 0.5) def get_solid_no_style(self): base = Square(side_length=1) result = VGroup(base) for v1, v2 in adjacent_pairs(base.get_vertices()): result.add(Polygon(v1, v2, math.sqrt(3) * OUT / 2)) result.set_height(2) return result class AmbientCubeWithLabels(AmbientDodecahedronShadow): solid_name = "Cube" def get_solid_no_style(self): return VCube() class DodecahedronFaceSum(Scene): def construct(self): expr = OldTexText( "Area(Shadow(Dodecahedron))", "=", "$\\displaystyle \\frac{1}{2}$", " $\\displaystyle \\sum_{j=1}^{12}$ ", "Area(Shadow(Face$_j$))", tex_to_color_map={ "Shadow": GREY_B, "Dodecahedron": BLUE_D, "Face$_j$": YELLOW, } ) expr.to_edge(UP, buff=MED_SMALL_BUFF) self.add(expr.slice_by_tex(None, "=")) self.wait() self.play(FadeIn(expr.slice_by_tex("="), shift=0.25 * UP)) self.wait() self.play(FlashAround(expr.get_part_by_tex("frac"), run_time=2)) self.play(FlashUnder(expr[-5:], run_time=2)) self.wait(2) class SphereShadow(ShadowScene): inf_light = True def construct(self): frame = self.camera.frame frame.set_height(7) frame.shift(OUT) sphere = self.solid shadow = self.shadow # shadow[1].always_sort_to_camera(self.camera) shadow_circle = Circle() shadow_circle.set_fill(BLACK, 0.8) shadow_circle.replace(shadow) shadow_circle.set_stroke(WHITE, 1) self.add(shadow_circle) self.begin_ambient_rotation( sphere, speed=0.3, initial_axis=[-1, -1, 0.5] ) self.wait(60) def get_solid(self): ep = 1e-3 sphere = Sphere( radius=1.5, u_range=(ep, TAU - ep), v_range=(ep, PI - ep), ) sphere = TexturedSurface(sphere, "EarthTextureMap", "NightEarthTextureMap") sphere.set_opacity(1) sphere.always_sort_to_camera(self.camera) mesh = SurfaceMesh(sphere) mesh.set_stroke(WHITE, 0.5, 0.25) return Group(sphere, mesh) class SphereInfo(Scene): def construct(self): kw = { "tex_to_color_map": { "{c}": RED, "=": WHITE, "R": BLUE }, "font_size": 36 } shadow = OldTex("\\text{Average shadow area} = \\pi R^2", **kw) surface = OldTex("\\text{Surface area} = 4 \\pi R^2", **kw) conclusion = OldTex("\\frac{1}{2}", "{c}", "=", "\\frac{1}{4}", **kw) eqs = VGroup(shadow, surface, conclusion) eqs.arrange(DOWN, buff=MED_LARGE_BUFF) for eq in eqs: eq.shift(eq.get_part_by_tex("=").get_x() * LEFT) eqs.to_corner(UR) for eq in eqs[:2]: eq[0].set_color(GREY_A) for eq in eqs: self.play(FadeIn(eq, lag_ratio=0.1)) self.wait() self.play(eqs[2].animate.scale(2, about_edge=UP)) rect = SurroundingRectangle(eqs[2]) rect.set_stroke(YELLOW, 2) self.play(ShowCreation(rect)) self.wait() class PiRSquared(Scene): def construct(self): form = OldTex("\\pi R^2")[0] form[1].set_color(BLUE) self.play(Write(form)) self.wait() class SwapConstantForFourth(Scene): def construct(self): eq = get_key_result("Dodecahedron") eq.to_edge(UP) parts = VGroup( eq.get_part_by_tex("frac"), eq.get_part_by_tex("{c}") ) fourth = OldTex("\\frac{1}{4}") fourth.move_to(parts, LEFT) fourth.set_color(RED) self.add(eq) self.wait() self.play( FadeOut(parts, UP), FadeIn(fourth, UP), eq.slice_by_tex("\\cdot").animate.next_to(fourth, RIGHT), ) self.wait() class ButSpheresAreSmooth(TeacherStudentsScene): def construct(self): self.student_says( OldTexText("But spheres don't\\\\have flat faces!"), target_mode="angry", index=2, added_anims=[self.teacher.change("guilty")] ) self.play_student_changes( "erm", "hesitant", "angry", look_at=self.screen, ) self.wait(6) class RepeatedRelation(Scene): def construct(self): # Relations relation = VGroup( Text("Average shadow"), OldTex("=").rotate(PI / 2), OldTex("\\frac{1}{2}", "c", "\\cdot (", "\\text{Surface area}", ")") ) relation[0].set_color(GREY_A) relation[2][1].set_color(RED) relation[2][3].set_color(BLUE) relation.arrange(DOWN) relation.scale(0.6) repeats = relation.get_grid(1, 4, buff=0.8) repeats.to_edge(LEFT, buff=MED_LARGE_BUFF) repeats.shift(0.5 * DOWN) for repeat in repeats: self.play(FadeIn(repeat[0], lag_ratio=0.1)) self.play( Write(repeat[1]), FadeIn(repeat[2], 0.5 * DOWN) ) self.wait() # Limit limit = OldTex( "\\lim_{|F| \\to 0}", "\\left(", "{\\text{Average shadow}", "\\over ", "\\text{Surface area}}", "\\right)", "=", "\\frac{1}{2}", "{c}", ) limit.set_color_by_tex("Average shadow", GREY_A) limit.set_color_by_tex("Surface area", BLUE) limit.set_color_by_tex("{c}", RED) limit.move_to(2.5 * DOWN) limit.match_x(repeats) new_rhs = OldTex("=", "{\\pi R^2", "\\over", "4\\pi R^2}") new_rhs.set_color_by_tex("\\pi R^2", GREY_A) new_rhs.set_color_by_tex("4\\pi R^2", BLUE) new_rhs.move_to(limit.get_part_by_tex("="), LEFT) self.play(Write(limit)) self.wait() self.play( limit.slice_by_tex("=").animate.next_to(new_rhs, RIGHT), GrowFromCenter(new_rhs) ) self.wait() class SimpleCross(Scene): def construct(self): lines = VGroup( Line(UP, DOWN).set_height(FRAME_HEIGHT), Line(LEFT, RIGHT).set_width(FRAME_WIDTH), ) self.play(ShowCreation(lines, lag_ratio=0.5)) self.wait() # Not needed? class AmbientCubeTurningIntoNewShapes(Scene): def construct(self): pass class PopularizaitonVsDoing(Scene): def construct(self): # Words popular = Text("Popularization of math") doing = Text("Doing math") words = VGroup(popular, doing) words.arrange(DOWN, buff=3) words.move_to(UP) self.play(FadeIn(popular, UP)) self.wait() self.play( TransformFromCopy( popular.get_part_by_text("math"), doing.get_part_by_text("math"), ), Write(doing[:len("Doing")]) ) self.wait() # Bars width = 8 bar = Rectangle(width, 0.5) bar.set_stroke(WHITE, 1) bar.next_to(popular, DOWN) left_bar, right_bar = bar.replicate(2) left_bar.set_fill(BLUE_E, 1) right_bar.set_fill(RED_E, 1) left_bar.stretch(0.5, 0, about_edge=LEFT) right_bar.stretch(0.5, 0, about_edge=RIGHT) left_brace = always_redraw(lambda: Brace(left_bar, DOWN, buff=SMALL_BUFF)) right_brace = always_redraw(lambda: Brace(right_bar, DOWN, buff=SMALL_BUFF)) left_label = Text("Insights", font_size=30, color=GREY_B) right_label = Text("Computations", font_size=30, color=GREY_B) always(left_label.next_to, left_brace, DOWN, SMALL_BUFF) always(right_label.next_to, right_brace, DOWN, SMALL_BUFF) bar_group = VGroup( bar, left_bar, right_bar, left_brace, right_brace, left_label, right_label, ) def set_bar_alpha(alpha, **kwargs): self.play( left_bar.animate.set_width(alpha * width, about_edge=LEFT, stretch=True), right_bar.animate.set_width((1 - alpha) * width, about_edge=RIGHT, stretch=True), **kwargs ) self.play(FadeIn(bar_group, lag_ratio=0.1)) set_bar_alpha(0.95, run_time=2) self.wait() self.add(bar_group.deepcopy().clear_updaters()) self.play( bar_group.animate.shift(doing.get_center() - popular.get_center()) ) set_bar_alpha(0.05, run_time=5) self.wait() # Embed self.embed() class MultipleMathematicalBackgrounds(TeacherStudentsScene): def construct(self): self.remove(self.background) labels = VGroup( OldTexText("$\\le$ High school"), OldTexText("$\\approx$ Undergrad"), OldTexText("$\\ge$ Ph.D."), ) for student, label in zip(self.students, labels): label.scale(0.7) label.next_to(student, UP) words = OldTexText("Explanation doesn't vary\\\\with backgrounds") words.to_edge(UP) lines = VGroup(*( DashedLine(words, label, buff=0.5) for label in labels )) lines.set_stroke(WHITE, 2) self.add(words) self.play( self.teacher.change("raise_right_hand"), self.change_students( "pondering", "thinking", "pondering", look_at=self.teacher.eyes, ), ) self.play( LaggedStartMap(ShowCreation, lines, lag_ratio=0.5), LaggedStartMap(FadeIn, labels, lag_ratio=0.5), ) self.wait(3) self.play( self.teacher.change("dejected").look(UP), self.change_students("hesitant", "well", "thinking"), LaggedStartMap(FadeOut, lines, scale=0.5), FadeOut(words, DOWN), ) self.wait(4) # Different levels kw = {"font_size": 30} methods = VGroup( OldTexText("Calculus\\\\primer", **kw), OldTexText("Quickly show\\\\key steps", **kw), OldTexText("Describe as a\\\\measure on SO(3)", **kw), ) new_lines = VGroup() colors = [GREEN_B, GREEN_C, GREEN_D] for method, label, color in zip(methods, labels, colors): method.move_to(label) method.shift(2.5 * UP) method.set_color(color) line = DashedLine(method, label, buff=0.25) line.set_stroke(color, 2) new_lines.add(line) self.play( self.teacher.change("raise_right_hand"), self.change_students( "erm", "pondering", "thinking", look_at=self.students.get_center() + 4 * UP ), LaggedStartMap(FadeIn, methods, lag_ratio=0.5), LaggedStartMap(ShowCreation, new_lines, lag_ratio=0.5), ) self.wait(4) class WatchingAVideo(Scene): def construct(self): self.add(FullScreenRectangle()) randy = Randolph() randy.to_corner(DL) screen = ScreenRectangle(height=5) screen.set_fill(BLACK, 1) screen.to_corner(UR) def blink_wait(n=1): for x in range(n): self.wait() self.play(Blink(randy)) self.wait() self.add(screen) self.add(randy) self.play(randy.change("pondering", screen)) blink_wait() self.play(randy.change("thinking", screen)) blink_wait() self.play(randy.change("hesitant", screen)) blink_wait(2) class CleverProofExample(Scene): def construct(self): initial_sum = OldTex("1^2 + 2^2 + 3^2 + \\cdots + n^2") tripple_tris, final_tri = self.get_triangle_sums() initial_sum.set_width(10) self.play(FadeIn(initial_sum, lag_ratio=0.1)) self.wait() tripple_tris.set_width(10) tripple_tris.to_edge(DOWN, buff=2) tris = tripple_tris[0] tri = tris[0] tri.save_state() tri.set_height(4) tri.center().to_edge(DOWN, buff=1) self.play( initial_sum.animate.set_width(8).to_edge(UP), FadeIn(tri, lag_ratio=0.1) ) self.wait() self.play( Restore(tri), FadeIn(tripple_tris[1:]) ) for i in (0, 1): bt1 = tris[i].copy() bt1.generate_target() bt1.target.rotate(120 * DEGREES) bt1.target.replace(tris[i + 1]) bt1.target.set_opacity(0) tris[i + 1].save_state() tris[i + 1].rotate(-120 * DEGREES) tris[i + 1].replace(tris[i]) tris[i + 1].set_opacity(0) self.play( MoveToTarget(bt1, remover=True), Restore(tris[i + 1]) ) self.wait() final_tri.set_height(3) final_tri.move_to(tripple_tris, UP) initial_sum.generate_target() eq = OldTex("=").scale(2) tripple_tris.generate_target() top_row = VGroup(initial_sum.target, eq, tripple_tris.target) top_row.arrange(RIGHT, buff=0.5) top_row.set_width(FRAME_WIDTH - 1) top_row.center().to_edge(UP) self.play( MoveToTarget(initial_sum), FadeIn(eq), MoveToTarget(tripple_tris), FadeTransform(tripple_tris.copy(), final_tri) ) self.wait() final1 = OldTex("= \\frac{2n + 1}{3} (1 + 2 + 3 + \\cdots + n)") final2 = OldTex("= \\frac{2n + 1}{3} \\frac{(n + 1)n}{2}") final3 = OldTex("= \\frac{(2n + 1)(n + 1)(n)}{6}") final_tri.generate_target() final_tri.target.set_height(2).to_edge(LEFT) for final in (final1, final2, final3): final.next_to(final_tri.target, RIGHT) self.play( MoveToTarget(final_tri), Write(final1), ) self.wait() self.play( FadeOut(final1, UP), FadeIn(final2, UP), ) self.wait() self.play( FadeOut(final2, UP), FadeIn(final3, UP), ) self.play(VGroup(final_tri, final3).animate.set_x(0)) self.wait() # Embed self.embed() def get_triangle_sums(self): dl_dots = OldTex("\\vdots").rotate(-30 * DEGREES) dr_dots = OldTex("\\vdots").rotate(30 * DEGREES) blank = Integer(0).set_opacity(0) n = OldTex("n") np1 = OldTex("(2n + 1)") dots = OldTex("\\dots") tri1 = VGroup( Integer(1).replicate(1), Integer(2).replicate(2), Integer(3).replicate(3), VGroup(dl_dots, blank.copy(), blank.copy(), dr_dots), VGroup(n.copy(), n.copy(), dots, n.copy(), n.copy()), ) tri2 = VGroup( n.replicate(1), VGroup(dl_dots, n).copy(), VGroup(Integer(3), blank, dr_dots).copy(), VGroup(Integer(2), Integer(3), dots, n).copy(), VGroup(Integer(1), Integer(2), Integer(3), dots, n).copy(), ) tri3 = VGroup( n.replicate(1), VGroup(n, dr_dots).copy(), VGroup(dl_dots, blank, Integer(3)).copy(), VGroup(n, dots, Integer(3), Integer(2)).copy(), VGroup(n, dots, Integer(3), Integer(2), Integer(1)).copy(), ) sum_tri = VGroup( np1.replicate(1), np1.replicate(2), np1.replicate(3), VGroup(dl_dots, *blank.replicate(6), dr_dots).copy(), VGroup(np1.copy(), np1.copy(), dots.copy(), np1.copy(), np1.copy()), ) tris = VGroup(tri1, tri2, tri3) for tri in (*tris, sum_tri): for row in tri: row.arrange(RIGHT, buff=0.5) tri.arrange(DOWN, buff=0.5) tris.arrange(RIGHT, buff=2.0) tris.set_width(6) plusses = VGroup( OldTex("+").move_to(tris[:2]), OldTex("+").move_to(tris[1:]), ) parens = OldTex("()")[0] parens.stretch(2, 1) parens.match_height(tris) parens[0].next_to(tris, LEFT) parens[1].next_to(tris, RIGHT) frac = OldTex("\\frac{1}{3}") frac.next_to(parens, LEFT) lhs = VGroup(tris, frac, parens, plusses) parens_copy = parens.copy() sum_tri.match_height(lhs) sum_tri.move_to(tris, LEFT) parens_copy[1].next_to(sum_tri, RIGHT) rhs = VGroup(frac.copy(), sum_tri, parens_copy) rhs.next_to(lhs, DOWN, LARGE_BUFF, aligned_edge=LEFT) # eq = OldTex("=") # eq.next_to(rhs, LEFT) return VGroup(lhs, rhs) class BlendOfMindsets(Scene): def construct(self): Text("Calculate specifics") Text("Understand generalities") Text("You need both") class ListernerEmail(Scene): def construct(self): # Letter rect = Rectangle(4, 7) rect.set_stroke(WHITE, 2) rect.set_fill("#060606", 1) lines = Line(LEFT, RIGHT).get_grid(15, 1) lines.set_width(0.8 * rect.get_width()) lines.arrange(DOWN) lines.set_height(0.7 * rect.get_height(), stretch=True) for n in [3, 8, -1]: lines[n].stretch(0.5, 0, about_edge=LEFT) if n > 0: lines[n + 1].set_opacity(0) lines.move_to(rect) salutation = Text("Hi Prof. Kontorovich,", font_size=30) salutation.next_to(lines, UP, aligned_edge=LEFT) lines.shift(0.2 * DOWN) letter = VGroup(rect, lines, salutation) self.add(rect) self.play( Write(salutation, run_time=1), ShowCreation(lines, rate_func=linear, run_time=3, lag_ratio=0.5), ) self.add(letter) self.wait() self.play(letter.animate.to_edge(LEFT)) # Phrases phrases = VGroup( Text("I’m a PhD student..."), Text( "...I had noticed my mathematical capabilities\n" "starting to fade (to which I attributed getting\n" "older and not being as sharp)..." ), Text( "...I realized that the entire problem, for me at least,\n" "was entirely about my lack of problems and drills." ), ) phrases.arrange(DOWN, buff=2.0, aligned_edge=LEFT) phrases.set_width(8) phrases.next_to(letter, RIGHT, LARGE_BUFF) highlights = VGroup() for i, w in [(0, 1), (5, 3), (11, 2.5)]: hrect = Rectangle(w, 0.1) hrect.set_stroke(width=0) hrect.set_fill(YELLOW, 0.5) hrect.move_to(lines[i], LEFT) highlights.add(hrect) highlights[0].shift(1.5 * RIGHT) highlights[2].align_to(lines, RIGHT) hlines = VGroup() for highlight, phrase in zip(highlights, phrases): hlines.add(VGroup( DashedLine(highlight.get_corner(UR), phrase.get_corner(UL), buff=0.1), DashedLine(highlight.get_corner(DR), phrase.get_corner(DL), buff=0.1), )) hlines.set_stroke(YELLOW, 1) for i in range(3): self.play( FadeIn(highlights[i]), *map(ShowCreation, hlines[i]), GrowFromPoint(phrases[i], highlights[i].get_right()) ) self.wait(2) # Embed self.embed() class FamousMathematicians(Scene): im_height = 3.5 def construct(self): # Portraits images = Group( ImageMobject("Newton"), ImageMobject("Euler"), ImageMobject("Gauss"), ImageMobject("Fourier"), ImageMobject("Riemann_cropped"), ImageMobject("Cauchy"), ImageMobject("Noether"), ImageMobject("Ramanujan"), ) names = VGroup( Text("Isaac Newton"), Text("Leonhard Euler"), Text("Carl Friedrich Gauss"), Text("Joseph Fourier"), Text("Bernhard Riemann"), Text("Augustin Cauchy"), Text("Emmy Noether"), Text("Srinivasa Ramanujan"), ) im_groups = Group() for im, name in zip(images, names): im.set_height(self.im_height) name.scale(0.6) name.set_color(GREY_A) name.next_to(im, DOWN) im_groups.add(Group(im, name)) # im_groups.arrange(RIGHT, aligned_edge=UP, buff=LARGE_BUFF) im_groups.arrange_in_grid(2, 4, aligned_edge=UP, buff=LARGE_BUFF) im_groups.set_width(FRAME_WIDTH - 2) im_groups.to_edge(LEFT) dots = OldTex("\\dots", font_size=72).replicate(2) dots[0].next_to(images[-5], RIGHT, MED_LARGE_BUFF) dots[1].next_to(images[-1], RIGHT, MED_LARGE_BUFF) self.play( LaggedStart(*map(FadeIn, (*im_groups, dots)), lag_ratio=0.25), run_time=5 ) self.wait() self.play( im_groups[0].animate.set_height(6).center().to_edge(LEFT), LaggedStart(*( FadeOut(mob, DR) for mob in (*im_groups[1:], dots) ), lag_ratio=0.25), run_time=2, ) self.wait() # Papers (do in editor) class InventingMath(Scene): def construct(self): pass class AmbientHourglass(ShadowScene): inf_light = True def construct(self): frame = self.camera.frame frame.set_z(3) self.init_frame_rotation() self.remove(self.solid, self.shadow) qint_func = bezier([0, 1, -1.25, 1, 0]) def func(u, v): qf = qint_func(v) x = qf * math.cos(u) y = qf * math.sin(u) x = np.sign(x) * abs(x)**0.5 y = np.sign(y) * abs(y)**0.5 return [x, y, 0.5 - v] ep = 1e-6 hourglass = ParametricSurface(func, (0, TAU), (0 + ep, 1 - ep)) hourglass.set_depth(2) hourglass.set_z(3) hourglass.set_color(BLUE_D) hourglass.set_opacity(0.5) hourglass.set_reflectiveness(0.1) hourglass.set_gloss(0.1) hourglass.set_shadow(0.5) hourglass.always_sort_to_camera(self.camera) mesh = SurfaceMesh(hourglass) mesh.set_flat_stroke(False) mesh.set_stroke(BLUE_B, 0.2, 0.5) mesh_shadow = mesh.copy() mesh_shadow.deactivate_depth_test() solid_group = Group(mesh_shadow, hourglass, mesh) shadow = self.shadow = get_shadow(solid_group) shadow[1].always_sort_to_camera(self.camera) self.add(solid_group, shadow) for x in range(30): self.random_toss(solid_group) self.wait() self.begin_ambient_rotation( solid_group, speed=0.5, initial_axis=[1, 0, 1], ) self.wait(35) class QuantifyConvexity(Scene): def construct(self): # Ask question nonconvex = Text("Non-convex") nonconvex.to_edge(UP) nonconvex.set_color(RED) question = Text("Can we quantify this?") question.next_to(nonconvex, DOWN, buff=1.5) question.to_edge(LEFT) arrow = Arrow(question, nonconvex.get_corner(DL)) self.play(Write(nonconvex)) self.wait() self.play( FadeIn(question, 0.5 * DOWN), ShowCreation(arrow), ) self.wait() # Binary choice double_arrow = OldTex("\\leftrightarrow") double_arrow.move_to(nonconvex) convex = Text("Convex") convex.set_color(GREEN) convex.next_to(double_arrow, RIGHT) self.play( nonconvex.animate.next_to(double_arrow, LEFT), Write(double_arrow), FadeIn(convex, shift=0.25 * RIGHT), Uncreate(arrow), FadeOut(question, 0.5 * DOWN), ) self.wait() # Spectrum interval = UnitInterval(width=7) interval.add_numbers() interval.to_corner(UL, buff=LARGE_BUFF) self.play( FadeTransform(double_arrow, interval), convex.animate.scale(0.5).next_to(interval.n2p(1), UP), nonconvex.animate.scale(0.5).next_to(interval.n2p(0), UP), ) self.wait() # Fraction shadow = get_key_result("Solid").slice_by_tex(None, "=") shadow.add(shadow[0].copy()) shadow.remove(shadow[0]) four_shadow = VGroup(OldTex("4 \\cdot"), shadow) four_shadow.arrange(RIGHT, buff=SMALL_BUFF) sa = Text("Surface area") frac = VGroup( four_shadow, Line().match_width(four_shadow).set_stroke(width=2), sa ) frac.arrange(DOWN) frac.set_width(3) frac.to_corner(UR) frac.match_y(interval) self.play(Write(shadow)) self.play(FadeIn(four_shadow[0])) self.wait() self.play(ShowCreation(frac[1])) self.play(FadeIn(sa)) self.wait() # Dot dot = GlowDot() dot.scale(2) dot.move_to(interval.n2p(1)) self.play(FadeIn(dot, RIGHT)) self.wait() self.play(dot.animate.move_to(interval.n2p(0.6)), run_time=2) self.wait() class GoalsOfMath(TeacherStudentsScene): def construct(self): words = Text("The goal of math\nis to answer questions") words.move_to(self.hold_up_spot, DOWN) words.to_edge(RIGHT, buff=2.0) aq = words.get_part_by_text("answer questions") aq.set_color(BLUE) dni = Text( "develop new ideas", t2c={"new ideas": YELLOW}, t2s={"new ideas": ITALIC}, ) dni.move_to(aq, LEFT) self.play( self.teacher.change("raise_right_hand", words), self.change_students(*3 * ["pondering"], look_at=words), Write(words) ) self.wait(2) self.add(aq, self.teacher) self.play( aq.animate.shift(0.5 * DOWN).set_opacity(0.2), Write(dni), self.teacher.change("well", words), self.change_students(*3 * ["thinking"], look_at=words) ) self.wait(3) class InfatuationWithGenerality(TeacherStudentsScene): def construct(self): self.student_says( OldTexText("Why are mathematicians\\\\obsessed with abstractions?"), index=0, added_anims=[ self.students[1].change("tease"), self.students[2].change("pondering"), ] ) self.play( self.teacher.change("well"), ) self.wait(6) class NumberphileFrame(VideoWrapper): animate_boundary = True title = "Bertrand's Paradox (with Numberphile)" title_config = { "font_size": 48 } wait_time = 16 class ByLine(Scene): def construct(self): lines = VGroup( OldTexText("Artwork by\\\\", "Kurt Bruns"), OldTexText("Music by\\\\", "Vince Rubinetti"), OldTexText("Other stuff\\\\", "Grant Sanderson"), ) for line in lines: line[0].set_color(GREY_B) line[1].scale(1.2, about_edge=UP) lines.arrange(DOWN, buff=1.5) self.add(lines) class EndScreen(PatreonEndScreen): pass class ThumbnailBackground(ShadowScene): plane_dims = (32, 20) def construct(self): frame = self.camera.frame frame.reorient(0) cube = self.solid cube.set_shadow(0.5) light = self.light light.next_to(cube, OUT, buff=2) light.shift(2 * LEFT) light.move_to(50 * OUT) gc = self.glow.replicate(10) gc.set_opacity(0.3) gc.clear_updaters() gc.arrange(RIGHT).match_width(cube) gc.move_to(6 * OUT) self.add(gc) outline = self.get_shadow_outline() light_lines = self.get_light_lines(outline) self.add(outline, light_lines) self.randomly_reorient(cube) self.randomly_reorient(cube) self.wait()