from manimlib.imports import * from active_projects.shadows import * # Abstract scenes class Cylinder(Sphere): """ Inherits from sphere so as to be as aligned as possible for transformations """ def func(self, u, v): return np.array([ np.cos(v), np.sin(v), np.cos(u) ]) class UnwrappedCylinder(Cylinder): def func(self, u, v): return np.array([ v - PI, -self.radius, np.cos(u) ]) class ParametricDisc(Sphere): CONFIG = { "u_min": 0, "u_max": 1, "stroke_width": 0, "checkerboard_colors": [BLUE_D], } def func(self, u, v): return np.array([ u * np.cos(v), u * np.sin(v), 0, ]) class SphereCylinderScene(SpecialThreeDScene): CONFIG = { "cap_config": { "stroke_width": 1, "stroke_color": WHITE, "fill_color": BLUE_D, "fill_opacity": 1, } } def get_cylinder(self, **kwargs): config = merge_dicts_recursively(self.sphere_config, kwargs) return Cylinder(**config) def get_cylinder_caps(self): R = self.sphere_config["radius"] caps = VGroup(*[ Circle( radius=R, **self.cap_config, ).shift(R * vect) for vect in [IN, OUT] ]) caps.set_shade_in_3d(True) return caps def get_unwrapped_cylinder(self): return UnwrappedCylinder(**self.sphere_config) def get_xy_plane(self): pass def get_ghost_surface(self, surface): result = surface.copy() result.set_fill(BLUE_E, opacity=0.5) result.set_stroke(WHITE, width=0.5, opacity=0.5) return result def project_point(self, point): radius = self.sphere_config["radius"] result = np.array(point) result[:2] = normalize(result[:2]) * radius return result def project_mobject(self, mobject): return mobject.apply_function(self.project_point) # Scenes for video class AskAboutShadowRelation(SpecialThreeDScene): CONFIG = { "R_color": YELLOW, "space_out_factor": 1.15, } def construct(self): self.show_surface_area() self.show_four_circles() self.ask_why() self.show_all_pieces() def show_surface_area(self): sphere = self.get_sphere() sphere.set_fill(BLUE_E, opacity=0.5) sphere.add_updater( lambda s, dt: s.rotate(0.1 * dt, axis=OUT) ) pieces = sphere.deepcopy() pieces.space_out_submobjects(1.5) pieces.shift(IN) pieces.set_color(GREEN) # radial_line = Line(ORIGIN, sphere.get_right()) # R_label = TexMobject("R") # R_label.set_color(BLUE) # R_label.rotate(90 * DEGREES, RIGHT) # R_label.next_to(radial_line, OUT, SMALL_BUFF) sa_equation = TexMobject( "\\text{Surface area} = 4\\pi R^2", tex_to_color_map={"R": BLUE} ) sa_equation.scale(1.5) sa_equation.to_edge(UP) self.set_camera_orientation( phi=70 * DEGREES, theta=-90 * DEGREES, ) self.add_fixed_in_frame_mobjects(sa_equation) self.play( Write(sphere, stroke_width=1), FadeInFromDown(sa_equation), # ShowCreation(radial_line), # FadeInFrom(R_label, IN), ) # self.play( # Transform( # sphere, pieces, # rate_func=there_and_back_with_pause, # run_time=2 # ) # ) self.play(LaggedStartMap( UpdateFromAlphaFunc, sphere, lambda mob: (mob, lambda m, a: m.set_fill( color=interpolate_color(BLUE_E, YELLOW, a), opacity=interpolate(0.5, 1, a) )), rate_func=there_and_back, lag_ratio=0.1, run_time=4 )) self.play( sphere.scale, 0.75, sphere.shift, 3 * LEFT, sa_equation.shift, 3 * LEFT, ) self.wait(2) self.sphere = sphere self.sa_equation = sa_equation def show_four_circles(self): sphere = self.sphere shadow = Circle( radius=sphere.get_width() / 2, stroke_color=WHITE, stroke_width=1, fill_color=BLUE_E, fill_opacity=1, ) radial_line = Line( shadow.get_center(), shadow.get_right(), color=YELLOW ) R_label = TexMobject("R").set_color(self.R_color) R_label.scale(0.8) R_label.next_to(radial_line, DOWN, SMALL_BUFF) shadow.add(radial_line, R_label) shadow.move_to( self.camera.transform_points_pre_display(sphere, [sphere.get_center()])[0] ) shadows = VGroup(*[shadow.copy() for x in range(4)]) shadows.arrange_in_grid(buff=MED_LARGE_BUFF) shadows.to_edge(RIGHT) area_label = TexMobject( "\\pi R^2", tex_to_color_map={"R": self.R_color} ) area_label.scale(1.2) area_labels = VGroup(*[ area_label.copy().move_to(interpolate( mob.get_center(), mob.get_top(), 0.5 )) for mob in shadows ]) # shadow.move_to(sphere) self.add_fixed_in_frame_mobjects(shadow) self.play(DrawBorderThenFill(shadow)) anims = [] for new_shadow in shadows: old_shadow = shadow.copy() self.add_fixed_in_frame_mobjects(old_shadow) anims.append(Transform( old_shadow, new_shadow, remover=True )) self.remove(shadow) self.play(*anims) self.add_fixed_in_frame_mobjects(shadows, area_labels) self.play(LaggedStartMap(FadeInFromLarge, area_labels)) self.wait() self.shadows = shadows self.shadow_area_labels = area_labels def ask_why(self): pass def show_all_pieces(self): shadows = self.shadows area_labels = self.shadow_area_labels sphere = self.sphere shadow_pieces_group = VGroup() for shadow in shadows: pieces = ParametricSurface( func=lambda u, v: np.array([ u * np.cos(TAU * v), u * np.sin(TAU * v), 0, ]), resolution=(12, 24) ) pieces.replace(shadow) pieces.match_style(sphere) shadow_pieces_group.add(pieces) self.add_fixed_in_frame_mobjects(shadow_pieces_group) self.play( FadeOut(shadows), FadeOut(area_labels), FadeIn(shadow_pieces_group) ) self.show_area_pieces(sphere) self.wait() self.show_area_pieces(*shadow_pieces_group) self.wait(2) self.unshow_area_pieces(sphere, *shadow_pieces_group) self.wait(3) def show_area_pieces(self, *mobjects): for mob in mobjects: mob.generate_target() mob.target.space_out_submobjects(self.space_out_factor) self.play(*map(MoveToTarget, mobjects)) self.play(*[ LaggedStartMap( ApplyMethod, mob, lambda m: (m.set_fill, YELLOW, 1), rate_func=there_and_back, lag_ratio=0.25, run_time=1.5 ) for mob in mobjects ]) def unshow_area_pieces(self, *mobjects): self.play(*[ ApplyMethod( mob.space_out_submobjects, 1.0 / self.space_out_factor ) for mob in mobjects ]) class ButWhy(TeacherStudentsScene): CONFIG = { "student_mode": "raise_right_hand", "question": "But why?", "teacher_mode": "guilty" } def construct(self): for student in self.students: student.change("pondering", self.screen) self.student_says( "But why?", student_index=2, target_mode=self.student_mode, bubble_kwargs={"direction": LEFT}, ) self.play( self.teacher.change, self.teacher_mode, self.students[2] ) self.wait(5) class ButWhyHappy(ButWhy): CONFIG = { "teacher_mode": "happy" } class ButWhySassy(ButWhy): CONFIG = { "teacher_mode": "sassy" } class ButWhyHesitant(ButWhy): CONFIG = { "teacher_mode": "hesitant" } class ButWhyAngry(ButWhy): CONFIG = { "teacher_mode": "angry" } class TryFittingCirclesDirectly(ExternallyAnimatedScene): pass class PreviewTwoMethods(MovingCameraScene): def construct(self): thumbnails = Group( ImageMobject("SphereSurfaceProof1"), ImageMobject("SphereSurfaceProof2"), ) for thumbnail in thumbnails: thumbnail.set_width(FRAME_WIDTH / 2 - 1) thumbnail.add_to_back(SurroundingRectangle( thumbnail, buff=0, stroke_color=WHITE, stroke_width=5 )) thumbnails.arrange(RIGHT, buff=LARGE_BUFF) title = TextMobject("Two proofs") title.scale(2) title.to_edge(UP) frame = self.camera.frame self.add(title) for thumbnail in thumbnails: self.play(FadeInFromDown(thumbnail)) self.wait() for thumbnail in thumbnails: frame.save_state() self.play( frame.replace, thumbnail, run_time=2 ) self.wait() self.play(Restore(frame, run_time=2)) class MapSphereOntoCylinder(SphereCylinderScene): def construct(self): sphere = self.get_sphere() sphere_ghost = self.get_ghost_surface(sphere) sphere_ghost.set_stroke(width=0) axes = self.get_axes() cylinder = self.get_cylinder() cylinder.set_fill(opacity=0.75) radius = cylinder.get_width() / 2 self.add(axes, sphere_ghost, sphere) self.wait() self.begin_ambient_camera_rotation() self.move_camera( **self.default_angled_camera_position, run_time=2, ) self.wait(2) self.play( ReplacementTransform(sphere, cylinder), run_time=3 ) self.wait(3) # Get rid of caps caps = self.get_cylinder_caps() caps[1].set_shade_in_3d(False) label = TextMobject("Label") label.scale(1.5) label.stretch(0.8, 0) label.rotate(90 * DEGREES, RIGHT) label.rotate(90 * DEGREES, OUT) label.shift(np.log(radius + SMALL_BUFF) * RIGHT) label.apply_complex_function(np.exp) label.rotate(90 * DEGREES, IN, about_point=ORIGIN) label.shift(OUT) label.set_background_stroke(width=0) self.play(FadeIn(caps)) self.wait() self.play( caps.space_out_submobjects, 2, caps.fade, 1, remover=True ) self.play(Write(label)) self.wait(2) self.play(FadeOut(label)) # Unwrap unwrapped_cylinder = self.get_unwrapped_cylinder() unwrapped_cylinder.set_fill(opacity=0.75) self.play( ReplacementTransform(cylinder, unwrapped_cylinder), run_time=3 ) self.stop_ambient_camera_rotation() self.move_camera( phi=90 * DEGREES, theta=-90 * DEGREES, ) # Show dimensions stroke_width = 5 top_line = Line( PI * radius * LEFT + radius * OUT, PI * radius * RIGHT + radius * OUT, color=YELLOW, stroke_width=stroke_width, ) side_line = Line( PI * radius * LEFT + radius * OUT, PI * radius * LEFT + radius * IN, color=RED, stroke_width=stroke_width, ) lines = VGroup(top_line, side_line) lines.shift(radius * DOWN) two_pi_R = TexMobject("2\\pi R") two_R = TexMobject("2R") texs = VGroup(two_pi_R, two_R) for tex in texs: tex.scale(1.5) tex.rotate(90 * DEGREES, RIGHT) two_pi_R.next_to(top_line, OUT) two_R.next_to(side_line, RIGHT) self.play( ShowCreation(top_line), FadeInFrom(two_pi_R, IN) ) self.wait() self.play( ShowCreation(side_line), FadeInFrom(two_R, RIGHT) ) self.wait() class CircumferenceOfCylinder(SphereCylinderScene): def construct(self): sphere = self.get_sphere() sphere_ghost = self.get_ghost_surface(sphere) cylinder = self.get_cylinder() cylinder_ghost = self.get_ghost_surface(cylinder) cylinder_ghost.set_stroke(width=0) radius = self.sphere_config["radius"] circle = Circle(radius=radius) circle.set_stroke(YELLOW, 5) circle.shift(radius * OUT) height = Line(radius * IN, radius * OUT) height.shift(radius * LEFT) height.set_stroke(RED, 5) self.set_camera_orientation( phi=70 * DEGREES, theta=-95 * DEGREES, ) self.begin_ambient_camera_rotation(0.01) self.add(sphere_ghost, cylinder_ghost) self.wait() self.play(ShowCreation(circle)) self.wait(2) self.play(ShowCreation(height)) self.wait(5) class UnfoldCircles(Scene): CONFIG = { "circle_style": { "fill_color": GREY_BROWN, "fill_opacity": 0, "stroke_color": GREY_BROWN, "stroke_width": 2, }, "radius": 1.0, "dr": 0.01, } def construct(self): self.show_rectangle_with_formula() self.add_four_circles() def show_rectangle_with_formula(self): TexMobject.CONFIG["background_stroke_width"] = 1 R = self.radius rect = Rectangle(width=TAU * R, height=2 * R) rect.set_fill(BLUE_E, 1) rect.set_stroke(width=0) p0, p1, p2 = [rect.get_corner(v) for v in (DL, UL, UR)] h_line = Line(p0, p1) h_line.set_stroke(RED, 3) w_line = Line(p1, p2) w_line.set_stroke(YELLOW, 3) two_R = TexMobject("2", "R") two_R.next_to(h_line, LEFT) two_pi_R = TexMobject("2", "\\pi", "R") two_pi_R.next_to(w_line, UP) pre_area_label = TexMobject( "2\\cdot", "2", "\\pi", "R", "\\cdot R" ) area_label = TexMobject("4", "\\pi", "R^2") for label in [area_label, pre_area_label]: label.next_to(rect.get_center(), UP, SMALL_BUFF) self.rect_group = VGroup( rect, h_line, w_line, two_R, two_pi_R, area_label ) self.area_label = area_label self.rect = rect self.add(rect, h_line, w_line, two_R, two_pi_R) self.play( TransformFromCopy(two_R[0], pre_area_label[0]), TransformFromCopy(two_R[1], pre_area_label[-1]), TransformFromCopy(two_pi_R, pre_area_label[1:-1]), ) self.wait() self.play( ReplacementTransform(pre_area_label[:2], area_label[:1]), ReplacementTransform(pre_area_label[2], area_label[1]), ReplacementTransform(pre_area_label[3:], area_label[2:]), ) self.wait() self.play( self.rect_group.to_corner, UL, {"buff": MED_SMALL_BUFF} ) def add_four_circles(self): rect_group = self.rect_group radius = self.radius radii_lines = VGroup(*[ Line(radius * UP, ORIGIN).set_stroke(WHITE, 2) for x in range(4) ]) radii_lines.arrange_in_grid(buff=1.3) radii_lines[2:].shift(RIGHT) radii_lines.next_to(rect_group, DOWN, buff=1.3) R_labels = VGroup(*[ TexMobject("R").next_to(line, LEFT, SMALL_BUFF) for line in radii_lines ]) unwrap_factor_tracker = ValueTracker(0) def get_circle(line): return always_redraw( lambda: self.get_unwrapped_circle( radius=radius, dr=self.dr, unwrap_factor=unwrap_factor_tracker.get_value(), center=line.get_top() ) ) circles = VGroup(*[get_circle(line) for line in radii_lines]) circle_copies = circles.copy() for mob in circle_copies: mob.clear_updaters() self.play( LaggedStartMap(Write, circle_copies, lag_ratio=0.7), LaggedStartMap(Write, R_labels), LaggedStartMap(ShowCreation, radii_lines), ) self.remove(circle_copies) self.add(circles, radii_lines, R_labels) self.wait() self.play( radii_lines[2:].shift, 2.9 * RIGHT, R_labels[2:].shift, 2.9 * RIGHT, VGroup( radii_lines[:2], R_labels[:2] ).to_edge, LEFT, {"buff": 1.0} ) self.play( unwrap_factor_tracker.set_value, 1, run_time=2 ) self.wait() # Move triangles triangles = circles.copy() for triangle in triangles: triangle.clear_updaters() border = Polygon(*[ triangle.get_corner(vect) for vect in (DL, UL, DR) ]) border.set_stroke(WHITE, 1) triangle.add(border) triangle.generate_target() rect = self.rect for i, triangle in enumerate(triangles): if i % 2 == 1: triangle.target.rotate(PI) vect = UP if i < 2 else DOWN triangle.target.move_to(rect, vect) self.play(FadeIn(triangles)) self.add(triangles, triangles.copy(), self.area_label) self.play(MoveToTarget(triangles[0])) self.wait() self.play(LaggedStartMap(MoveToTarget, triangles)) self.wait() # def get_unwrapped_circle(self, radius, dr, unwrap_factor=0, center=ORIGIN): radii = np.arange(0, radius + dr, dr) rings = VGroup() for r in radii: r_factor = 1 + 3 * (r / radius) alpha = unwrap_factor**r_factor alpha = np.clip(alpha, 0, 0.9999) angle = interpolate(TAU, 0, alpha) length = TAU * r arc_radius = length / angle ring = Arc( start_angle=-90 * DEGREES, angle=angle, radius=arc_radius, **self.circle_style ) ring.shift(center + (r - arc_radius) * DOWN) # ring = AnnularSector( # inner_radius=r1, # outer_radius=r2, # angle=TAU, # start_angle=-TAU / 4, # **self.circle_style # ) rings.add(ring) return rings class ShowProjection(SphereCylinderScene): CONFIG = { # "default_angled_camera_position": { # "theta": -155 * DEGREES, # } } def construct(self): self.setup_shapes() self.show_many_tiny_rectangles() self.project_all_rectangles() self.focus_on_one() self.label_sides() self.show_length_scaled_up() self.show_height_scaled_down() def setup_shapes(self): self.sphere = self.get_sphere() self.cylinder = self.get_cylinder() self.ghost_sphere = self.get_ghost_surface(self.sphere) self.ghost_sphere.scale(0.99) self.ghost_cylinder = self.get_ghost_surface(self.cylinder) self.ghost_cylinder.set_stroke(width=0) self.add(self.get_axes()) self.set_camera_to_default_position() self.begin_ambient_camera_rotation() def show_many_tiny_rectangles(self): ghost_sphere = self.ghost_sphere pieces = self.sphere.copy() random.shuffle(pieces.submobjects) for piece in pieces: piece.save_state() pieces.space_out_submobjects(2) pieces.fade(1) self.add(ghost_sphere) self.play(LaggedStartMap(Restore, pieces)) self.wait() self.pieces = pieces def project_all_rectangles(self): pieces = self.pieces for piece in pieces: piece.save_state() piece.generate_target() self.project_mobject(piece.target) piece.target.set_fill(opacity=0.5) example_group = self.get_example_group([1, -1, 2]) proj_lines = example_group[1] self.example_group = example_group self.play(*map(ShowCreation, proj_lines)) self.play( LaggedStartMap(MoveToTarget, pieces), ) self.wait() def focus_on_one(self): ghost_cylinder = self.ghost_cylinder pieces = self.pieces example_group = self.example_group original_rect, rect_proj_lines, rect = example_group equation = self.get_equation(rect) lhs, equals, rhs = equation[:3] lhs.save_state() rhs.save_state() self.play( FadeIn(ghost_cylinder), FadeOut(pieces), FadeIn(original_rect), ) self.play(TransformFromCopy(original_rect, rect)) self.wait() self.add_fixed_in_frame_mobjects(lhs, equals, rhs) self.move_fixed_in_frame_mob_to_unfixed_mob(lhs, original_rect) self.move_fixed_in_frame_mob_to_unfixed_mob(rhs, rect) self.play( Restore(lhs), Restore(rhs), FadeInFromDown(equals), ) self.wait(5) self.equation = equation self.example_group = example_group def label_sides(self): sphere = self.sphere equation = self.equation w_brace, h_brace = equation.braces width_label, height_label = equation.labels u_values, v_values = sphere.get_u_values_and_v_values() radius = sphere.radius lat_lines = VGroup(*[ ParametricFunction( lambda t: radius * sphere.func(u, t), t_min=sphere.v_min, t_max=sphere.v_max, ) for u in u_values ]) lon_lines = VGroup(*[ ParametricFunction( lambda t: radius * sphere.func(t, v), t_min=sphere.u_min, t_max=sphere.u_max, ) for v in v_values ]) for lines in lat_lines, lon_lines: for line in lines: line.add(DashedVMobject(line, spacing=-1)) line.set_points([]) line.set_stroke(width=2) lines.set_shade_in_3d(True) lat_lines.set_color(RED) lon_lines.set_color(PINK) self.play( *map(ShowCreationThenDestruction, lat_lines), run_time=2 ) self.add_fixed_in_frame_mobjects(w_brace, width_label) self.play( GrowFromCenter(w_brace), Write(width_label), ) self.wait() self.play( *map(ShowCreationThenDestruction, lon_lines), run_time=2 ) self.add_fixed_in_frame_mobjects(h_brace, height_label) self.play( GrowFromCenter(h_brace), Write(height_label), ) self.wait(2) def show_length_scaled_up(self): ghost_sphere = self.ghost_sphere example_group = self.example_group equation = self.equation equation.save_state() new_example_groups = [ self.get_example_group([1, -1, z]) for z in [6, 0.25] ] r1, lines, r2 = example_group self.stop_ambient_camera_rotation() self.move_camera( phi=65 * DEGREES, theta=-80 * DEGREES, added_anims=[ ghost_sphere.set_stroke, {"opacity": 0.1}, lines.set_stroke, {"width": 3}, ] ) for eg in new_example_groups: eg[1].set_stroke(width=3) self.show_length_stretch_of_rect(example_group) all_example_groups = [example_group, *new_example_groups] for eg1, eg2 in zip(all_example_groups, all_example_groups[1:]): if eg1 is new_example_groups[0]: self.move_camera( phi=70 * DEGREES, theta=-110 * DEGREES ) self.remove(eg1) self.play( ReplacementTransform(eg1.deepcopy(), eg2), Transform( equation, self.get_equation(eg2[2]) ) ) if eg1 is example_group: self.move_camera( phi=0, theta=-90 * DEGREES, ) self.show_length_stretch_of_rect(eg2) self.play( ReplacementTransform(all_example_groups[-1], example_group), Restore(equation) ) def show_length_stretch_of_rect(self, example_group): s_rect, proj_lines, c_rect = example_group rects = VGroup(s_rect, c_rect) line1, line2 = lines = VGroup(*[ Line(m.get_anchors()[1], m.get_anchors()[2]) for m in rects ]) lines.set_stroke(YELLOW, 5) lines.set_shade_in_3d(True) proj_lines_to_fade = VGroup( proj_lines[0], proj_lines[3], ) self.play( FadeIn(lines[0]), FadeOut(rects), FadeOut(proj_lines_to_fade) ) for x in range(3): anims = [] if lines[1] in self.mobjects: anims.append(FadeOut(lines[1])) self.play( TransformFromCopy(lines[0], lines[1]), *anims ) self.play( FadeOut(lines), FadeIn(rects), FadeIn(proj_lines_to_fade), ) self.remove(rects, proj_lines_to_fade) self.add(example_group) def show_height_scaled_down(self): ghost_sphere = self.ghost_sphere ghost_cylinder = self.ghost_cylinder example_group = self.example_group equation = self.equation r1, lines, r2 = example_group to_fade = VGroup(*[ mob for mob in it.chain(ghost_sphere, ghost_cylinder) if np.dot(mob.get_center(), [1, 1, 0]) < 0 ]) to_fade.save_state() new_example_groups = [ self.get_example_group([1, -1, z]) for z in [6, 0.25] ] for eg in new_example_groups: eg[::2].set_stroke(YELLOW, 2) eg.set_stroke(width=1) all_example_groups = VGroup(example_group, *new_example_groups) self.play( to_fade.shift, IN, to_fade.fade, 1, remover=True ) self.move_camera( phi=85 * DEGREES, theta=-135 * DEGREES, added_anims=[ lines.set_stroke, {"width": 1}, r1.set_stroke, YELLOW, 2, 1, r2.set_stroke, YELLOW, 2, 1, ] ) self.show_shadow(example_group) for eg1, eg2 in zip(all_example_groups, all_example_groups[1:]): self.remove(*eg1.get_family()) self.play( ReplacementTransform(eg1.deepcopy(), eg2), Transform( equation, self.get_equation(eg2[2]) ) ) self.show_shadow(eg2) self.move_camera( phi=70 * DEGREES, theta=-115 * DEGREES, added_anims=[ ReplacementTransform( all_example_groups[-1], example_group, ), Restore(equation), ] ) self.begin_ambient_camera_rotation() self.play(Restore(to_fade)) self.wait(5) def show_shadow(self, example_group): s_rect, lines, c_rect = example_group for x in range(3): self.play(TransformFromCopy(s_rect, c_rect)) # def get_projection_lines(self, piece): result = VGroup() radius = self.sphere_config["radius"] for corner in piece.get_anchors()[:-1]: start = np.array(corner) end = np.array(corner) start[:2] = np.zeros(2) end[:2] = (radius + 0.03) * normalize(end[:2]) kwargs = { "color": YELLOW, "stroke_width": 0.5, } result.add(VGroup(*[ Line(p1, p2, **kwargs) for p1, p2 in [(start, corner), (corner, end)] ])) result.set_shade_in_3d(True) return result def get_equation(self, rect): length = get_norm(rect.get_anchors()[1] - rect.get_anchors()[0]) height = get_norm(rect.get_anchors()[2] - rect.get_anchors()[1]) lhs = Rectangle(width=length, height=height) rhs = Rectangle(width=height, height=length) eq_rects = VGroup(lhs, rhs) eq_rects.set_stroke(width=0) eq_rects.set_fill(YELLOW, 1) eq_rects.scale(2) equals = TexMobject("=") equation = VGroup(lhs, equals, rhs) equation.arrange(RIGHT) equation.to_corner(UR) brace = Brace(Line(ORIGIN, 0.5 * RIGHT), DOWN) w_brace = brace.copy().match_width(lhs, stretch=True) h_brace = brace.copy().rotate(-90 * DEGREES) h_brace.match_height(lhs, stretch=True) w_brace.next_to(lhs, DOWN, buff=SMALL_BUFF) h_brace.next_to(lhs, LEFT, buff=SMALL_BUFF) braces = VGroup(w_brace, h_brace) width_label = TextMobject("Width") height_label = TextMobject("Height") labels = VGroup(width_label, height_label) labels.scale(0.75) width_label.next_to(w_brace, DOWN, SMALL_BUFF) height_label.next_to(h_brace, LEFT, SMALL_BUFF) equation.braces = braces equation.labels = labels equation.add(braces, labels) return equation def move_fixed_in_frame_mob_to_unfixed_mob(self, m1, m2): phi = self.camera.phi_tracker.get_value() theta = self.camera.theta_tracker.get_value() target = m2.get_center() target = rotate_vector(target, -theta - 90 * DEGREES, OUT) target = rotate_vector(target, -phi, RIGHT) m1.move_to(target) m1.scale(0.1) m1.shift(SMALL_BUFF * UR) return m1 def get_example_group(self, vect): pieces = self.pieces rect = pieces[np.argmax([ np.dot(r.saved_state.get_center(), vect) for r in pieces ])].saved_state.copy() rect_proj_lines = self.get_projection_lines(rect) rect.set_fill(YELLOW, 1) original_rect = rect.copy() self.project_mobject(rect) rect.shift( 0.001 * np.array([*rect.get_center()[:2], 0]) ) result = VGroup(original_rect, rect_proj_lines, rect) result.set_shade_in_3d(True) return result class SlantedShadowSquishing(ShowShadows): CONFIG = { "num_reorientations": 4, "random_seed": 2, } def setup(self): ShowShadows.setup(self) self.surface_area_label.fade(1) self.shadow_area_label.fade(1) self.shadow_area_decimal.fade(1) def construct(self): # Show creation self.set_camera_orientation( phi=70 * DEGREES, theta=-150 * DEGREES, ) self.begin_ambient_camera_rotation(0.01) square = self.obj3d.deepcopy() square.clear_updaters() shadow = always_redraw(lambda: get_shadow(square)) # Reorient self.add(square, shadow) for n in range(self.num_reorientations): angle = 40 * DEGREES self.play( Rotate(square, angle, axis=RIGHT), run_time=2 ) def get_object(self): square = Square() square.set_shade_in_3d(True) square.set_height(2) square.set_stroke(WHITE, 0.5) square.set_fill(BLUE_C, 1) return square class JustifyLengthStretch(ShowProjection): CONFIG = { "R_color": RED, "d_color": WHITE, "d_ambiguity_iterations": 4, } def construct(self): self.setup_shapes() self.add_ghosts() self.add_example_group() self.cut_cross_section() self.label_R() self.label_d() self.show_similar_triangles() def add_ghosts(self): self.add(self.ghost_sphere, self.ghost_cylinder) def add_example_group(self): self.pieces = self.sphere for piece in self.pieces: piece.save_state() self.example_group = self.get_example_group([1, 0.1, 1.5]) self.add(self.example_group) self.set_camera_orientation(theta=-45 * DEGREES) def cut_cross_section(self): sphere = self.ghost_sphere cylinder = self.ghost_cylinder to_fade = VGroup(*[ mob for mob in it.chain(sphere, cylinder) if np.dot(mob.get_center(), DOWN) > 0 ]) self.lost_hemisphere = to_fade to_fade.save_state() circle = Circle( stroke_width=2, stroke_color=PINK, radius=self.sphere.radius ) circle.rotate(90 * DEGREES, RIGHT) self.circle = circle self.example_group.set_stroke(YELLOW, 1) self.stop_ambient_camera_rotation() self.play( Rotate( to_fade, -PI, axis=OUT, about_point=sphere.get_left(), run_time=2 ), VFadeOut(to_fade, run_time=2), FadeIn(circle), ) # self.move_camera( # phi=80 * DEGREES, # theta=-85 * DEGREES, # ) def label_R(self): circle = self.circle R_line = Line(ORIGIN, circle.get_right()) R_line.set_color(self.R_color) R_label = TexMobject("R") R_label.next_to(R_line, IN) self.add_fixed_orientation_mobjects(R_label) self.play( ShowCreation(R_line), FadeInFrom(R_label, IN), ) self.wait() self.R_line = R_line self.R_label = R_label def label_d(self): example_group = self.example_group s_rect = example_group[0] d_line = self.get_d_line( s_rect.get_corner(IN + RIGHT + DOWN) ) alt_d_line = self.get_d_line( s_rect.get_corner(OUT + LEFT + DOWN) ) d_label = TexMobject("d") d_label.next_to(d_line, IN) self.add_fixed_orientation_mobjects(d_label) self.play( ShowCreation(d_line), FadeInFrom(d_label, IN), ) self.wait() for x in range(self.d_ambiguity_iterations): to_fade_out = [d_line, alt_d_line][x % 2] to_fade_in = [d_line, alt_d_line][(x + 1) % 2] self.play( FadeIn(to_fade_in), FadeOut(to_fade_out), d_label.next_to, to_fade_in, IN, ) self.d_line = d_line self.d_label = d_label def show_similar_triangles(self): d_line = self.d_line d_label = self.d_label R_line = self.R_line R_label = self.R_label example_group = self.example_group s_rect, proj_lines, c_rect = example_group p1 = s_rect.get_anchors()[1] p2 = s_rect.get_anchors()[2] p0 = np.array(p1) p0[:2] = np.zeros(2) triangle = Polygon(p0, p1, p2) triangle.set_stroke(width=0) triangle.set_fill(GREEN, opacity=1) base = Line(p1, p2) base.set_stroke(PINK, 3) big_triangle = Polygon( p0, self.project_point(p1), self.project_point(p2) ) big_triangle.set_stroke(width=0) big_triangle.set_fill(RED, opacity=1) equation = VGroup( triangle.copy().rotate(-3 * DEGREES), TexMobject("\\sim"), big_triangle.copy().rotate(-3 * DEGREES) ) equation.arrange(RIGHT, buff=SMALL_BUFF) equation.to_corner(UL) eq_d = TexMobject("d").next_to(equation[0], DOWN, SMALL_BUFF) eq_R = TexMobject("R").next_to(equation[2], DOWN, SMALL_BUFF) VGroup(equation, eq_d, eq_R).rotate(20 * DEGREES, axis=RIGHT) for mob in [triangle, big_triangle]: mob.set_shade_in_3d(True) mob.set_fill(opacity=0.8) self.move_camera( phi=40 * DEGREES, theta=-85 * DEGREES, added_anims=[ d_label.next_to, d_line, DOWN, SMALL_BUFF, d_line.set_stroke, {"width": 1}, FadeOut(proj_lines[0]), FadeOut(proj_lines[3]), FadeOut(R_label) ], run_time=2, ) point = VectorizedPoint(p0) point.set_shade_in_3d(True) self.play( ReplacementTransform(point, triangle), Animation(self.camera.phi_tracker) ) self.add_fixed_in_frame_mobjects(equation, eq_d, eq_R) self.play( FadeInFrom(equation[0], 7 * RIGHT + 2.5 * DOWN), FadeIn(equation[1:]), FadeInFromDown(eq_d), FadeInFromDown(eq_R), Animation(self.camera.phi_tracker) ) self.wait() for x in range(2): self.play(ShowCreationThenDestruction(base)) self.wait() d_line_copy = d_line.copy().set_stroke(WHITE, 3) self.play(ShowCreation(d_line_copy)) self.play(FadeOut(d_line_copy)) self.wait(2) R_label.next_to(R_line, DOWN, SMALL_BUFF) R_label.shift(0.25 * IN) self.play( ReplacementTransform(triangle, big_triangle), FadeIn(R_label), Animation(self.camera.phi_tracker) ) self.wait(3) self.move_camera( phi=70 * DEGREES, theta=-70 * DEGREES, added_anims=[ big_triangle.set_fill, {"opacity": 0.25}, d_label.next_to, d_line, IN, {"buff": 0.3}, ] ) self.begin_ambient_camera_rotation() lost_hemisphere = self.lost_hemisphere lost_hemisphere.restore() left_point = self.sphere.get_left() lost_hemisphere.rotate(-PI, axis=OUT, about_point=left_point) self.play( Rotate( lost_hemisphere, PI, axis=OUT, about_point=left_point, run_time=2, ), VFadeIn(lost_hemisphere), FadeOut(self.circle), R_line.set_color, self.R_color, ) self.wait(10) # def get_d_line(self, sphere_point): end = sphere_point start = np.array(end) start[:2] = np.zeros(2) d_line = Line(start, end) d_line.set_color(self.d_color) return d_line class JustifyLengthStretchHigherRes(JustifyLengthStretch): CONFIG = { "sphere_config": { "resolution": (2 * 24, 2 * 48) }, } class JustifyLengthStretchHighestRes(JustifyLengthStretch): CONFIG = { "sphere_config": { "resolution": (4 * 24, 4 * 48) }, } class ProTipNameThings(Scene): CONFIG = { "tip_descriptor": "(Deceptively simple) problem solving tip:", "tip": "Start with names", } def construct(self): words = TextMobject( self.tip_descriptor, self.tip, ) words[1].set_color(YELLOW) words.to_edge(UP) self.play(FadeIn(words[0])) self.wait() self.play(FadeInFromDown(words[1])) self.wait() class WidthScaleLabel(Scene): def construct(self): text = TexMobject( "\\text{Width scale factor} =", "\\frac{R}{d}" ) self.play(Write(text)) self.wait() class HeightSquishLabel(Scene): def construct(self): text = TexMobject( "\\text{Height squish factor} =", "\\frac{R}{d}" ) self.play(Write(text)) self.wait() class TinierAndTinerRectangles(SphereCylinderScene): CONFIG = { "n_iterations": 5, } def construct(self): spheres = [ self.get_sphere( resolution=(12 * (2**n), 24 * (2**n)), stroke_width=0, ) for n in range(self.n_iterations) ] self.set_camera_to_default_position() self.add(self.get_axes()) self.begin_ambient_camera_rotation() self.add(spheres[0]) for s1, s2 in zip(spheres, spheres[1:]): self.wait() random.shuffle(s2.submobjects) for piece in s2: piece.save_state() s2.space_out_submobjects(1.2) s2.fade(1) for piece in s1: piece.add(VectorizedPoint(piece.get_center() / 2)) self.play( LaggedStartMap(Restore, s2) ) self.remove(s1) self.wait(5) class LimitViewToCrossSection(JustifyLengthStretch): CONFIG = { "d_ambiguity_iterations": 0, } def construct(self): self.setup_shapes() self.add_ghosts() self.add_example_group() self.cut_cross_section() self.label_R() self.label_d() self.move_camera( phi=90 * DEGREES, theta=-90 * DEGREES, ) self.play( FadeOut(self.ghost_sphere), FadeOut(self.ghost_cylinder), ) class JustifyHeightSquish(MovingCameraScene): CONFIG = { # As measured from previous scene "top_phi": 0.5242654422649652, "low_phi": 0.655081802831207, "radius": 2, "R_color": RED, "d_color": WHITE, "little_triangle_color": BLUE, "big_triangle_color": GREY_BROWN, "alpha_color": WHITE, "beta_color": WHITE, } def construct(self): self.recreate_cross_section() self.show_little_triangle() self.show_tangent_to_radius() self.tweak_parameter() self.label_angles() def recreate_cross_section(self): axes = Axes( number_line_config={ "unit_size": 2, } ) circle = Circle( radius=self.radius, stroke_color=PINK, stroke_width=2, ) R_line = self.get_R_line(90 * DEGREES) R_line.set_color(self.R_color) R_label = TexMobject("R") R_label.next_to(R_line, DOWN, SMALL_BUFF) d_lines = VGroup(*[ self.get_d_line(phi) for phi in [self.low_phi, self.top_phi] ]) d_line = d_lines[0] d_line.set_color(self.d_color) d_label = TexMobject("d") d_label.next_to(d_line, DOWN, SMALL_BUFF) proj_lines = VGroup(*[ self.get_R_line(phi) for phi in [self.top_phi, self.low_phi] ]) proj_lines.set_stroke(YELLOW, 1) s_rect_line, c_rect_line = [ Line( *[l.get_end() for l in lines], stroke_color=YELLOW, stroke_width=2, ) for lines in [d_lines, proj_lines] ] mobjects = [ axes, circle, R_line, d_line, R_label, d_label, proj_lines, s_rect_line, c_rect_line, ] self.add(*mobjects) self.set_variables_as_attrs(*mobjects) def show_little_triangle(self): phi = self.low_phi d_phi = abs(self.low_phi - self.top_phi) tri_group = self.get_double_triangle_group(phi, d_phi) lil_tri, big_tri = tri_group frame = self.camera_frame frame.save_state() scale_factor = 0.1 sw_sf = 0.2 # stroke_width scale factor d_sf = 0.3 # d_label scale factor hyp = lil_tri.hyp leg = lil_tri.leg2 leg.rotate(PI) VGroup(hyp, leg).set_stroke(YELLOW, 1) hyp_word = TextMobject("Rectangle height $\\rightarrow$") leg_word = TextMobject("$\\leftarrow$ Projection") words = VGroup(hyp_word, leg_word) words.set_height(0.4 * lil_tri.get_height()) words.set_background_stroke(width=0) hyp_word.next_to(hyp.get_center(), LEFT, buff=0.05) leg_word.next_to(leg, RIGHT, buff=0.02) stroke_width_changers = VGroup() for mob in self.mobjects: if mob in [self.d_label, frame]: continue mob.generate_target() mob.save_state() mob.target.set_stroke( width=sw_sf * mob.get_stroke_width() ) stroke_width_changers.add(mob) self.play( frame.scale, scale_factor, frame.move_to, lil_tri, self.d_label.scale, d_sf, {"about_edge": UP}, *map(MoveToTarget, stroke_width_changers) ) self.play(DrawBorderThenFill(lil_tri, stroke_width=0.5)) self.wait() self.play( ShowCreation(hyp), LaggedStartMap( DrawBorderThenFill, hyp_word, stroke_width=0.5, run_time=1, ), ) self.wait() self.play(TransformFromCopy(hyp, leg)) self.play(TransformFromCopy( hyp_word, leg_word, path_arc=-PI / 2, )) self.wait() self.play( frame.restore, self.d_label.scale, 1 / d_sf, {"about_edge": UP}, *map(Restore, stroke_width_changers), run_time=3 ) self.set_variables_as_attrs( hyp_word, leg_word, tri_group ) def show_tangent_to_radius(self): tri_group = self.tri_group lil_tri, big_tri = tri_group lil_hyp = lil_tri.hyp phi = self.low_phi circle_point = self.get_circle_point(phi) tangent = lil_hyp.copy() tangent.set_stroke(WHITE, 2) tangent.scale(5 / tangent.get_length()) tangent.move_to(circle_point) R_line = self.R_line R_label = self.R_label d_label = self.d_label elbow = Elbow(angle=(-phi - PI / 2), width=0.15) elbow.shift(circle_point) elbow.set_stroke(WHITE, 1) self.tangent_elbow = elbow self.play(GrowFromPoint(tangent, circle_point)) self.wait() self.play( Rotate( R_line, 90 * DEGREES - phi, about_point=ORIGIN, ), R_label.next_to, 0.5 * circle_point, DR, {"buff": 0}, d_label.shift, SMALL_BUFF * UL, ) self.play(ShowCreation(elbow)) self.wait() self.add(big_tri, d_label, R_line, elbow) d_label.set_background_stroke(width=0) self.play(DrawBorderThenFill(big_tri)) self.wait() self.set_variables_as_attrs(tangent, elbow) def tweak_parameter(self): tri_group = self.tri_group lil_tri = tri_group[0] d_label = self.d_label d_line = self.d_line R_label = self.R_label R_line = self.R_line frame = self.camera_frame to_fade = VGroup( self.proj_lines, self.s_rect_line, self.c_rect_line, self.hyp_word, self.leg_word, lil_tri.hyp, lil_tri.leg2, ) rad_tangent = VGroup( R_line, self.tangent, self.elbow, ) phi_tracker = ValueTracker(self.low_phi) self.play( frame.scale, 0.6, frame.shift, UR, R_label.scale, 0.6, {"about_edge": UL}, d_label.scale, 0.6, {"about_point": d_label.get_top() + SMALL_BUFF * DOWN}, *map(FadeOut, to_fade), ) curr_phi = self.low_phi d_phi = abs(self.top_phi - self.low_phi) alt_phis = [ 80 * DEGREES, 20 * DEGREES, 50 * DEGREES, curr_phi ] for new_phi in alt_phis: self.add(tri_group, d_label) self.play( phi_tracker.set_value, new_phi, UpdateFromFunc( tri_group, lambda tg: tg.become( self.get_double_triangle_group( phi_tracker.get_value(), d_phi ) ) ), Rotate( rad_tangent, -(new_phi - curr_phi), about_point=ORIGIN, ), MaintainPositionRelativeTo(R_label, R_line), UpdateFromFunc( d_line, lambda dl: dl.become( self.get_d_line(phi_tracker.get_value()) ), ), MaintainPositionRelativeTo(d_label, d_line), run_time=2 ) self.wait() curr_phi = new_phi for tri in tri_group: self.play(Indicate(tri)) self.wait() self.play(*map(FadeIn, to_fade)) self.remove(phi_tracker) def label_angles(self): # Getting pretty hacky here... tri_group = self.tri_group lil_tri, big_tri = tri_group d_label = self.d_label R_label = self.R_label frame = self.camera_frame alpha = self.low_phi beta = 90 * DEGREES - alpha circle_point = self.get_circle_point(alpha) alpha_arc = Arc( start_angle=90 * DEGREES, angle=-alpha, radius=0.2, stroke_width=2, ) beta_arc = Arc( start_angle=PI, angle=beta, radius=0.2, stroke_width=2, ) beta_arc.shift(circle_point) alpha_label = TexMobject("\\alpha") alpha_label.scale(0.5) alpha_label.set_color(self.alpha_color) alpha_label.next_to(alpha_arc, UP, buff=SMALL_BUFF) alpha_label.shift(0.05 * DR) beta_label = TexMobject("\\beta") beta_label.scale(0.5) beta_label.set_color(self.beta_color) beta_label.next_to(beta_arc, LEFT, buff=SMALL_BUFF) beta_label.shift(0.07 * DR) VGroup(alpha_label, beta_label).set_background_stroke(width=0) elbow = Elbow(width=0.15, angle=-90 * DEGREES) elbow.shift(big_tri.get_corner(UL)) elbow.set_stroke(width=2) equation = TexMobject( "\\alpha", "+", "\\beta", "+", "90^\\circ", "=", "180^\\circ" ) equation.scale(0.6) equation.next_to(frame.get_corner(UR), DL) movers = VGroup( alpha_label.deepcopy(), beta_label.deepcopy(), elbow.copy() ) indices = [0, 2, 4] for mover, index in zip(movers, indices): mover.target = VGroup(equation[index]) # Show equation self.play( FadeOut(d_label), FadeOut(R_label), ShowCreation(alpha_arc), ShowCreation(beta_arc), ) self.wait() self.play(FadeInFrom(alpha_label, UP)) self.wait() self.play(FadeInFrom(beta_label, LEFT)) self.wait() self.play(ShowCreation(elbow)) self.wait() self.play( LaggedStartMap(MoveToTarget, movers), LaggedStartMap(FadeInFromDown, equation[1:4:2]) ) self.wait() self.play(FadeInFrom(equation[-2:], LEFT)) self.remove(equation, movers) self.add(equation) self.wait() # Zoom in self.remove(self.tangent_elbow) stroke_width_changers = VGroup(*[ mob for mob in self.mobjects if mob not in [ beta_arc, beta_label, frame, equation, ] ]) for mob in stroke_width_changers: mob.generate_target() mob.save_state() mob.target.set_stroke( width=0.3 * mob.get_stroke_width() ) equation.set_background_stroke(width=0) scaled_arcs = VGroup(beta_arc, self.tangent_elbow) beta_label.set_background_stroke(color=BLACK, width=0.3) self.play( ApplyMethod( VGroup(frame, equation).scale, 0.15, {"about_point": circle_point + 0.1 * LEFT}, ), ApplyMethod( beta_label.scale, 0.3, {"about_point": circle_point}, ), scaled_arcs.set_stroke, {"width": 0.3}, scaled_arcs.scale, 0.3, {"about_point": circle_point}, *map(MoveToTarget, stroke_width_changers) ) # Show small triangle angles TexMobject.CONFIG["background_stroke_width"] = 0 words = VGroup(self.hyp_word, self.leg_word) alpha_arc1 = Arc( start_angle=90 * DEGREES + beta, angle=0.95 * alpha, radius=0.3 * 0.2, stroke_width=beta_arc.get_stroke_width(), ).shift(circle_point) alpha_arc2 = Arc( start_angle=0, angle=-0.95 * alpha, radius=0.3 * 0.2, stroke_width=beta_arc.get_stroke_width(), ).shift(lil_tri.hyp.get_end()) beta_arc1 = Arc( start_angle=90 * DEGREES, angle=beta, radius=0.3 * 0.2, stroke_width=beta_arc.get_stroke_width(), ).shift(circle_point) deg90 = TexMobject("90^\\circ") deg90.set_height(0.8 * beta_label.get_height()) deg90.next_to(self.tangent_elbow, DOWN, buff=0.025) # deg90.set_background_stroke(width=0) q_mark = TexMobject("?") q_mark.set_height(0.5 * beta_label.get_height()) q_mark.next_to(alpha_arc1, LEFT, buff=0.025) q_mark.shift(0.01 * UP) alpha_label1 = TexMobject("\\alpha") alpha_label1.set_height(0.7 * q_mark.get_height()) alpha_label1.move_to(q_mark) alpha_label2 = alpha_label1.copy() alpha_label2.next_to( alpha_arc2, RIGHT, buff=0.01 ) alpha_label2.set_background_stroke(color=BLACK, width=0.3) beta_label1 = beta_label.copy() beta_label1.scale(0.7) beta_label1.set_background_stroke(color=BLACK, width=0.3) beta_label1.next_to( beta_arc1, UP, buff=0.01 ) beta_label1.shift(0.01 * LEFT) self.play(FadeOut(words)) self.play(FadeInFrom(deg90, 0.1 * UP)) self.wait(0.25) self.play(WiggleOutThenIn(beta_label)) self.wait(0.25) self.play( ShowCreation(alpha_arc1), FadeInFrom(q_mark, 0.1 * RIGHT) ) self.wait() self.play(ShowPassingFlash( self.tangent.copy().scale(0.1).set_stroke(PINK, 0.5) )) self.wait() self.play(ReplacementTransform(q_mark, alpha_label1)) self.play(ShowCreationThenFadeAround( equation, surrounding_rectangle_config={ "buff": 0.015, "stroke_width": 0.5, }, )) self.wait() self.play( ShowCreation(alpha_arc2), FadeIn(alpha_label2), ) self.play( ShowCreation(beta_arc1), FadeIn(beta_label1), ) self.wait() # def get_double_triangle_group(self, phi, d_phi): p0 = self.get_circle_point(phi) p1 = self.get_circle_point(phi - d_phi) p2 = np.array(p1) p2[0] = p0[0] little_triangle = Polygon( p0, p1, p2, stroke_width=0, fill_color=self.little_triangle_color, fill_opacity=1, ) big_triangle = Polygon( p0, ORIGIN, p0 - p0[0] * RIGHT, stroke_width=0, fill_color=self.big_triangle_color, fill_opacity=1 ) result = VGroup(little_triangle, big_triangle) for tri in result: p0, p1, p2 = tri.get_anchors()[:3] tri.hyp = Line(p0, p1) tri.leg1 = Line(p1, p2) tri.leg2 = Line(p2, p0) tri.side_lines = VGroup( tri.hyp, tri.leg1, tri.leg2 ) tri.side_lines.set_stroke(WHITE, 1) result.set_stroke(width=0) return result def get_R_line(self, phi): y = self.radius * np.cos(phi) x = self.radius return Line(ORIGIN, x * RIGHT).shift(y * UP) def get_d_line(self, phi): end = self.get_circle_point(phi) start = np.array(end) start[0] = 0 return Line(start, end) def get_circle_point(self, phi): return rotate_vector(self.radius * UP, -phi) class ProTipTangentRadii(ProTipNameThings): CONFIG = { "tip_descriptor": "Pro tip:", "tip": "A circle's tangent is perpendicular to its radius", } class ProTipTweakParameters(ProTipNameThings): CONFIG = { "tip_descriptor": "Pro tip:", "tip": "Tweak parameters $\\rightarrow$ make", } class DrawSquareThenFade(Scene): def construct(self): square = Square(color=YELLOW, stroke_width=5) self.play(ShowCreation(square)) self.play(FadeOut(square)) class WhyAreWeDoingThis(TeacherStudentsScene): def construct(self): self.student_says( "Hang on, what \\\\ are we doing?", student_index=2, bubble_kwargs={"direction": LEFT}, target_mode="hesitant" ) self.change_student_modes( "maybe", "pondering", "hesitant", added_anims=[self.teacher.change, "tease"] ) self.wait(3) self.play( RemovePiCreatureBubble(self.students[2]), self.teacher.change, "raise_right_hand", self.change_student_modes(*2 * ["pondering"]) ) self.look_at(self.screen) self.wait(2) class SameEffectAsRotating(Scene): CONFIG = { "rectangle_config": { "height": 2, "width": 1, "stroke_width": 0, "fill_color": YELLOW, "fill_opacity": 1, "background_stroke_width": 2, "background_stroke_color": BLACK, }, "x_stretch": 2, "y_stretch": 0.5, } def construct(self): rect1 = Rectangle(**self.rectangle_config) rect2 = rect1.copy() rect2.stretch(self.x_stretch, 0) rect2.stretch(self.y_stretch, 1) rotated_rect1 = rect1.copy() rotated_rect1.rotate(-90 * DEGREES) arrow = Arrow(ORIGIN, RIGHT, buff=0, color=WHITE) group = VGroup(rect1, arrow, rect2) group.arrange(RIGHT) group.center() moving_rect = rect1.copy() low_brace = always_redraw( lambda: Brace( moving_rect, DOWN, buff=SMALL_BUFF, min_num_quads=2, ) ) right_brace = always_redraw( lambda: Brace( moving_rect, RIGHT, buff=SMALL_BUFF, min_num_quads=2, ) ) times_R_over_d = TexMobject("\\times \\frac{R}{d}") times_d_over_R = TexMobject("\\times \\frac{d}{R}") times_R_over_d.add_updater( lambda m: m.next_to(low_brace, DOWN, SMALL_BUFF) ) times_d_over_R.add_updater( lambda m: m.next_to(right_brace, RIGHT, SMALL_BUFF) ) self.add(rect1, arrow) self.play(moving_rect.move_to, rect2) self.add(low_brace) self.play( moving_rect.match_width, rect2, {"stretch": True}, FadeIn(times_R_over_d), ) self.add(right_brace) self.play( moving_rect.match_height, rect2, {"stretch": True}, FadeIn(times_d_over_R), ) self.wait() rotated_rect1.move_to(moving_rect) self.play(TransformFromCopy( rect1, rotated_rect1, path_arc=-90 * DEGREES, run_time=2 )) class NotSameEffectAsRotating(SameEffectAsRotating): CONFIG = { "rectangle_config": { "width": 1.5, "height": 1.5, } } class ShowParameterization(Scene): def construct(self): u_color = PINK v_color = GREEN tex_kwargs = { "tex_to_color_map": { "u": u_color, "v": v_color, } } vector = Matrix( [ ["\\text{cos}(u)\\text{sin}(v)"], ["\\text{sin}(u)\\text{sin}(v)"], ["\\text{cos}(v)"] ], element_to_mobject_config=tex_kwargs, element_alignment_corner=DOWN, ) vector.to_edge(UP) ranges = VGroup( TexMobject("0 \\le u \\le 2\\pi", **tex_kwargs), TexMobject("0 \\le v \\le \\pi", **tex_kwargs), TextMobject( "Sample $u$ and $v$ with \\\\ the same density", tex_to_color_map={ "$u$": u_color, "$v$": v_color, } ) ) ranges.arrange(DOWN) ranges.next_to(vector, DOWN) self.add(vector) self.add(ranges) class RdLabels(Scene): def construct(self): rect = Rectangle(height=1, width=0.5) cR = TexMobject("cR") cR.next_to(rect, LEFT, SMALL_BUFF) cd = TexMobject("cd") cd.next_to(rect, DOWN, SMALL_BUFF) labels = VGroup(cd, cR) for label in labels: label[1].set_color(BLUE) self.play(FadeInFromDown(label)) class RotateAllPiecesWithExpansion(ShowProjection): CONFIG = { "sphere_config": { "radius": 1.5, }, "with_expansion": True } def construct(self): self.setup_shapes() self.rotate_all_pieces() def rotate_all_pieces(self): sphere = self.sphere cylinder = self.cylinder ghost_sphere = self.ghost_sphere ghost_sphere.scale(0.99) # Shuffle sphere and cylinder same way random.seed(0) random.shuffle(sphere.submobjects) random.seed(0) random.shuffle(cylinder.submobjects) sphere_target = VGroup() for piece in sphere: p0, p1, p2, p3 = piece.get_anchors()[:4] piece.set_points_as_corners([ p3, p0, p1, p2, p3 ]) piece.generate_target() sphere_target.add(piece.target) piece.target.move_to( (1 + random.random()) * piece.get_center() ) self.add(ghost_sphere, sphere) self.wait() if self.with_expansion: self.play(LaggedStartMap( MoveToTarget, sphere )) self.wait() self.play(*[ Rotate(piece, 90 * DEGREES, axis=piece.get_center()) for piece in sphere ]) self.wait() self.play(Transform(sphere, cylinder, run_time=2)) self.wait(5) class RotateAllPiecesWithoutExpansion(RotateAllPiecesWithExpansion): CONFIG = { "with_expansion": False, } class ThinkingCritically(PiCreatureScene): def construct(self): randy = self.pi_creature self.play(randy.change, "pondering") self.wait() self.play( randy.change, "hesitant", 2 * UP, ) self.wait() self.play(randy.change, "sassy") self.wait() self.play(randy.change, "angry") self.wait(4) class WriteNotEquals(Scene): def construct(self): symbol = TexMobject("\\ne") symbol.scale(2) symbol.set_background_stroke(width=0) self.play(Write(symbol)) self.wait() class RectangulatedSphere(SphereCylinderScene): CONFIG = { "sphere_config": { "resolution": (10, 20) }, "uniform_color": False, "wait_time": 10, } def construct(self): sphere = self.get_sphere() if self.uniform_color: sphere.set_stroke(BLUE_E, width=0.5) sphere.set_fill(BLUE_E) self.set_camera_to_default_position() self.begin_ambient_camera_rotation(0.05) self.add(sphere) self.wait(self.wait_time) class SmoothSphere(RectangulatedSphere): CONFIG = { "sphere_config": { "resolution": (200, 400), }, "uniform_color": True, "wait_time": 0, } class SequenceOfSpheres(SphereCylinderScene): def construct(self): n_shapes = 4 spheres, cylinders = groups = VGroup(*[ VGroup(*[ func(resolution=(n, 2 * n)) for k in range(1, n_shapes + 1) for n in [3 * (2**k)] ]) for func in [self.get_sphere, self.get_cylinder] ]) groups.scale(0.5) for group in groups: for shape in group: for piece in shape: piece.make_jagged() shape.set_stroke(width=0) for group in groups: group.add(self.get_oriented_tex("?").scale(2)) group.arrange(RIGHT, buff=LARGE_BUFF) groups.arrange(IN, buff=1.5) all_equals = VGroup() for sphere, cylinder in zip(spheres, cylinders): equals = self.get_oriented_tex("=") equals.scale(1.5) equals.rotate(90 * DEGREES, UP) equals.move_to(interpolate( sphere.get_nadir(), cylinder.get_zenith(), 0.5 )) all_equals.add(equals) all_equals.remove(all_equals[-1]) arrow_groups = VGroup() for group in groups: arrow_group = VGroup() for m1, m2 in zip(group, group[1:]): arrow = self.get_oriented_tex("\\rightarrow") arrow.move_to(interpolate( m1.get_right(), m2.get_left(), 0.5 )) arrow_group.add(arrow) arrow_groups.add(arrow_group) q_marks = VGroup(*[ group[-1] for group in groups ]) final_arrows = VGroup( arrow_groups[0][-1], arrow_groups[1][-1], ) for arrow in final_arrows: dots = self.get_oriented_tex("\\dots") dots.next_to(arrow, RIGHT, SMALL_BUFF) arrow.add(dots) q_marks.shift(MED_LARGE_BUFF * RIGHT) tilted_final_arrows = VGroup( final_arrows[0].copy().rotate( -45 * DEGREES, axis=DOWN ).shift(0.75 * IN), final_arrows[1].copy().rotate( 45 * DEGREES, axis=DOWN ).shift(0.75 * OUT), ) final_q_mark = q_marks[0].copy() final_q_mark.move_to(q_marks) self.set_camera_orientation( phi=80 * DEGREES, theta=-90 * DEGREES, ) for i in range(n_shapes): anims = [ FadeInFrom(spheres[i], LEFT), FadeInFrom(cylinders[i], LEFT), ] if i > 0: anims += [ Write(arrow_group[i - 1]) for arrow_group in arrow_groups ] self.play(*anims, run_time=1) self.play(GrowFromCenter(all_equals[i])) self.play( FadeInFrom(q_marks, LEFT), Write(final_arrows) ) self.wait() self.play( Transform(final_arrows, tilted_final_arrows), Transform(q_marks, VGroup(final_q_mark)), ) self.wait() def get_oriented_tex(self, tex): result = TexMobject(tex) result.rotate(90 * DEGREES, RIGHT) return result class WhatIsSurfaceArea(SpecialThreeDScene): CONFIG = { "change_power": True, } def construct(self): title = TextMobject("What is surface area?") title.scale(1.5) title.to_edge(UP) title.shift(0.035 * RIGHT) self.add_fixed_in_frame_mobjects(title) power_tracker = ValueTracker(1) surface = always_redraw( lambda: self.get_surface( radius=3, amplitude=1, power=power_tracker.get_value() ) ) pieces = surface.copy() pieces.clear_updaters() random.shuffle(pieces.submobjects) self.set_camera_to_default_position() self.begin_ambient_camera_rotation() # self.add(self.get_axes()) self.play(LaggedStartMap( DrawBorderThenFill, pieces, lag_ratio=0.2, )) self.remove(pieces) self.add(surface) if self.change_power: self.play( power_tracker.set_value, 5, run_time=2 ) self.play( power_tracker.set_value, 1, run_time=2 ) self.wait(2) def get_surface(self, radius, amplitude, power): def alt_pow(x, y): return np.sign(x) * (np.abs(x) ** y) return ParametricSurface( lambda u, v: radius * np.array([ v * np.cos(TAU * u), v * np.sin(TAU * u), 0, ]) + amplitude * np.array([ 0, 0, (v**2) * alt_pow(np.sin(5 * TAU * u), power), ]), resolution=(100, 20), v_min=0.01 ) class AltWhatIsSurfaceArea(WhatIsSurfaceArea): CONFIG = { "change_power": False, } def get_surface(self, radius, amplitude, power): return ParametricSurface( lambda u, v: radius * (1 - 0.8 * (v**2) ** power) * np.array([ np.cos(TAU * u) * (1 + 0.5 * v * np.sin(5 * TAU * u)), np.sin(TAU * u) * (1 + 0.5 * v * np.sin(5 * TAU * u)), v, ]), v_min=-1, v_max=1, resolution=(100, 25), ) class EoCWrapper(Scene): def construct(self): title = TextMobject("Essence of calculus") title.scale(1.5) title.to_edge(UP) rect = ScreenRectangle(height=6) rect.next_to(title, DOWN) self.add(title) self.play(ShowCreation(rect)) self.wait() class RoleOfCalculus(SpecialThreeDScene): CONFIG = { "camera_config": { "light_source_start_point": [-4, 5, 7], } } def construct(self): calc = TexMobject("\\int", "\\int") calc.space_out_submobjects(0.4) calc.scale(2) arrow = Vector(2 * RIGHT, color=WHITE) sphere = self.get_sphere() sphere.rotate(70 * DEGREES, axis=LEFT) group = VGroup(calc, arrow, sphere) group.arrange(RIGHT) group.shift(0.5 * RIGHT) cross = Cross(group[:2], stroke_width=10) # arrow2 = arrow.copy() self.add(calc, arrow) self.play(Write(sphere)) self.wait() self.play(ShowCreation(cross)) self.wait() self.play( sphere.next_to, ORIGIN, LEFT, 1.0, arrow.move_to, ORIGIN, LEFT, calc.next_to, ORIGIN, RIGHT, 2.25, FadeOut(cross), path_arc=PI, run_time=2, ) self.wait() class UnwrappedCircleLogic(UnfoldCircles): CONFIG = { "radius": 1.25, "dr": 0.01, } def construct(self): radius = self.radius dr = self.dr TexMobject.CONFIG["background_stroke_width"] = 2 unwrap_factor_tracker = ValueTracker(0) center_tracker = VectorizedPoint() highligt_prop_tracker = ValueTracker(0.5) def get_highlight_prop(): return highligt_prop_tracker.get_value() def get_r(): return radius * get_highlight_prop() center_tracker.move_to(4.5 * LEFT) def get_unwrapped_circle(): result = self.get_unwrapped_circle( radius=radius, dr=dr, unwrap_factor=unwrap_factor_tracker.get_value(), center=center_tracker.get_center() ) self.get_submob_from_prop( result, get_highlight_prop() ).set_stroke(YELLOW, 2) return result unwrapped_circle = always_redraw(get_unwrapped_circle) circle = unwrapped_circle.copy() circle.clear_updaters() R_line = Line(circle.get_center(), circle.get_bottom()) R_line.set_stroke(WHITE, 2) R_label = TexMobject("R") R_label.next_to(R_line, LEFT) circle_group = VGroup(circle, R_line, R_label) tri_R_line = always_redraw( lambda: Line( ORIGIN, radius * DOWN ).shift(center_tracker.get_center()) ) # Unwrap self.play(FadeInFromDown(circle_group)) self.add(circle_group, unwrapped_circle, tri_R_line, R_label) circle_group.set_stroke(opacity=0.5) self.play( unwrap_factor_tracker.set_value, 1, run_time=2 ) self.play( center_tracker.move_to, circle.get_right() + (radius + MED_SMALL_BUFF) * RIGHT, circle_group.set_stroke, {"opacity": 1}, ) self.wait() # Change radius r_line = always_redraw( lambda: Line( ORIGIN, get_r() * DOWN, stroke_width=2, stroke_color=WHITE, ).shift(circle.get_center()) ) r_label = TexMobject("r") r_label.add_updater( lambda m: m.next_to(r_line, LEFT, SMALL_BUFF) ) two_pi_r_label = TexMobject("2\\pi r") two_pi_r_label.add_updater( lambda m: m.next_to( self.get_submob_from_prop( unwrapped_circle, get_highlight_prop(), ), DOWN, SMALL_BUFF ) ) circle.add_updater( lambda m: m.match_style(unwrapped_circle) ) self.play( ReplacementTransform(R_line, r_line), ReplacementTransform(R_label, r_label), FadeInFromDown( two_pi_r_label.copy().clear_updaters(), remover=True ) ) self.add(two_pi_r_label) for prop in [0.2, 0.8, 0.5]: self.play( highligt_prop_tracker.set_value, prop, run_time=2 ) # Show line line = Line(*[ unwrapped_circle.get_corner(vect) for vect in (UL, DR) ]) line.set_color(PINK) line.set_fill(BLACK, 1) line_word = TextMobject("Line") line_word.next_to(ORIGIN, UP, SMALL_BUFF) line_word.rotate(line.get_angle(), about_point=ORIGIN) line_word.shift(line.get_center()) curve = line.copy() curve.points[1] = unwrapped_circle.get_corner(DL) not_line = TextMobject("Not line") not_line.rotate(line.get_angle() / 2) not_line.move_to(line_word) not_line.shift(0.3 * DOWN) self.play( ShowCreation(line), Write(line_word), ) self.wait() self.play(highligt_prop_tracker.set_value, 1) self.wait() # Bend line.save_state() line_word.save_state() self.play( Transform(line, curve), Transform(line_word, not_line), ) self.wait() self.play( Restore(line), Restore(line_word), # FadeIn(two_pi_r_label), ) self.wait() def get_submob_from_prop(self, mob, prop): n = len(mob.submobjects) return mob[min(int(prop * n), n - 1)] class AskAboutDirectConnection(TeacherStudentsScene, SpecialThreeDScene): CONFIG = { "camera_config": { "light_source_start_point": [-4, 5, 7], } } def construct(self): sphere = Sphere() cylinder = Cylinder() for mob in sphere, cylinder: mob.rotate(70 * DEGREES, LEFT) formula = TexMobject("4\\pi R^2") formula.set_color(BLUE) circle = Circle() circle.set_stroke(width=0) circle.set_fill(GREY_BROWN, 1) area_label = TexMobject("\\pi R^2", background_stroke_width=0) area_label.scale(1.5) circle.add(area_label) group = VGroup( sphere, cylinder, formula, circle ) for mob in group: mob.set_height(1.5) formula.scale(0.5) group.arrange(RIGHT, buff=1.5) group.to_edge(UP, buff=2) group[1:3].to_edge(UP) arrows = VGroup() for m1, m2 in zip(group, group[1:]): arrow = Arrow( m1.get_center(), m2.get_center(), buff=1, color=WHITE ) arrows.add(arrow) direct_arrow = Arrow( sphere, circle, color=WHITE ) q_marks = TexMobject(*"???") q_marks.space_out_submobjects(1.5) q_marks.scale(1.5) q_marks.next_to(direct_arrow, DOWN) self.play( self.teacher.change, "raise_right_hand", self.get_student_changes( *3 * ["pondering"], look_at_arg=group, ), LaggedStartMap(FadeInFromDown, group), LaggedStartMap(GrowArrow, arrows) ) self.wait() self.play( self.teacher.change, "pondering", self.students[2].change, "raise_right_hand", GrowArrow(direct_arrow), LaggedStartMap( FadeInFrom, q_marks, lambda m: (m, UP), lag_ratio=0.8, run_time=1.5, ) ) self.change_student_modes( "erm", "sassy", "raise_right_hand", ) self.wait(2) self.look_at(group) self.wait(2) class ExercisesGiveLearning(MovingCameraScene): def construct(self): bulb = Lightbulb() arrow1 = Arrow(ORIGIN, RIGHT, buff=0) lectures = TextMobject("Lectures") exercises = TextMobject("Exercises") frame = self.camera_frame frame.scale(0.7) bulb.next_to(arrow1, RIGHT) for word in lectures, exercises: word.next_to(arrow1, LEFT) cross = Cross(lectures) # Knock down lectures self.add(lectures) self.play(GrowArrow(arrow1)) self.play(LaggedStartMap(DrawBorderThenFill, bulb)) self.play(ShowCreation(cross)) self.play( VGroup(lectures, cross).shift, DOWN, FadeInFrom(exercises, UP) ) self.wait() # Show self arrow2 = arrow1.copy() arrow2.next_to(lectures, LEFT) logo = Logo() logo.set_height(1) logo.next_to(arrow2, LEFT) pupil_copy = logo.pupil.copy() self.add(logo, pupil_copy) self.play( frame.shift, 1.5 * LEFT, Write(logo, run_time=1.5) ) self.remove(pupil_copy) self.play( GrowArrow(arrow2), FadeOut(cross) ) self.wait() self.play( VGroup(logo, arrow2).next_to, exercises, LEFT ) self.wait() class NobodyLikesHomework(TeacherStudentsScene): def construct(self): self.change_student_modes( "angry", "pleading", "angry", added_anims=[self.teacher.change, "guilty"] ) self.wait() self.change_all_student_modes( "tired", look_at_arg=8 * RIGHT + 4 * DOWN, added_anims=[self.teacher.change, "tease"] ) self.wait(2) class ChallengeMode(Scene): def construct(self): words = TextMobject("Challenge mode: Predict the proof") words.scale(1.5) words.to_edge(UP) self.play(Write(words)) self.wait() class SecondProof(SpecialThreeDScene): CONFIG = { "sphere_config": { "resolution": (30, 30), }, "n_random_subsets": 12, } def construct(self): self.setup_shapes() self.divide_into_rings() self.show_shadows() self.correspond_to_every_other_ring() self.cut_cross_section() self.show_theta() self.enumerate_rings() self.ask_about_ring_circumference() self.ask_about_shadow_area() self.ask_about_2_to_1_correspondance() self.show_all_shadow_rings() self.ask_about_global_correspondance() def setup_shapes(self): sphere = self.get_sphere() sphere.set_stroke(WHITE, width=0.25) self.add(sphere) self.sphere = sphere u_values, v_values = sphere.get_u_values_and_v_values() rings = VGroup(*[VGroup() for u in u_values]) for piece in sphere: rings[piece.u_index].add(piece.copy()) self.set_ring_colors(rings) self.rings = rings self.axes = self.get_axes() self.add(self.axes) self.set_camera_to_default_position() self.begin_ambient_camera_rotation() def divide_into_rings(self): rings = self.rings self.play(FadeIn(rings), FadeOut(self.sphere)) self.play( rings.space_out_submobjects, 1.5, rate_func=there_and_back_with_pause, run_time=3 ) self.wait(2) rings.save_state() def show_shadows(self): rings = self.rings north_rings = rings[:len(rings) // 2] ghost_rings = rings.copy() ghost_rings.set_fill(opacity=0.0) ghost_rings.set_stroke(WHITE, width=0.5, opacity=0.2) north_rings.submobjects.reverse() shadows = self.get_shadow(north_rings) for piece in shadows.family_members_with_points(): piece.set_stroke( piece.get_fill_color(), width=0.5, ) for shadow in shadows: shadow.save_state() shadows.become(north_rings) self.add(ghost_rings) self.play(FadeOut(rings), Animation(shadows)) self.play(LaggedStartMap(Restore, shadows)) self.wait() self.move_camera(phi=40 * DEGREES) self.wait(3) # Show circle radius = self.sphere_config["radius"] radial_line = Line(ORIGIN, radius * RIGHT) radial_line.set_stroke(RED) R_label = TexMobject("R") R_label.set_background_stroke(width=1) R_label.next_to(radial_line, DOWN) self.play( FadeInFromDown(R_label), ShowCreation(radial_line) ) self.play(Rotating( radial_line, angle=TAU, about_point=ORIGIN, rate_func=smooth, run_time=3, )) self.wait() self.set_variables_as_attrs( shadows, ghost_rings, radial_line, R_label ) def correspond_to_every_other_ring(self): rings = self.rings shadows = self.shadows shadows.submobjects.reverse() rings.restore() self.set_ring_colors(rings) every_other_ring = rings[1::2] self.move_camera( phi=70 * DEGREES, theta=-135 * DEGREES, added_anims=[ FadeOut(self.R_label), FadeOut(self.radial_line), ], run_time=2, ) shadows_copy = shadows.copy() shadows.fade(1) self.play( ReplacementTransform( shadows_copy, every_other_ring ), FadeOut(self.ghost_rings), run_time=2, ) self.wait(5) self.every_other_ring = every_other_ring def cut_cross_section(self): shadows = self.shadows every_other_ring = self.every_other_ring rings = self.rings back_half = self.get_hemisphere(rings, UP) front_half = self.get_hemisphere(rings, DOWN) front_half_ghost = front_half.copy() front_half_ghost.set_fill(opacity=0.2) front_half_ghost.set_stroke(opacity=0) # shaded_back_half = back_half.copy() # for piece in shaded_back_half.family_members_with_points(): # piece.points = piece.points[::-1] # shaded_back_half.scale(0.999) # shaded_back_half.set_fill(opacity=0.5) radius = self.sphere_config["radius"] circle = Circle(radius=radius) circle.set_stroke(PINK, 2) circle.rotate(90 * DEGREES, RIGHT) every_other_ring_copy = every_other_ring.copy() self.add(every_other_ring_copy) self.remove(every_other_ring) rings.set_fill(opacity=0.8) rings.set_stroke(opacity=0.6) self.play( FadeIn(back_half), FadeIn(front_half_ghost), FadeIn(circle), FadeOut(shadows), FadeOut(every_other_ring_copy), ) self.wait() self.set_variables_as_attrs( back_half, front_half, front_half_ghost, slice_circle=circle ) def show_theta(self): theta_tracker = ValueTracker(0) get_theta = theta_tracker.get_value theta_group = always_redraw( lambda: self.get_theta_group(get_theta()) ) theta_mob_opacity_tracker = ValueTracker(0) get_theta_mob_opacity = theta_mob_opacity_tracker.get_value theta_mob = theta_group[-1] theta_mob.add_updater( lambda m: m.set_fill(opacity=get_theta_mob_opacity()) ) theta_mob.add_updater( lambda m: m.set_background_stroke( width=get_theta_mob_opacity() ) ) lit_ring = always_redraw( lambda: self.get_ring_from_theta( self.rings, get_theta() ).copy().set_color(YELLOW) ) self.stop_ambient_camera_rotation() self.move_camera(theta=-60 * DEGREES) self.add(theta_group, lit_ring) n_rings = len(self.rings) - 1 lit_ring_index = int((30 / 180) * n_rings) angle = PI * lit_ring_index / n_rings for alpha in [angle, 0, PI, angle]: self.play( theta_tracker.set_value, alpha, theta_mob_opacity_tracker.set_value, 1, Animation(self.camera.phi_tracker), run_time=2, ) self.wait() # Label d-theta radius = self.sphere_config["radius"] d_theta = PI / len(self.rings) alt_theta = get_theta() + d_theta alt_theta_group = self.get_theta_group(alt_theta) alt_R_line = alt_theta_group[1] # d_theta_arc = Arc( # start_angle=get_theta(), # angle=d_theta, # radius=theta_group[0].radius, # stroke_color=PINK, # stroke_width=3, # ) # d_theta_arc.rotate(90 * DEGREES, axis=RIGHT, about_point=ORIGIN) brace = Brace(Line(ORIGIN, radius * d_theta * RIGHT), UP) brace.rotate(90 * DEGREES, RIGHT) brace.next_to(self.sphere, OUT, buff=0) brace.add_to_back(brace.copy().set_stroke(BLACK, 3)) brace.rotate( get_theta() + d_theta / 2, axis=UP, about_point=ORIGIN, ) brace_label = TexMobject("R\\,d\\theta") brace_label.rotate(90 * DEGREES, RIGHT) brace_label.next_to(brace, OUT + RIGHT, buff=0) radial_line = self.radial_line R_label = self.R_label R_label.rotate(90 * DEGREES, RIGHT) R_label.next_to(radial_line, IN, SMALL_BUFF) self.play( TransformFromCopy(theta_group[1], alt_R_line), GrowFromCenter(brace), Animation(self.camera.phi_tracker), ) self.wait() self.move_camera( phi=90 * DEGREES, theta=-90 * DEGREES, ) self.wait() self.play( FadeInFrom(brace_label, IN), ) self.play( ShowCreation(radial_line), FadeIn(R_label), ) self.wait() self.move_camera( phi=70 * DEGREES, theta=-70 * DEGREES, ) self.wait(3) self.set_variables_as_attrs( theta_tracker, lit_ring, theta_group, brace, brace_label, d_theta, alt_R_line, theta_mob_opacity_tracker, ) def enumerate_rings(self): pass # Skip, for now... def ask_about_ring_circumference(self): theta = self.theta_tracker.get_value() radius = self.sphere_config["radius"] circle = Circle( radius=radius * np.sin(theta) ) circle.shift(radius * np.cos(theta) * OUT) circle.set_stroke(Color("red"), 5) to_fade = VGroup( self.R_label, self.radial_line, self.brace, self.brace_label ) self.move_camera( phi=0 * DEGREES, theta=-90 * DEGREES, added_anims=[FadeOut(to_fade)] ) self.play(ShowCreation(circle)) self.wait() self.move_camera( phi=70 * DEGREES, theta=-70 * DEGREES, added_anims=[ FadeIn(to_fade), FadeOut(circle), ] ) self.wait() def ask_about_shadow_area(self): lit_ring = self.lit_ring lit_ring_copy = lit_ring.copy() lit_ring_copy.clear_updaters() all_shadows = self.shadows all_shadows.set_fill(BLUE_E, 0.5) for piece in all_shadows.family_members_with_points(): piece.set_stroke(width=0) radius = self.sphere_config["radius"] shadow = self.get_shadow(lit_ring) theta = self.theta_tracker.get_value() d_theta = self.d_theta def get_dashed_line(angle): p0 = np.cos(angle) * OUT + np.sin(angle) * RIGHT p0 *= radius p1 = np.array([*p0[:2], 0]) result = DashedLine(p0, p1) result.set_stroke(WHITE, 1) result.add_to_back( result.copy().set_stroke(BLACK, 2) ) result.set_shade_in_3d(True) return result dashed_lines = VGroup(*[ get_dashed_line(t) for t in [theta, theta + d_theta] ]) self.play( ReplacementTransform(lit_ring_copy, shadow), FadeOut(self.R_label), FadeOut(self.radial_line), Animation(self.camera.phi_tracker), *map(ShowCreation, dashed_lines), run_time=2, ) self.wait(2) self.set_variables_as_attrs( dashed_lines, lit_shadow=shadow, ) def ask_about_2_to_1_correspondance(self): theta_tracker = ValueTracker(0) get_theta = theta_tracker.get_value new_lit_ring = always_redraw( lambda: self.get_ring_from_theta( self.rings, get_theta() ).copy().set_color(PINK) ) self.add(new_lit_ring) for angle in [PI, 0, PI]: self.play( theta_tracker.set_value, angle, Animation(self.camera.phi_tracker), run_time=3 ) self.remove(new_lit_ring) self.remove(theta_tracker) def show_all_shadow_rings(self): lit_ring_copy = self.lit_ring.copy() lit_ring_copy.clear_updaters() self.remove(self.lit_ring) theta_group_copy = self.theta_group.copy() theta_group_copy.clear_updaters() self.remove(self.theta_group, *self.theta_group) to_fade = VGroup( theta_group_copy, self.alt_R_line, self.brace, self.brace_label, lit_ring_copy, self.lit_shadow, self.slice_circle, self.dashed_lines, ) R_label = self.R_label radial_line = self.radial_line R_label.rotate( -90 * DEGREES, axis=RIGHT, about_point=radial_line.get_center() ) shadows = self.shadows self.set_ring_colors(shadows, [GREY_BROWN, DARK_GREY]) for submob in shadows: submob.save_state() shadows.become(self.rings.saved_state[:len(shadows)]) self.play( FadeOut(to_fade), LaggedStartMap(FadeIn, shadows), self.theta_mob_opacity_tracker.set_value, 0, ) self.play( LaggedStartMap(Restore, shadows), ApplyMethod( self.camera.phi_tracker.set_value, 60 * DEGREES, ), ApplyMethod( self.camera.theta_tracker.set_value, -130 * DEGREES, ), run_time=3 ) self.play( ShowCreation(radial_line), FadeIn(R_label), Animation(self.camera.phi_tracker), ) self.begin_ambient_camera_rotation() self.wait() rings = self.rings rings_copy = rings.saved_state.copy() self.set_ring_colors(rings_copy) self.play( FadeOut(R_label), FadeOut(radial_line), FadeIn(rings_copy) ) self.remove(rings_copy) rings.become(rings_copy) self.add(rings) def ask_about_global_correspondance(self): rings = self.rings self.play( FadeOut(rings[::2]) ) self.wait(8) # def set_ring_colors(self, rings, colors=[BLUE_E, BLUE_D]): for i, ring in enumerate(rings): color = colors[i % len(colors)] ring.set_fill(color, opacity=1) ring.set_stroke(color, width=0.5, opacity=1) for piece in ring: piece.insert_n_curves(4) piece.on_sphere = True piece.points = np.array([ *piece.points[3:-1], *piece.points[:3], piece.points[3] ]) return rings def get_shadow(self, mobject): result = mobject.copy() result.apply_function( lambda p: np.array([*p[:2], 0]) ) return result def get_hemisphere(self, group, vect): if len(group.submobjects) == 0: if np.dot(group.get_center(), vect) > 0: return group else: return VMobject() else: return VGroup(*[ self.get_hemisphere(submob, vect) for submob in group ]) def get_northern_hemisphere(self, group): return self.get_hemisphere(group, OUT) def get_theta(self, ring): piece = ring[0] point = piece.points[3] return np.arccos(point[2] / get_norm(point)) def get_theta_group(self, theta): arc = Arc( start_angle=90 * DEGREES, angle=-theta, radius=0.5, ) arc.rotate(90 * DEGREES, RIGHT, about_point=ORIGIN) arc.set_stroke(YELLOW, 2) theta_mob = TexMobject("\\theta") theta_mob.rotate(90 * DEGREES, RIGHT) vect = np.cos(theta / 2) * OUT + np.sin(theta / 2) * RIGHT theta_mob.move_to( (arc.radius + 0.25) * normalize(vect), ) theta_mob.set_background_stroke(width=1) radius = self.sphere_config["radius"] point = arc.point_from_proportion(1) radial_line = Line( ORIGIN, radius * normalize(point) ) radial_line.set_stroke(WHITE, 2) return VGroup(arc, radial_line, theta_mob) def get_ring_from_theta(self, rings, theta): n_rings = len(rings) index = min(int((theta / PI) * n_rings), n_rings - 1) return rings[index] class SecondProofHigherRes(SecondProof): CONFIG = { "sphere_config": { "resolution": (60, 60), }, } class SecondProofHighestRes(SecondProof): CONFIG = { "sphere_config": { "resolution": (120, 120), }, } class RangeFrom0To180(Scene): def construct(self): angle = Integer(0, unit="^\\circ") angle.scale(2) self.add(angle) self.wait() self.play(ChangeDecimalToValue( angle, 180, run_time=2, )) self.wait() class Question1(Scene): def construct(self): kwargs = { "tex_to_color_map": { "circumference": RED, } } question = TextMobject( """ \\small Question \\#1: What is the circumference of\\\\ one of these rings (in terms of $R$ and $\\theta$)?\\\\ """, **kwargs ) prompt = TextMobject( """ Multiply this circumference by $R\\,d\\theta$ to \\\\ get an approximation of the ring's area. """, **kwargs ) for words in question, prompt: words.set_width(FRAME_WIDTH - 1) self.play(FadeInFromDown(question)) self.wait(2) for word in question, prompt: word.circum = word.get_part_by_tex("circumference") word.remove(word.circum) self.play( FadeOutAndShift(question, UP), FadeInFromDown(prompt), question.circum.replace, prompt.circum, run_time=1.5 ) self.wait() class YouCouldIntegrate(TeacherStudentsScene): def construct(self): self.student_says( "Integrate?", student_index=2, bubble_kwargs={"direction": LEFT}, ) self.play(self.teacher.change, "hesitant") self.wait() self.teacher_says( "We'll be a bit \\\\ more Archimedean", target_mode="speaking" ) self.change_all_student_modes("confused") self.wait() class Question2(Scene): def construct(self): question = TextMobject( """ Question \\#2: What is the area of the shadow of\\\\ one of these rings? (In terms of $R$, $\\theta$, and $d\\theta$). """, tex_to_color_map={ "shadow": YELLOW, } ) question.set_width(FRAME_WIDTH - 1) self.play(FadeInFromDown(question)) self.wait() class Question3(Scene): def construct(self): question = TextMobject("Question \\#3:") question.to_edge(LEFT) equation = TextMobject( "(Shadow area)", "=", "$\\frac{1}{2}$", "(Area of one of the rings)" ) equation[0][1:-1].set_color(YELLOW) equation[3][1:-1].set_color(PINK) equation.next_to(question, RIGHT) which_one = TextMobject("Which one?") # which_one.set_color(YELLOW) brace = Brace(equation[-1], DOWN, buff=SMALL_BUFF) which_one.next_to(brace, DOWN, SMALL_BUFF) self.add(question) self.play(FadeInFrom(equation)) self.wait() self.play( GrowFromCenter(brace), Write(which_one) ) self.wait() class ExtraHint(Scene): def construct(self): title = TextMobject("Extra hint") title.scale(2.5) title.shift(UP) equation = TexMobject( "\\sin(2\\theta) = 2\\sin(\\theta)\\cos(\\theta)" ) equation.next_to(title, DOWN) self.add(title, equation) class Question4(Scene): def construct(self): question = TextMobject( "Question \\#4:", "Explain how the shadows relate to\\\\" "every other ring on the sphere.", tex_to_color_map={ "shadows": YELLOW, "every other ring": BLUE, } ) self.add(question[0]) self.wait() self.play(FadeInFromDown(question[1:])) self.wait() class Question5(Scene): def construct(self): question = TextMobject( """ Question \\#5: Why does this imply that the \\\\ shadow is $\\frac{1}{4}$ the surface area? """ ) self.play(FadeInFromDown(question)) self.wait() class SpherePatronThanks(Scene): CONFIG = { "specific_patrons": [ "1stViewMaths", "Adrian Robinson", "Alexis Olson", "Ali Yahya", "Andrew Busey", "Ankalagon", "Antonio Juarez", "Art Ianuzzi", "Arthur Zey", "Awoo", "Bernd Sing", "Boris Veselinovich", "Brian Staroselsky", "brian tiger chow", "Brice Gower", "Britt Selvitelle", "Burt Humburg", "Carla Kirby", "Charles Southerland", "Chris Connett", "Christian Kaiser", "Clark Gaebel", "Cooper Jones", "Danger Dai", "Dave B", "Dave Kester", "dave nicponski", "David Clark", "David Gow", "Delton Ding", "Devarsh Desai", "Devin Scott", "eaglle", "Eric Younge", "Eryq Ouithaqueue", "Evan Phillips", "Federico Lebron", "Florian Chudigiewitsch", "Giovanni Filippi", "Graham", "Hal Hildebrand", "J", "Jacob Magnuson", "Jameel Syed", "James Hughes", "Jan Pijpers", "Jason Hise", "Jeff Linse", "Jeff Straathof", "Jerry Ling", "John Griffith", "John Haley", "John Shaughnessy", "John V Wertheim", "Jonathan Eppele", "Jonathan Wilson", "Joseph John Cox", "Joseph Kelly", "Juan Benet", "Julian Pulgarin", "Kai-Siang Ang", "Kanan Gill", "Kaustuv DeBiswas", "L0j1k", "Linh Tran", "Luc Ritchie", "Ludwig Schubert", "Lukas -krtek.net- Novy", "Lukas Biewald", "Magister Mugit", "Magnus Dahlström", "Magnus Lysfjord", "Mark B Bahu", "Markus Persson", "Mathew Bramson", "Mathias Jansson", "Matt Langford", "Matt Roveto", "Matt Russell", "Matthew Cocke", "Maurício Collares", "Mehdi Razavi", "Michael Faust", "Michael Hardel", "MrSneaky", "Mustafa Mahdi", "Márton Vaitkus", "Nathan Jessurun", "Nero Li", "Oliver Steele", "Omar Zrien", "Peter Ehrnstrom", "Peter Mcinerney", "Quantopian", "Randy C. Will", "Richard Burgmann", "Ripta Pasay", "Rish Kundalia", "Robert Teed", "Roobie", "Roy Larson", "Ryan Atallah", "Ryan Williams", "Scott Walter, Ph.D.", "Sindre Reino Trosterud", "soekul", "Solara570", "Song Gao", "Steven Soloway", "Stevie Metke", "Ted Suzman", "Valeriy Skobelev", "Vassili Philippov", "Xavier Bernard", "Yana Chernobilsky", "Yaw Etse", "YinYangBalance Asia", "Zach Cardwell", ], } def construct(self): self.add_title() self.show_columns() def add_title(self): title = TextMobject("Funded by the community, with special thanks to:") title.set_color(YELLOW) title.to_edge(UP) underline = Line(LEFT, RIGHT) underline.set_width(title.get_width() + MED_SMALL_BUFF) underline.next_to(title, DOWN, SMALL_BUFF) title.add(underline) self.add(title) self.title = title def show_columns(self): random.seed(1) random.shuffle(self.specific_patrons) patrons = VGroup(*[ TextMobject(name) for name in self.specific_patrons ]) columns = VGroup() column_size = 15 for n in range(0, len(patrons), column_size): column = patrons[n:n + column_size] column.arrange( DOWN, aligned_edge=LEFT ) columns.add(column) columns.set_height(6) for group in columns[:4], columns[4:]: for k, column in enumerate(group): column.move_to( 6.5 * LEFT + 3.75 * k * RIGHT + 2.5 * UP, UL ) self.add(columns[:4]) self.wait() for k in range(4): self.play( FadeOut(columns[k]), FadeIn(columns[k + 4]), ) self.wait() class EndScreen(PatreonEndScreen): CONFIG = { "thanks_words": "", } class ForThoseStillAround(Scene): def construct(self): words = TextMobject("Still here?") words.scale(1.5) url = TextMobject("3blue1brown.com/store") # url.scale(1.5) url.to_edge(UP, buff=MED_SMALL_BUFF) self.play(Write(words)) self.wait() self.play(ReplacementTransform(words, url)) self.wait() class PatronWords(Scene): def construct(self): words = TextMobject("\\$2+ Patrons get \\\\ 50\\% off") words.to_corner(UL) words.set_color(RED) self.add(words) class PlushMe(TeacherStudentsScene): def construct(self): self.student_says("Plushie me?") self.change_student_modes("happy", None, "happy") self.play(self.teacher.change, "confused") self.wait() self.teacher_says("...why?", target_mode="maybe") self.wait(2) class Thumbnail(SpecialThreeDScene): CONFIG = { "camera_config": { "light_source_start_point": [-10, 5, 7], } } def construct(self): radius = 1.75 sphere = self.get_sphere(radius=radius) sphere.rotate(70 * DEGREES, LEFT) sphere.set_fill(BLUE_E) sphere.set_stroke(WHITE, 0.5) circles = VGroup(*[ Circle(radius=radius) for x in range(4) ]) circles.set_stroke(WHITE, 2) circles.set_fill(BLUE_E, 1) circles[0].set_fill(GREY_BROWN) circles.arrange_in_grid() for circle in circles: formula = TexMobject("\\pi", "R", "^2") formula.set_color_by_tex("R", YELLOW) formula.scale(2) formula.move_to(circle) circle.add(formula) equals = TexMobject("=") equals.scale(3) group = VGroup(sphere, equals, circles) group.arrange(RIGHT, buff=MED_SMALL_BUFF) equals.shift(3 * SMALL_BUFF * RIGHT) why = TextMobject("Why?!") why.set_color(YELLOW) why.scale(2.5) why.next_to(sphere, UP) sa_formula = TexMobject("4\\pi", "R", "^2") sa_formula.set_color_by_tex("R", YELLOW) sa_formula.scale(2) sa_formula.next_to(sphere, DOWN) self.camera.distance_tracker.set_value(100) self.add(sphere, equals, circles, why, sa_formula)