diff --git a/eoc/chapter5.py b/eoc/chapter5.py index 7992df73..1e90b9b9 100644 --- a/eoc/chapter5.py +++ b/eoc/chapter5.py @@ -80,20 +80,15 @@ class SlopeOfCircleExample(ZoomedScene): "zoomed_canvas_corner_buff" : MED_SMALL_BUFF, } def construct(self): - should_skip_animations = self.skip_animations - self.skip_animations = True - self.setup_plane() self.introduce_circle() self.talk_through_pythagorean_theorem() self.draw_example_slope() - # self.show_perpendicular_radius() + self.show_perpendicular_radius() self.show_dx_and_dy() self.write_slope_as_dy_dx() - # self.point_out_this_is_not_a_graph() - self.skip_animations = should_skip_animations + self.point_out_this_is_not_a_graph() self.perform_implicit_derivative() - self.show_rearrangement() self.show_final_slope() def setup_plane(self): @@ -115,6 +110,7 @@ class SlopeOfCircleExample(ZoomedScene): circle.point_from_proportion(1./8), UP+RIGHT ) + equation.to_edge(RIGHT) self.play(ShowCreation(circle, run_time = 2)) self.play(Write(equation)) @@ -239,12 +235,11 @@ class SlopeOfCircleExample(ZoomedScene): self.play(PiCreatureBubbleIntroduction( morty, "Suppose you \\\\ don't know this.", )) + to_fade =self.get_mobjects_from_last_animation() self.play(Blink(morty)) self.dither() - self.play(*map( - FadeOut, [morty, morty.bubble, morty.bubble.content] - )) + self.play(*map(FadeOut, to_fade)) self.play(*map(FadeOut, [radial_line, perp_mark])) self.dither() @@ -317,13 +312,15 @@ class SlopeOfCircleExample(ZoomedScene): dy_dx = TexMobject("\\frac{dy}{dx}") VGroup(*dy_dx[:2]).highlight(RED) VGroup(*dy_dx[-2:]).highlight(GREEN) - dy_dx.next_to(new_slope_word, RIGHT, buff = SMALL_BUFF) + dy_dx.next_to(new_slope_word, RIGHT) dy_dx.add_background_rectangle() self.play(Transform(slope_word, new_slope_word)) self.play(Write(dy_dx)) self.dither() + self.dy_dx = dy_dx + def point_out_this_is_not_a_graph(self): equation = self.circle_equation x = equation[1][0] @@ -364,13 +361,370 @@ class SlopeOfCircleExample(ZoomedScene): def perform_implicit_derivative(self): equation = self.circle_equation + morty = Mortimer() + morty.flip() + morty.next_to(ORIGIN, LEFT) + morty.to_edge(DOWN, buff = SMALL_BUFF) + q_marks = TexMobject("???") + q_marks.next_to(morty, UP) - def show_rearrangement(self): - pass + rect = Rectangle( + width = SPACE_WIDTH - SMALL_BUFF, + height = SPACE_HEIGHT - SMALL_BUFF, + stroke_width = 0, + fill_color = BLACK, + fill_opacity = 0.8, + ) + rect.to_corner(DOWN+RIGHT, buff = 0) + + derivative = TexMobject("2x\\,dx + 2y\\,dy = 0") + dx = VGroup(*derivative[2:4]) + dy = VGroup(*derivative[7:9]) + dx.highlight(GREEN) + dy.highlight(RED) + + + + self.play( + FadeIn(rect), + FadeIn(morty), + equation.next_to, ORIGIN, DOWN, MED_LARGE_BUFF, + equation.shift, SPACE_WIDTH*RIGHT/2, + ) + self.play( + morty.change_mode, "confused", + morty.look_at, equation + ) + self.play(Blink(morty)) + derivative.next_to(equation, DOWN) + derivative.shift( + equation[1][-3].get_center()[0]*RIGHT - \ + derivative[-2].get_center()[0]*RIGHT + ) + + + #Differentiate + self.play( + morty.look_at, derivative[0], + *[ + ReplacementTransform( + equation[1][i].copy(), + derivative[j], + ) + for i, j in (1, 0), (0, 1) + ] + ) + self.play(Write(dx, run_time = 1)) + self.dither() + self.play(*[ + ReplacementTransform( + equation[1][i].copy(), + derivative[j], + ) + for i, j in (2, 4), (3, 6), (4, 5) + ]) + self.play(Write(dy, run_time = 1)) + self.play(Blink(morty)) + self.play(*[ + ReplacementTransform( + equation[1][i].copy(), + derivative[j], + ) + for i, j in (-3, -2), (-2, -1), (-1, -1) + ]) + self.dither() + + #React + self.play(morty.change_mode, "erm") + self.play(Blink(morty)) + self.play(Write(q_marks)) + self.dither() + self.play(Indicate(dx), morty.look_at, dx) + self.play(Indicate(dy), morty.look_at, dy) + self.dither() + self.play( + morty.change_mode, "shruggie", + FadeOut(q_marks) + ) + self.play(Blink(morty)) + self.play( + morty.change_mode, "pondering", + morty.look_at, derivative, + ) + + #Rearrange + x, y, eq = np.array(derivative)[[1, 6, 9]] + final_form = TexMobject( + "\\frac{dy}{dx} = \\frac{-x}{y}" + ) + new_dy = VGroup(*final_form[:2]) + new_dx = VGroup(*final_form[3:5]) + new_dy.highlight(dy.get_color()) + new_dx.highlight(dx.get_color()) + new_dy.add(final_form[2]) + new_x = VGroup(*final_form[6:8]) + new_y = VGroup(*final_form[8:10]) + new_eq = final_form[5] + + final_form.next_to(derivative, DOWN) + final_form.shift((eq.get_center()[0]-new_eq.get_center()[0])*RIGHT) + + + self.play(*[ + ReplacementTransform( + mover.copy(), target, + run_time = 2, + path_arc = np.pi/2, + ) + for mover, target in [ + (dy, new_dy), + (dx, new_dx), + (eq, new_eq), + (x, new_x), + (y, new_y) + ] + ] + [ + morty.look_at, final_form + ]) + self.dither(2) + + self.morty = morty + self.neg_x_over_y = VGroup(*final_form[6:]) def show_final_slope(self): - pass - + morty = self.morty + dy_dx = self.dy_dx + coords = self.example_point_coords_mob + x, y = coords[1][1].copy(), coords[1][3].copy() + + frac = self.neg_x_over_y.copy() + frac.generate_target() + eq = TexMobject("=") + eq.add_background_rectangle() + eq.next_to(dy_dx, RIGHT) + frac.target.next_to(eq, RIGHT) + frac.target.shift(SMALL_BUFF*DOWN) + rect = BackgroundRectangle(frac.target) + + self.play( + FadeIn(rect), + MoveToTarget(frac), + Write(eq), + morty.look_at, rect, + run_time = 2, + ) + self.dither() + self.play(FocusOn(coords), morty.look_at, coords) + self.play(Indicate(coords)) + scale_factor = 1.4 + self.play( + x.scale, scale_factor, + x.highlight, GREEN, + x.move_to, frac[1], + FadeOut(frac[1]), + y.scale, scale_factor, + y.highlight, RED, + y.move_to, frac[3], DOWN, + y.shift, SMALL_BUFF*UP, + FadeOut(frac[3]), + morty.look_at, frac, + run_time = 2 + ) + self.dither() + self.play(Blink(morty)) + +class NameImplicitDifferentation(TeacherStudentsScene): + def construct(self): + title = TextMobject("``Implicit differentiation''") + + equation = TexMobject("x^2", "+", "y^2", "=", "5^2") + derivative = TexMobject( + "2x\\,dx", "+", "2y\\,dy", "=", "0" + ) + VGroup(*derivative[0][2:]).highlight(GREEN) + VGroup(*derivative[2][2:]).highlight(RED) + arrow = Arrow(ORIGIN, DOWN, buff = SMALL_BUFF) + group = VGroup(title, equation, arrow, derivative) + group.arrange_submobjects(DOWN) + group.to_edge(UP) + + self.add(title, equation) + self.play( + self.get_teacher().change_mode, "raise_right_hand", + ShowCreation(arrow) + ) + self.change_student_modes( + *["confused"]*3, + look_at_arg = derivative, + added_anims = [ReplacementTransform(equation.copy(), derivative)] + ) + self.dither(2) + self.teacher_says( + "Don't worry...", + added_anims = [ + group.scale, 0.7, + group.to_corner, UP+LEFT, + ] + ) + self.change_student_modes(*["happy"]*3) + self.dither(3) + +class Ladder(VMobject): + CONFIG = { + "height" : 4, + "width" : 1, + "n_rungs" : 6, + } + def generate_points(self): + left_line, right_line = [ + Line(ORIGIN, self.height*UP).shift(self.width*vect/2.0) + for vect in LEFT, RIGHT + ] + rungs = [ + Line( + left_line.point_from_proportion(a), + right_line.point_from_proportion(a), + ) + for a in np.linspace(0, 1, self.n_rungs+2)[1:-1] + ] + self.add(left_line, right_line, *rungs) + self.center() + + +class RelatedRatesExample(ThreeDScene): + CONFIG = { + "start_x" : 3.0, + "start_y" : 4.0, + "wall_color" : color_gradient([GREY_BROWN, BLACK], 4)[1], + "wall_center" : LEFT, + } + def construct(self): + should_skip_animations = self.skip_animations + self.skip_animations = True + + self.introduce_ladder() + self.write_related_rates() + self.measure_ladder() + self.skip_animations = should_skip_animations + self.slide_ladder() + + def introduce_ladder(self): + ladder = Ladder(height = self.get_ladder_length()) + + wall = Prism( + dimensions = [0.5, 6, 5], + fill_color = self.wall_color, + fill_opacity = 1, + ) + wall.rotate(np.pi/12, UP) + wall.shift(self.wall_center) + + ladder.generate_target() + ladder.fallen = ladder.copy() + ladder.target.rotate(self.get_ladder_angle(), LEFT) + ladder.fallen.rotate(np.pi/2, LEFT) + for ladder_copy in ladder.target, ladder.fallen: + ladder_copy.rotate(-5*np.pi/12, UP) + ladder_copy.next_to(wall, LEFT, 0, DOWN) + ladder_copy.shift(LARGE_BUFF*RIGHT) + + + self.play( + ShowCreation(ladder, run_time = 2) + ) + self.dither() + + self.play( + DrawBorderThenFill(wall), + MoveToTarget(ladder), + run_time = 2 + ) + self.dither() + + self.ladder = ladder + + def write_related_rates(self): + words = TextMobject("Related rates") + words.to_corner(UP+RIGHT) + self.play(Write(words)) + self.dither() + + def measure_ladder(self): + ladder = self.ladder + ladder_brace = self.get_ladder_brace(ladder) + + x_and_y_lines = self.get_x_and_y_lines(ladder) + x_line, y_line = x_and_y_lines + + y_label = TexMobject("%dm"%int(self.start_y)) + y_label.next_to(y_line, LEFT, buff = SMALL_BUFF) + y_label.highlight(y_line.get_color()) + + x_label = TexMobject("%dm"%int(self.start_x)) + x_label.next_to(x_line, UP) + x_label.highlight(x_line.get_color()) + + self.play( + GrowFromCenter(ladder_brace), + Write(ladder_brace.length_label), + ) + self.dither() + self.play(ShowCreation(y_line), Write(y_label)) + self.dither() + self.play(ShowCreation(x_line), Write(x_label)) + self.dither(2) + self.play(*map(FadeOut, [x_label, y_label])) + + self.ladder_brace = ladder_brace + self.x_and_y_lines = x_and_y_lines + + def slide_ladder(self): + ladder = self.ladder + brace = self.ladder_brace + x_and_y_lines = self.x_and_y_lines + + self.play( + Transform( + ladder, ladder.fallen, + rate_func = None, + run_time = self.start_y, + ), + ) + self.dither() + + + ######### + + def get_ladder_brace(self, ladder): + vect = rotate_vector(LEFT, -self.get_ladder_angle()) + brace = Brace(ladder, vect) + length_string = "%dm"%int(self.get_ladder_length()) + length_label = brace.get_text(length_string) + brace.length_label = length_label + return brace + + def get_x_and_y_lines(self, ladder): + top_point = ladder.get_corner(UP+RIGHT) + bottom_point = ladder.get_corner(DOWN+LEFT) + interim_point = top_point[0]*RIGHT + bottom_point[1]*UP + y_line = Line(top_point, interim_point) + y_line.highlight(RED) + x_line = Line(bottom_point, interim_point) + x_line.highlight(GREEN) + + return VGroup(x_line, y_line) + + def get_ladder_angle(self): + if hasattr(self, "ladder"): + c1 = self.ladder.get_corner(UP+RIGHT) + c2 = self.ladder.get_corner(DOWN+LEFT) + vect = c1-c2 + return np.pi/2 - angle_of_vector(vect) + else: + return np.arctan(self.start_x/self.start_y) + + def get_ladder_length(self): + return np.linalg.norm([self.start_x, self.start_y]) diff --git a/mobject/mobject.py b/mobject/mobject.py index 7ad2b79e..09c23615 100644 --- a/mobject/mobject.py +++ b/mobject/mobject.py @@ -292,7 +292,7 @@ class Mobject(object): self.do_in_place(self.stretch, factor, dim) return self - def stretch_to_fit(self, length, dim, stretch = True): + def rescale_to_fit(self, length, dim, stretch = False): old_length = self.length_over_dim(dim) if old_length == 0: return self @@ -303,16 +303,16 @@ class Mobject(object): return self def stretch_to_fit_width(self, width): - return self.stretch_to_fit(width, 0) + return self.rescale_to_fit(width, 0, stretch = True) def stretch_to_fit_height(self, height): - return self.stretch_to_fit(height, 1) + return self.rescale_to_fit(height, 1, stretch = True) def scale_to_fit_width(self, width): - return self.stretch_to_fit(width, 0, stretch = False) + return self.rescale_to_fit(width, 0, stretch = False) def scale_to_fit_height(self, height): - return self.stretch_to_fit(height, 1, stretch = False) + return self.rescale_to_fit(height, 1, stretch = False) def space_out_submobjects(self, factor = 1.5, **kwargs): self.scale_in_place(factor) @@ -337,7 +337,7 @@ class Mobject(object): self.stretch_to_fit_width(mobject.get_width()) self.stretch_to_fit_height(mobject.get_height()) else: - self.stretch_to_fit( + self.rescale_to_fit( mobject.length_over_dim(dim_to_match), dim_to_match, stretch = False diff --git a/mobject/tex_mobject.py b/mobject/tex_mobject.py index 103dca69..e260ad0f 100644 --- a/mobject/tex_mobject.py +++ b/mobject/tex_mobject.py @@ -171,7 +171,7 @@ class Brace(TexMobject): mob.rotate(angle) def put_at_tip(self, mob, **kwargs): - mob.next_to(self, self.direction, **kwargs) + mob.next_to(self.get_tip(), self.get_direction(), **kwargs) return self def get_text(self, *text, **kwargs): @@ -179,6 +179,17 @@ class Brace(TexMobject): self.put_at_tip(text_mob, **kwargs) return text_mob + def get_tip(self): + # Very specific to the LaTeX representation + # of a brace, but it's the only way I can think + # of to get the tip regardless of orientation. + return self.submobjects[2].get_anchors()[7] + + def get_direction(self): + vect = self.get_tip() - self.get_center() + return vect/np.linalg.norm(vect) + + def tex_hash(expression, template_tex_file): diff --git a/topics/three_dimensions.py b/topics/three_dimensions.py index 305420e7..eaf8ae6b 100644 --- a/topics/three_dimensions.py +++ b/topics/three_dimensions.py @@ -24,7 +24,7 @@ class CameraWithPerspective(Camera): class ThreeDCamera(CameraWithPerspective): CONFIG = { - "sun_vect" : 3*UP+LEFT, + "sun_vect" : 5*UP+LEFT, "shading_factor" : 0.5, } def __init__(self, *args, **kwargs): @@ -66,18 +66,16 @@ class ThreeDCamera(CameraWithPerspective): def display_multiple_vectorized_mobjects(self, vmobjects): def z_cmp(*vmobs): - is_three_d = np.array([ - hasattr(vm, "part_of_three_d_mobject") - for vm in vmobs - ]) - if sum(is_three_d) == 2: - cmp_vect = self.get_unit_normal_vect(vmobs[0]) + #Compare to three dimensional mobjects based on their + #z value, otherwise don't compare. + is_three_d = [hasattr(vm, "part_of_3d_mobject") for vm in vmobs] + has_points = [vm.get_num_points() > 0 for vm in vmobs] + if all(is_three_d) and all(has_points): + cmp_vect = self.get_unit_normal_vect(vm) return cmp(*[ np.dot(vm.get_center(), cmp_vect) for vm in vmobs ]) - elif sum(is_three_d) == 1: - return 1 if is_three_d[0] else -1 else: return 0 Camera.display_multiple_vectorized_mobjects( @@ -95,7 +93,7 @@ class ThreeDMobject(VMobject): def __init__(self, **kwargs): VMobject.__init__(self, **kwargs) for submobject in self.submobject_family(): - submobject.part_of_three_d_mobject = True + submobject.part_of_3d_mobject = True class Cube(ThreeDMobject): CONFIG = { @@ -113,7 +111,14 @@ class Cube(ThreeDMobject): self.add(face) - +class Prism(Cube): + CONFIG = { + "dimensions" : [3, 2, 1] + } + def generate_points(self): + Cube.generate_points(self) + for dim, value in enumerate(self.dimensions): + self.rescale_to_fit(value, dim, stretch = True)