diff --git a/active_projects/sphere_area.py b/active_projects/sphere_area.py index 6473da15..b75de6aa 100644 --- a/active_projects/sphere_area.py +++ b/active_projects/sphere_area.py @@ -53,8 +53,9 @@ class SphereCylinderScene(SpecialThreeDScene): } } - def get_cylinder(self): - return Cylinder(**self.sphere_config) + def get_cylinder(self, **kwargs): + config = merge_config([kwargs, self.sphere_config]) + return Cylinder(**config) def get_cylinder_caps(self): R = self.sphere_config["radius"] @@ -396,10 +397,217 @@ class MapSphereOntoCylinder(SphereCylinderScene): two_pi_R.next_to(top_line, OUT) two_R.next_to(side_line, RIGHT) - self.play(LaggedStart( - DrawBorderThenFill, VGroup(*lines, *texs) - )) + 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_submobjects_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 updating_mobject_from_func( + 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( + LaggedStart(Write, circle_copies, lag_ratio=0.7), + LaggedStart(Write, R_labels), + LaggedStart(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(LaggedStart(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): @@ -498,8 +706,8 @@ class ShowProjection(SphereCylinderScene): def label_sides(self): sphere = self.sphere equation = self.equation - l_brace, h_brace = equation.braces - length_label, height_label = equation.labels + 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 @@ -532,10 +740,10 @@ class ShowProjection(SphereCylinderScene): *map(ShowCreationThenDestruction, lat_lines), run_time=2 ) - self.add_fixed_in_frame_mobjects(l_brace, length_label) + self.add_fixed_in_frame_mobjects(w_brace, width_label) self.play( - GrowFromCenter(l_brace), - Write(length_label), + GrowFromCenter(w_brace), + Write(width_label), ) self.wait() self.play( @@ -734,18 +942,18 @@ class ShowProjection(SphereCylinderScene): equation.to_corner(UR) brace = Brace(Line(ORIGIN, 0.5 * RIGHT), DOWN) - l_brace = brace.copy().match_width(lhs, stretch=True) + w_brace = brace.copy().match_width(lhs, stretch=True) h_brace = brace.copy().rotate(-90 * DEGREES) h_brace.match_height(lhs, stretch=True) - l_brace.next_to(lhs, DOWN, buff=SMALL_BUFF) + w_brace.next_to(lhs, DOWN, buff=SMALL_BUFF) h_brace.next_to(lhs, LEFT, buff=SMALL_BUFF) - braces = VGroup(l_brace, h_brace) + braces = VGroup(w_brace, h_brace) - length_label = TextMobject("Length") + width_label = TextMobject("Width") height_label = TextMobject("Height") - labels = VGroup(length_label, height_label) + labels = VGroup(width_label, height_label) labels.scale(0.75) - length_label.next_to(l_brace, DOWN, SMALL_BUFF) + width_label.next_to(w_brace, DOWN, SMALL_BUFF) height_label.next_to(h_brace, LEFT, SMALL_BUFF) equation.braces = braces @@ -884,17 +1092,17 @@ class JustifyLengthStretch(ShowProjection): VFadeOut(to_fade, run_time=2), FadeIn(circle), ) - self.move_camera( - phi=80 * DEGREES, - theta=-85 * DEGREES, - ) + # 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 + DOWN) + R_label.next_to(R_line, IN) self.add_fixed_orientation_mobjects(R_label) self.play( @@ -916,7 +1124,7 @@ class JustifyLengthStretch(ShowProjection): s_rect.get_corner(OUT + LEFT + DOWN) ) d_label = TexMobject("d") - d_label.next_to(d_line, IN + DOWN) + d_label.next_to(d_line, IN) self.add_fixed_orientation_mobjects(d_label) self.play( @@ -930,7 +1138,7 @@ class JustifyLengthStretch(ShowProjection): self.play( FadeIn(to_fade_in), FadeOut(to_fade_out), - d_label.next_to, to_fade_in, IN + DOWN, + d_label.next_to, to_fade_in, IN, ) self.d_line = d_line @@ -942,7 +1150,7 @@ class JustifyLengthStretch(ShowProjection): R_line = self.R_line R_label = self.R_label example_group = self.example_group - s_rect = example_group[0] + s_rect, proj_lines, c_rect = example_group p1 = s_rect.get_anchors()[1] p2 = s_rect.get_anchors()[2] @@ -961,55 +1169,69 @@ class JustifyLengthStretch(ShowProjection): big_triangle.set_fill(RED, opacity=1) equation = VGroup( - triangle.copy(), + triangle.copy().rotate(-3 * DEGREES), TexMobject("\\sim"), - big_triangle.copy() + big_triangle.copy().rotate(-3 * DEGREES) ) equation.arrange_submobjects(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=0 * DEGREES, - theta=-90 * DEGREES, - distance=1000, + 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, ) - self.play(FadeInFromLarge(triangle, 3)) + 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() - self.play(ShowCreation(d_line)) + 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) - self.play( - TransformFromCopy(triangle, equation[0]), - FadeIn(equation[1:]), - FadeInFromDown(eq_d), - FadeInFromDown(eq_R), - ) - self.wait() + R_label.next_to(R_line, DOWN, SMALL_BUFF) + R_label.shift(0.25 * IN) self.play( ReplacementTransform(triangle, big_triangle), - FadeOut(d_label), FadeIn(R_label), + Animation(self.camera.phi_tracker) ) - self.add(R_line) - R_line.set_color(WHITE) - self.play(ShowCreation(R_line)) self.wait(3) - self.add_fixed_in_frame_mobjects(equation, eq_d, eq_R) self.move_camera( phi=70 * DEGREES, theta=-70 * DEGREES, added_anims=[ - big_triangle.set_fill, {"opacity": 0.25} + big_triangle.set_fill, {"opacity": 0.25}, + d_label.next_to, d_line, IN, {"buff": 0.3}, ] ) self.begin_ambient_camera_rotation() @@ -1017,16 +1239,15 @@ class JustifyLengthStretch(ShowProjection): lost_hemisphere.restore() left_point = self.sphere.get_left() lost_hemisphere.rotate(-PI, axis=OUT, about_point=left_point) - d_label.next_to(d_line, IN, buff=0.3) self.play( Rotate( lost_hemisphere, PI, axis=OUT, about_point=left_point, + run_time=2, ), VFadeIn(lost_hemisphere), FadeOut(self.circle), - FadeIn(d_label), R_line.set_color, self.R_color, ) self.wait(10) @@ -1042,6 +1263,22 @@ class JustifyLengthStretch(ShowProjection): 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 LengthScaleLabel(Scene): def construct(self): text = TexMobject( @@ -1053,15 +1290,21 @@ class LengthScaleLabel(Scene): 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(4) + 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:]): @@ -1077,6 +1320,7 @@ class TinierAndTinerRectangles(SphereCylinderScene): LaggedStart(Restore, s2) ) self.remove(s1) + self.wait(5) class LimitViewToCrossSection(JustifyLengthStretch): @@ -1103,9 +1347,1479 @@ class LimitViewToCrossSection(JustifyLengthStretch): 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), + LaggedStart( + 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( + LaggedStart(MoveToTarget, movers), + LaggedStart(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(CircleThenFadeAround( + 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 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): + def construct(self): + rect1 = Rectangle( + height=2, width=1, + stroke_width=0, + fill_color=YELLOW, + fill_opacity=1, + background_stroke_width=2, + background_stroke_color=BLACK, + ) + rect2 = rect1.copy().rotate(-90 * DEGREES) + arrow = Arrow(ORIGIN, RIGHT, buff=0, color=WHITE) + group = VGroup(rect1, arrow, rect2) + group.arrange_submobjects(RIGHT) + group.center() + moving_rect = rect1.copy() + + low_brace = updating_mobject_from_func( + lambda: Brace(moving_rect, DOWN, buff=SMALL_BUFF) + ) + right_brace = updating_mobject_from_func( + lambda: Brace(moving_rect, RIGHT, buff=SMALL_BUFF) + ) + 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() + self.play(TransformFromCopy( + rect1, rect2, + path_arc=-90 * DEGREES, + run_time=2 + )) + + +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(LaggedStart( + 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_submobjects(RIGHT, buff=LARGE_BUFF) + groups.arrange_submobjects(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): + 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 = updating_mobject_from_func( + 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(LaggedStart( + DrawBorderThenFill, pieces, + lag_ratio=0.2, + )) + self.remove(pieces) + self.add(surface) + 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 UnwrappedCircleLogic(UnfoldCircles): + def construct(self): + radius = 1.25 + dr = 0.001 + 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_color(YELLOW) + return result + + unwrapped_circle = updating_mobject_from_func(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 = updating_mobject_from_func( + 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 = updating_mobject_from_func( + 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_submobjects(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, + ), + LaggedStart(FadeInFromDown, group), + LaggedStart(GrowArrow, arrows) + ) + self.wait() + self.play( + self.teacher.change, "pondering", + self.students[2].change, "raise_right_hand", + GrowArrow(direct_arrow), + LaggedStart( + 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(LaggedStart(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 SecondProof(SpecialThreeDScene): + CONFIG = { + "sphere_config": { + "resolution": (50, 50), + } + } + + 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_area() + self.ask_about_shadow_area() + self.ask_about_2_to_1_correspondance() + self.ask_about_global_correspondance() + + def setup_shapes(self): + sphere = self.get_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) + sphere.set_stroke(WHITE, width=0.25) + self.add(sphere) + self.sphere = sphere + 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(ApplyFunction(self.set_ring_colors, rings)) + self.play( + rings.space_out_submobjects, 1.5, + rate_func=there_and_back_with_pause, + run_time=3 + ) + self.wait(2) + + def show_shadows(self): + rings = self.rings + north_rings = self.get_northern_hemisphere(rings) + ghost_rings = rings.copy() + ghost_rings.set_fill(opacity=0.0) + ghost_rings.set_stroke(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(LaggedStart(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 + + every_other_ring = rings[1::2] + every_other_ring.set_fill(opacity=0.5) + self.move_camera( + phi=70 * DEGREES, + theta=-135 * DEGREES, + added_anims=[ + FadeOut(self.R_label), + FadeOut(self.radial_line), + ], + run_time=2, + ) + self.play( + TransformFromCopy(shadows, every_other_ring), + 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 + ghost_rings = self.ghost_rings + rings = self.rings + + back_half = self.get_hemisphere(rings, UP) + front_half = self.get_hemisphere(rings, DOWN) + + # shaded_back_half = back_half.copy() + # for piece in shaded_back_half: + # piece.points = np.array(list(reversed(piece.points))) + # shaded_back_half.scale(0.99) + + 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) + rings.set_fill(opacity=0.8) + self.play( + FadeIn(rings), + FadeOut(shadows), + FadeOut(ghost_rings), + ) + self.remove(every_other_ring_copy) + self.play( + FadeIn(circle), + FadeOutAndShift(front_half, IN) + ) + self.wait() + + self.back_half = back_half + self.circle = circle + + def show_theta(self): + theta_tracker = ValueTracker(0) + get_theta = theta_tracker.get_value + theta_group = updating_mobject_from_func( + 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 = updating_mobject_from_func( + 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) + angle = PI * 8 / 50 + for angle in [angle, 0, PI, angle]: + self.play( + theta_tracker.set_value, angle, + 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), + FadeInFrom(brace_label, IN) + ) + self.move_camera( + phi=90 * DEGREES, + theta=-90 * DEGREES, + ) + self.wait() + self.play( + ShowCreation(radial_line), + FadeIn(R_label), + ) + self.wait() + self.move_camera( + phi=70 * DEGREES, + theta=-70 * DEGREES, + ) + + self.theta_tracker = theta_tracker + self.lit_ring = lit_ring + self.theta_group = theta_group + + def enumerate_rings(self): + pass # Skip, for now... + + def ask_about_ring_area(self): pass + + def ask_about_shadow_area(self): + pass + + def ask_about_2_to_1_correspondance(self): + pass + + def ask_about_global_correspondance(self): + pass + + # + 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) + # for piece in ring[::2]: + # piece.set_color(BLUE_E) + # 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.next_to( + arc.radius * normalize(vect), + vect, + buff=SMALL_BUFF + ) + 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 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()