diff --git a/eoc/chapter2.py b/eoc/chapter2.py index 1cfb5755..aa44822b 100644 --- a/eoc/chapter2.py +++ b/eoc/chapter2.py @@ -53,6 +53,7 @@ class Car(SVGMobject): randy.look(RIGHT) randy.move_to(self) randy.shift(0.07*self.height*(RIGHT+UP)) + self.randy = randy self.add_to_back(randy) orientation_line = Line(self.get_left(), self.get_right()) @@ -318,10 +319,11 @@ class IntroduceCar(Scene): CONFIG = { "should_transition_to_graph" : True, "show_distance" : True, + "point_A" : DOWN+4*LEFT, + "point_B" : DOWN+5*RIGHT, } def construct(self): - point_A = DOWN+4*LEFT - point_B = DOWN+5*RIGHT + point_A, point_B = self.point_A, self.point_B A = Dot(point_A) B = Dot(point_B) line = Line(point_A, point_B) @@ -411,6 +413,8 @@ class GraphCarTrajectory(GraphScene): "graph_origin" : 2.5*DOWN + 5*LEFT, "default_graph_colors" : [DISTANCE_COLOR, VELOCITY_COLOR], "default_derivative_color" : VELOCITY_COLOR, + "time_of_journey" : 10, + "care_movement_rate_func" : smooth, } def construct(self): self.setup_axes(animate = False) @@ -423,10 +427,11 @@ class GraphCarTrajectory(GraphScene): self.ask_critically_about_velocity() def graph_sigmoid_trajectory_function(self, **kwargs): - graph = self.graph_function( + graph = self.get_graph( lambda t : 100*smooth(t/10.), **kwargs ) + self.s_graph = graph return graph def introduce_graph(self, graph, origin): @@ -446,6 +451,9 @@ class GraphCarTrajectory(GraphScene): car = Car() car.rotate(np.pi/2) car.move_to(origin) + car_target = origin*RIGHT + graph.point_from_proportion(1)*UP + + self.add(car) self.play( ShowCreation( @@ -453,11 +461,12 @@ class GraphCarTrajectory(GraphScene): rate_func = None, ), MoveCar( - car, self.coords_to_point(0, 100), + car, car_target, + rate_func = self.care_movement_rate_func ), UpdateFromFunc(h_line, h_update), UpdateFromFunc(v_line, v_update), - run_time = 10, + run_time = self.time_of_journey, ) self.dither() self.play(*map(FadeOut, [h_line, v_line, car])) @@ -537,8 +546,12 @@ class GraphCarTrajectory(GraphScene): self.rect = rect def get_change_lines(self, curr_time, delta_t = 1): - p1 = self.input_to_graph_point(curr_time) - p2 = self.input_to_graph_point(curr_time+delta_t) + p1 = self.input_to_graph_point( + curr_time, self.s_graph + ) + p2 = self.input_to_graph_point( + curr_time+delta_t, self.s_graph + ) interim_point = p2[0]*RIGHT + p1[1]*UP delta_t_line = Line(p1, interim_point, color = TIME_COLOR) delta_s_line = Line(interim_point, p2, color = DISTANCE_COLOR) @@ -546,17 +559,16 @@ class GraphCarTrajectory(GraphScene): return VGroup(delta_t_line, delta_s_line, brace) def show_velocity_graph(self): - velocity_graph = self.get_derivative_graph() + velocity_graph = self.get_derivative_graph(self.s_graph) self.play(ShowCreation(velocity_graph)) def get_velocity_label(v_graph): - result = self.label_graph( + result = self.get_graph_label( v_graph, label = "v(t)", direction = UP+RIGHT, - proportion = 0.5, + x_val = 5, buff = SMALL_BUFF, - animate = False, ) self.remove(result) return result @@ -580,7 +592,7 @@ class GraphCarTrajectory(GraphScene): self.play(FadeOut(self.rect)) #Change distance and velocity graphs - self.graph.save_state() + self.s_graph.save_state() velocity_graph.save_state() label.save_state() def shallow_slope(t): @@ -598,10 +610,9 @@ class GraphCarTrajectory(GraphScene): double_smooth_graph_function, ] for graph_func in graph_funcs: - new_graph = self.graph_function( + new_graph = self.get_graph( graph_func, color = DISTANCE_COLOR, - is_main_graph = False ) self.remove(new_graph) new_velocity_graph = self.get_derivative_graph( @@ -609,13 +620,13 @@ class GraphCarTrajectory(GraphScene): ) new_velocity_label = get_velocity_label(new_velocity_graph) - self.play(Transform(self.graph, new_graph)) + self.play(Transform(self.s_graph, new_graph)) self.play( Transform(velocity_graph, new_velocity_graph), Transform(label, new_velocity_label), ) self.dither(2) - self.play(self.graph.restore) + self.play(self.s_graph.restore) self.play( velocity_graph.restore, label.restore, @@ -640,11 +651,12 @@ class ShowSpeedometer(IntroduceCar): "needle_height" : 0.8, "should_transition_to_graph" : False, "show_distance" : False, + "speedometer_title_text" : "Speedometer", } def setup(self): start_angle = -np.pi/6 end_angle = 7*np.pi/6 - speedomoeter = Arc( + speedometer = Arc( start_angle = start_angle, angle = end_angle-start_angle ) @@ -655,7 +667,7 @@ class ShowSpeedometer(IntroduceCar): label = TexMobject(str(10*index)) label.scale_to_fit_height(self.tick_length) label.shift((1+self.tick_length)*vect) - speedomoeter.add(tick, label) + speedometer.add(tick, label) needle = Polygon( LEFT, UP, RIGHT, @@ -666,46 +678,45 @@ class ShowSpeedometer(IntroduceCar): needle.stretch_to_fit_width(self.needle_width) needle.stretch_to_fit_height(self.needle_height) needle.rotate(end_angle-np.pi/2) - speedomoeter.add(needle) - speedomoeter.needle = needle + speedometer.add(needle) + speedometer.needle = needle - speedomoeter.center_offset = speedomoeter.get_center() + speedometer.center_offset = speedometer.get_center() - speedomoeter_title = TextMobject("Speedometer") - speedomoeter_title.to_corner(UP+LEFT) - speedomoeter.next_to(speedomoeter_title, DOWN) + speedometer_title = TextMobject(self.speedometer_title_text) + speedometer_title.to_corner(UP+LEFT) + speedometer.next_to(speedometer_title, DOWN) - self.speedomoeter = speedomoeter - self.speedomoeter_title = speedomoeter_title + self.speedometer = speedometer + self.speedometer_title = speedometer_title def introduce_added_mobjects(self): - speedomoeter = self.speedomoeter - speedomoeter_title = self.speedomoeter_title + speedometer = self.speedometer + speedometer_title = self.speedometer_title - speedomoeter.save_state() - speedomoeter.rotate(-np.pi/2, UP) - speedomoeter.scale_to_fit_height(self.car.get_height()/4) - speedomoeter.move_to(self.car) - speedomoeter.shift((self.car.get_width()/4)*RIGHT) + speedometer.save_state() + speedometer.rotate(-np.pi/2, UP) + speedometer.scale_to_fit_height(self.car.get_height()/4) + speedometer.move_to(self.car) + speedometer.shift((self.car.get_width()/4)*RIGHT) - self.play(speedomoeter.restore, run_time = 2) - self.play(Write(speedomoeter_title, run_time = 1)) + self.play(speedometer.restore, run_time = 2) + self.play(Write(speedometer_title, run_time = 1)) - def get_added_movement_anims(self): - needle = self.speedomoeter.needle - center = self.speedomoeter.get_center() - self.speedomoeter.center_offset - return [ - Rotating( - needle, - about_point = center, - radians = -np.pi/2, - run_time = 10, - rate_func = there_and_back - ) - ] + def get_added_movement_anims(self, **kwargs): + needle = self.speedometer.needle + center = self.speedometer.get_center() - self.speedometer.center_offset + default_kwargs = { + "about_point" : center, + "radians" : -np.pi/2, + "run_time" : 10, + "rate_func" : there_and_back, + } + default_kwargs.update(kwargs) + return [Rotating(needle, **default_kwargs)] # def construct(self): - # self.add(self.speedomoeter) + # self.add(self.speedometer) # self.play(*self.get_added_movement_anims()) class VelocityInAMomentMakesNoSense(Scene): @@ -985,8 +996,8 @@ class SidestepParadox(Scene): car = Car() car.shift(DOWN) show_speedometer = ShowSpeedometer(skip_animations = True) - speedomoeter = show_speedometer.speedomoeter - speedomoeter.next_to(car, UP) + speedometer = show_speedometer.speedometer + speedometer.next_to(car, UP) title = TextMobject( "Instantaneous", "rate of change" @@ -1001,7 +1012,7 @@ class SidestepParadox(Scene): new_words.highlight(TIME_COLOR) self.add(title, car) - self.play(Write(speedomoeter)) + self.play(Write(speedometer)) self.dither() self.play(Write(cross)) self.dither() @@ -1379,7 +1390,7 @@ class SecantLineToTangentLine(GraphCarTrajectory, DefineTrueDerivative): def get_ds_dt_group(self, dt, animate = False): points = [ - self.input_to_graph_point(time) + self.input_to_graph_point(time, self.graph) for time in self.curr_time, self.curr_time+dt ] dots = map(Dot, points) @@ -1440,16 +1451,12 @@ class SecantLineToTangentLine(GraphCarTrajectory, DefineTrueDerivative): return 50*smooth(t/5.) else: return 50*(1+smooth((t-5)/5.)) - graph = self.graph_function( - double_smooth_graph_function, - animate = False - ) - self.graph_label = self.label_graph( - graph, "s(t)", - proportion = 1, + self.graph = self.get_graph(double_smooth_graph_function) + self.graph_label = self.get_graph_label( + self.graph, "s(t)", + x_val = self.x_max, direction = DOWN+RIGHT, buff = SMALL_BUFF, - animate = False ) def add_derivative_definition(self, target_upper_left): @@ -1520,6 +1527,7 @@ class SecantLineToTangentLine(GraphCarTrajectory, DefineTrueDerivative): v_line = self.get_vertical_line_to_graph( self.curr_time, + self.graph, line_class = Line, line_kwargs = { "color" : MAROON_B, @@ -1529,7 +1537,7 @@ class SecantLineToTangentLine(GraphCarTrajectory, DefineTrueDerivative): def v_line_update(v_line): v_line.put_start_and_end_on( self.coords_to_point(self.curr_time, 0), - self.input_to_graph_point(self.curr_time), + self.input_to_graph_point(self.curr_time, self.graph), ) return v_line self.play(ShowCreation(v_line)) diff --git a/eoc/chapter8.py b/eoc/chapter8.py new file mode 100644 index 00000000..3cff96e0 --- /dev/null +++ b/eoc/chapter8.py @@ -0,0 +1,740 @@ +from helpers import * + +from mobject.tex_mobject import TexMobject +from mobject import Mobject +from mobject.image_mobject import ImageMobject +from mobject.vectorized_mobject import * + +from animation.animation import Animation +from animation.transform import * +from animation.simple_animations import * +from animation.playground import * +from topics.geometry import * +from topics.characters import * +from topics.functions import * +from topics.fractals import * +from topics.number_line import * +from topics.combinatorics import * +from topics.numerals import * +from topics.three_dimensions import * +from topics.objects import * +from scene import Scene +from scene.zoomed_scene import ZoomedScene +from scene.reconfigurable_scene import ReconfigurableScene +from camera import Camera +from mobject.svg_mobject import * +from mobject.tex_mobject import * + +from eoc.graph_scene import GraphScene +from eoc.chapter2 import Car, MoveCar, ShowSpeedometer, \ + IncrementNumber, GraphCarTrajectory, SecantLineToTangentLine, \ + VELOCITY_COLOR, TIME_COLOR, DISTANCE_COLOR +from topics.common_scenes import OpeningQuote, PatreonThanks + +def v_rate_func(t): + return 4*t - 4*(t**2) + +def s_rate_func(t): + return 3*(t**2) - 2*(t**3) + +def v_func(t): + return t*(8-t) + +def s_func(t): + return 4*t**2 - (t**3)/3. + + +class Chapter8OpeningQuote(OpeningQuote, PiCreatureScene): + CONFIG = { + "quote" : [ + " One should never try to prove anything that \\\\ is not ", + "almost obvious", ". " + ], + "quote_arg_separator" : "", + "highlighted_quote_terms" : { + "almost obvious" : BLUE, + }, + "author" : "Alexander Grothendieck" + } + def construct(self): + self.remove(self.pi_creature) + OpeningQuote.construct(self) + + words_copy = self.quote.get_part_by_tex("obvious").copy() + author = self.author + author.save_state() + formula = self.get_formula() + formula.next_to(author, DOWN, MED_LARGE_BUFF) + formula.to_edge(LEFT) + + self.revert_to_original_skipping_status() + self.play(FadeIn(self.pi_creature)) + self.play( + author.next_to, self.pi_creature.get_corner(UP+LEFT), UP, + self.pi_creature.change_mode, "raise_right_hand" + ) + self.dither(3) + self.play( + author.restore, + self.pi_creature.change_mode, "plain" + ) + self.play( + words_copy.next_to, self.pi_creature, + LEFT, MED_SMALL_BUFF, UP, + self.pi_creature.change_mode, "thinking" + ) + self.dither(2) + self.play( + Write(formula), + self.pi_creature.change_mode, "confused" + ) + self.dither() + + def get_formula(self): + result = TexMobject( + "{d(\\sin(\\theta)) \\over \\,", "d\\theta}", "=", + "\\lim_{", "h", " \\to 0}", + "{\\sin(\\theta+", "h", ") - \\sin(\\theta) \\over", " h}", "=", + "\\lim_{", "h", " \\to 0}", + "{\\big[ \\sin(\\theta)\\cos(", "h", ") + ", + "\\sin(", "h", ")\\cos(\\theta)\\big] - \\sin(\\theta) \\over", "h}", + "= \\dots" + ) + result.highlight_by_tex("h", GREEN, substring = False) + result.highlight_by_tex("d\\theta", GREEN) + + result.scale_to_fit_width(2*SPACE_WIDTH - 2*MED_SMALL_BUFF) + return result + +class ThisVideo(TeacherStudentsScene): + def construct(self): + series = VideoSeries() + series.to_edge(UP) + this_video = series[7] + this_video.save_state() + next_video = series[8] + + deriv, integral, v_t, dt, equals, v_T = formula = TexMobject( + "\\frac{d}{dT}", + "\\int_0^T", "v(t)", "\\,dt", + "=", "v(T)" + ) + formula.highlight_by_tex("v", VELOCITY_COLOR) + formula.next_to(self.teacher.get_corner(UP+LEFT), UP, MED_LARGE_BUFF) + + self.play(FadeIn(series, submobject_mode = "lagged_start")) + self.play( + this_video.shift, this_video.get_height()*DOWN/2, + this_video.highlight, YELLOW, + self.teacher.change_mode, "raise_right_hand", + ) + self.play(Write(VGroup(integral, v_t, dt))) + self.change_student_modes(*["erm"]*3) + self.dither() + self.play(Write(VGroup(deriv, equals, v_T)), ) + self.change_student_modes(*["confused"]*3) + self.dither(3) + self.play( + this_video.restore, + next_video.shift, next_video.get_height()*DOWN/2, + next_video.highlight, YELLOW, + integral[0].copy().next_to, next_video, DOWN, MED_LARGE_BUFF, + FadeOut(formula), + *it.chain(*[ + [pi.change_mode, "plain", pi.look_at, next_video] + for pi in self.pi_creatures + ]) + ) + self.dither(2) + +class InCarRestrictedView(ShowSpeedometer): + CONFIG = { + "speedometer_title_text" : "Your view", + } + def construct(self): + car = Car() + car.move_to(self.point_A) + self.car = car + car.randy.save_state() + Transform(car.randy, Randolph()).update(1) + car.randy.next_to(car, RIGHT, MED_LARGE_BUFF) + car.randy.look_at(car) + + window = car[1][6].copy() + window.is_subpath = False + window.set_fill(BLACK, opacity = 0.75) + window.set_stroke(width = 0) + + square = Square(stroke_color = WHITE) + square.replace(VGroup(self.speedometer, self.speedometer_title)) + square.scale_in_place(1.5) + square.pointwise_become_partial(square, 0.25, 0.75) + + time_label = TextMobject("Time (in seconds):", "0") + time_label.shift(2*UP) + + dots = VGroup(*map(Dot, [self.point_A, self.point_B])) + line = Line(*dots, buff = 0) + line.highlight(DISTANCE_COLOR) + brace = Brace(line, DOWN) + brace_text = brace.get_text("Distance traveled?") + + + #Sit in car + self.add(car) + self.play(Blink(car.randy)) + self.play(car.randy.restore, Animation(car)) + self.play(ShowCreation(window, run_time = 2)) + self.dither() + + #Show speedometer + self.introduce_added_mobjects() + self.play(ShowCreation(square)) + self.dither() + + #Travel + self.play(FadeIn(time_label)) + self.play( + MoveCar(car, self.point_B, rate_func = s_rate_func), + IncrementNumber(time_label[1], run_time = 8), + MaintainPositionRelativeTo(window, car), + *self.get_added_movement_anims( + rate_func = v_rate_func, + radians = -(16.0/70)*4*np.pi/3 + ), + run_time = 8 + ) + eight = TexMobject("8").move_to(time_label[1]) + self.play(Transform( + time_label[1], eight, + rate_func = squish_rate_func(smooth, 0, 0.5) + )) + self.dither() + + #Ask about distance + self.play(*map(ShowCreation, dots)) + self.play(ShowCreation(line)) + self.play( + GrowFromCenter(brace), + Write(brace_text) + ) + self.dither(2) + +class GraphDistanceVsTime(GraphCarTrajectory): + CONFIG = { + "y_min" : 0, + "y_max" : 100, + "y_axis_height" : 6, + "y_tick_frequency" : 10, + "y_labeled_nums" : range(10, 100, 10), + "y_axis_label" : "Distance (in meters)", + "x_min" : -1, + "x_max" : 9, + "x_axis_width" : 9, + "x_tick_frequency" : 1, + "x_leftmost_tick" : None, #Change if different from x_min + "x_labeled_nums" : range(1, 9), + "x_axis_label" : "$t$", + "time_of_journey" : 8, + "care_movement_rate_func" : s_rate_func, + "num_graph_anchor_points" : 100 + } + def construct(self): + self.setup_axes() + graph = self.get_graph( + s_func, + color = DISTANCE_COLOR, + x_min = 0, + x_max = 8, + ) + origin = self.coords_to_point(0, 0) + graph_label = self.get_graph_label( + graph, "s(t)", color = DISTANCE_COLOR + ) + self.introduce_graph(graph, origin) + +class PlotVelocity(GraphScene): + CONFIG = { + "x_min" : -1, + "x_max" : 9, + "x_axis_width" : 9, + "x_tick_frequency" : 1, + "x_labeled_nums" : range(1, 9), + "x_axis_label" : "$t$", + "y_min" : 0, + "y_max" : 25, + "y_axis_height" : 6, + "y_tick_frequency" : 5, + "y_labeled_nums" : range(5, 30, 5), + "y_axis_label" : "Velocity in $\\frac{\\text{meters}}{\\text{second}}$", + } + def construct(self): + self.setup_axes() + self.add_speedometer() + self.plot_points() + self.draw_curve() + + def add_speedometer(self): + speedometer = Speedometer() + speedometer.next_to(self.y_axis_label_mob, RIGHT, LARGE_BUFF) + speedometer.to_edge(UP) + + self.play(DrawBorderThenFill( + speedometer, + submobject_mode = "lagged_start", + rate_func = None, + )) + + self.speedometer = speedometer + + def plot_points(self): + times = range(0, 9) + points = [ + self.coords_to_point(t, v_func(t)) + for t in times + ] + dots = VGroup(*[Dot(p, radius = 0.07) for p in points]) + dots.highlight(VELOCITY_COLOR) + + pre_dots = VGroup() + dot_intro_anims = [] + + for time, dot in zip(times, dots): + pre_dot = dot.copy() + self.speedometer.move_needle_to_velocity(v_func(time)) + pre_dot.move_to(self.speedometer.get_needle_tip()) + pre_dot.set_fill(opacity = 0) + pre_dots.add(pre_dot) + dot_intro_anims += [ + ApplyMethod( + pre_dot.set_fill, YELLOW, 1, + run_time = 0.1, + ), + ReplacementTransform( + pre_dot, dot, + run_time = 0.9, + ) + ] + self.speedometer.move_needle_to_velocity(0) + + self.play( + Succession( + *dot_intro_anims, rate_func = None + ), + ApplyMethod( + self.speedometer.move_needle_to_velocity, + v_func(4), + rate_func = squish_rate_func( + lambda t : 1-v_rate_func(t), + 0, 0.95, + ) + ), + run_time = 5 + ) + self.dither() + + def draw_curve(self): + graph, label = self.get_v_graph_and_label() + + self.revert_to_original_skipping_status() + self.play(ShowCreation(graph, run_time = 3)) + self.play(Write(graph_label)) + self.dither() + + ## + + def get_v_graph_and_label(self): + graph = self.get_graph( + v_func, + x_min = 0, + x_max = 8, + color = VELOCITY_COLOR + ) + graph_label = TexMobject("v(t)", "=t(8-t)") + graph_label.highlight_by_tex("v(t)", VELOCITY_COLOR) + graph_label.next_to( + graph.point_from_proportion(7./8.), + UP+RIGHT + ) + self.v_graph = graph + self.v_graph_label = graph_label + return graph, graph_label + +class Chapter2Wrapper(Scene): + def construct(self): + title = TextMobject("Chapter 2: The paradox of the derivative") + title.to_edge(UP) + rect = Rectangle(width = 16, height = 9, color = WHITE) + rect.scale_to_fit_height(1.5*SPACE_HEIGHT) + rect.next_to(title, DOWN) + + self.add(title) + self.play(ShowCreation(rect)) + self.dither(3) + +class GivenDistanceWhatIsVelocity(GraphCarTrajectory): + def construct(self): + self.force_skipping() + self.setup_axes() + graph = self.graph_sigmoid_trajectory_function() + origin = self.coords_to_point(0, 0) + + self.introduce_graph(graph, origin) + self.comment_on_slope(graph, origin) + self.revert_to_original_skipping_status() + self.show_velocity_graph() + +class DerivativeOfDistance(SecantLineToTangentLine): + def construct(self): + self.setup_axes() + self.remove(self.y_axis_label_mob, self.x_axis_label_mob) + self.add_derivative_definition(self.y_axis_label_mob) + self.add_graph() + self.draw_axes() + self.show_tangent_line() + +class AskAboutAntiderivative(PlotVelocity): + def construct(self): + self.setup_axes() + self.add_v_graph() + self.write_s_formula() + self.write_antiderivative() + + + def add_v_graph(self): + graph, label = self.get_v_graph_and_label() + self.play(ShowCreation(graph)) + self.play(Write(label)) + + self.graph = graph + self.graph_label = label + + def write_s_formula(self): + ds_dt = TexMobject("ds", "\\over\\,", "dt") + ds_dt.highlight_by_tex("ds", DISTANCE_COLOR) + ds_dt.highlight_by_tex("dt", TIME_COLOR) + ds_dt.next_to(self.graph_label, UP, LARGE_BUFF) + + v_t = self.graph_label.get_part_by_tex("v(t)") + arrow = Arrow( + ds_dt.get_bottom(), v_t.get_top(), + color = WHITE, + ) + + self.play( + Write(ds_dt, run_time = 2), + ShowCreation(arrow) + ) + self.dither() + + def write_antiderivative(self): + randy = Randolph() + randy.to_corner(DOWN+LEFT) + randy.shift(2*RIGHT) + words = TexMobject( + "{d(", "???", ") \\over \\,", "dt}", "=", "t(8-t)" + ) + words.highlight_by_tex("t(8-t)", VELOCITY_COLOR) + words.highlight_by_tex("???", DISTANCE_COLOR) + words.highlight_by_tex("dt", TIME_COLOR) + words.scale(0.7) + + self.play(FadeIn(randy)) + self.play(PiCreatureSays( + randy, words, + target_mode = "confused", + bubble_kwargs = {"height" : 3, "width" : 4}, + )) + self.play(Blink(randy)) + self.dither() + +class Antiderivative(PiCreatureScene): + def construct(self): + functions = self.get_functions("t^2", "2t") + alt_functions = self.get_functions("???", "t(8-t)") + top_arc, bottom_arc = arcs = self.get_arcs(functions) + derivative = TextMobject("Derivative") + derivative.next_to(top_arc, UP) + antiderivative = TextMobject("``Antiderivative''") + antiderivative.next_to(bottom_arc, DOWN) + antiderivative.highlight(bottom_arc.get_color()) + group = VGroup(functions, arcs, derivative, antiderivative) + + self.add(functions, top_arc, derivative) + self.dither() + self.play( + ShowCreation(bottom_arc), + Write(antiderivative), + self.pi_creature.change_mode, "raise_right_hand" + ) + self.dither(2) + for pair in reversed(zip(functions, alt_functions)): + self.play( + Transform(*pair), + self.pi_creature.change_mode, "pondering" + ) + self.dither(2) + + self.pi_creature_says( + "But first!", + target_mode = "surprised", + look_at_arg = 50*OUT, + added_anims = [group.to_edge, LEFT], + run_time = 1, + ) + self.dither() + + + def get_functions(self, left_tex, right_tex): + left = TexMobject(left_tex) + left.shift(2*LEFT) + left.highlight(DISTANCE_COLOR) + right = TexMobject(right_tex) + right.shift(2*RIGHT) + right.highlight(VELOCITY_COLOR) + result = VGroup(left, right) + result.shift(UP) + return result + + def get_arcs(self, functions): + f1, f2 = functions + top_line = Line(f1.get_corner(UP+RIGHT), f2.get_corner(UP+LEFT)) + bottom_line = Line(f1.get_corner(DOWN+RIGHT), f2.get_corner(DOWN+LEFT)) + top_arc = Arc(start_angle = 5*np.pi/6, angle = -2*np.pi/3) + bottom_arc = top_arc.copy() + bottom_arc.rotate(np.pi) + arcs = VGroup(top_arc, bottom_arc) + arcs.scale_to_fit_width(top_line.get_width()) + for arc in arcs: + arc.add_tip() + top_arc.next_to(top_line, UP) + bottom_arc.next_to(bottom_line, DOWN) + bottom_arc.highlight(MAROON_B) + + return arcs + +class AreaUnderVGraph(PlotVelocity): + def construct(self): + self.setup_axes() + self.add(*self.get_v_graph_and_label()) + self.show_rects() + + def show_rects(self): + rect_list = self.get_riemann_rectangles_list( + self.v_graph, 7, + max_dx = 1.0, + x_min = 0, + x_max = 8, + ) + flat_graph = self.get_graph(lambda t : 0) + rects = self.get_riemann_rectangles( + flat_graph, x_min = 0, x_max = 8, dx = 1.0 + ) + + for new_rects in rect_list: + new_rects.set_fill(opacity = 0.8) + rects.align_submobjects(new_rects) + for alt_rect in rects[::2]: + alt_rect.set_fill(opacity = 0) + self.play(Transform( + rects, new_rects, + run_time = 2, + submobject_mode = "lagged_start" + )) + self.dither() + +class ConstantVelocityCar(Scene): + def construct(self): + car = Car() + car.scale(2) + car.move_to(3*LEFT + 3*DOWN) + + self.add(car) + self.dither() + self.play(MoveCar( + car, 6*RIGHT+3*DOWN, + run_time = 5, + rate_func = None, + )) + self.dither() + +class ConstantVelocityPlot(PlotVelocity): + CONFIG = { + "x_axis_label" : "Time" + } + def construct(self): + self.setup_axes() + self.x_axis_label_mob.shift(DOWN) + self.draw_graph() + self.show_product() + self.comment_on_area_wierdness() + self.note_units() + + + def draw_graph(self): + graph = self.get_graph( + lambda t : 10, + x_min = 0, + x_max = 8, + color = VELOCITY_COLOR + ) + + self.play(ShowCreation(graph, rate_func = None, run_time = 3)) + self.dither() + + self.graph = graph + + def show_product(self): + rect = Rectangle( + stroke_width = 0, + fill_color = DISTANCE_COLOR, + fill_opacity = 0.5 + ) + rect.replace( + VGroup(self.graph, VectorizedPoint(self.graph_origin)), + stretch = True + ) + + right_brace = Brace(rect, RIGHT) + top_brace = Brace(rect, UP) + v_label = right_brace.get_text( + "$10 \\frac{\\text{meters}}{\\text{second}}$", + ) + v_label.highlight(VELOCITY_COLOR) + t_label = top_brace.get_text( + "8 seconds" + ) + t_label.highlight(TIME_COLOR) + + s_label = TexMobject("10", "\\times", "8", "\\text{ meters}") + s_label.highlight_by_tex("10", VELOCITY_COLOR) + s_label.highlight_by_tex("8", TIME_COLOR) + s_label.move_to(rect) + + self.play( + GrowFromCenter(right_brace), + Write(v_label), + ) + self.play( + GrowFromCenter(top_brace), + Write(t_label), + ) + self.play( + FadeIn(rect), + Write(s_label), + Animation(self.graph) + ) + self.dither(2) + + self.area_rect = rect + self.s_label = s_label + + def comment_on_area_wierdness(self): + randy = Randolph() + randy.to_corner(DOWN+LEFT) + bubble = randy.get_bubble( + "Distance \\\\ is area?", + bubble_class = ThoughtBubble, + height = 3, + width = 4, + fill_opacity = 1, + ) + bubble.content.scale_in_place(0.8) + bubble.content.shift(SMALL_BUFF*UP) + VGroup(bubble[-1], bubble.content).shift(1.5*LEFT) + + self.play(FadeIn(randy)) + self.play(randy.change_mode, "pondering") + self.play( + self.area_rect.highlight, YELLOW, + *map(Animation, self.get_mobjects()), + rate_func = there_and_back + ) + self.play(Blink(randy)) + self.play( + randy.change_mode, "confused", + randy.look_at, randy.bubble, + ShowCreation(bubble), + Write(bubble.content), + ) + self.dither() + self.play(Blink(randy)) + self.dither() + self.play( + randy.change_mode, "pondering", + FadeOut(bubble), + FadeOut(bubble.content), + ) + + self.randy = randy + + def note_units(self): + x_line, y_line = lines = VGroup(*[ + axis.main_line.copy() + for axis in self.x_axis, self.y_axis + ]) + lines.highlight(TIME_COLOR) + + self.play(ShowCreation(x_line)) + self.play(Indicate(self.x_axis_label_mob)) + self.play(FadeOut(x_line)) + self.play( + ShowCreation(y_line), + self.randy.look_at, self.y_axis_label_mob + ) + self.play(Indicate(self.y_axis_label_mob)) + self.play(FadeOut(y_line)) + self.dither() + for direction in UP, DOWN: + self.play( + ApplyWave( + self.area_rect, + run_time = 1, + direction = direction, + amplitude = MED_SMALL_BUFF, + ), + *map(Animation, self.get_mobjects()) + [ + self.randy.look_at, self.area_rect + ] + ) + self.dither() + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eoc/graph_scene.py b/eoc/graph_scene.py index 6173b17f..a082c817 100644 --- a/eoc/graph_scene.py +++ b/eoc/graph_scene.py @@ -221,6 +221,24 @@ class GraphScene(Scene): rectangles.set_stroke(BLACK, width = stroke_width) return rectangles + def get_riemann_rectangles_list( + self, + graph, + n_iterations, + max_dx = 0.5, + power_base = 2, + **kwargs + ): + return [ + self.get_riemann_rectangles( + graph = graph, + dx = float(max_dx)/(power_base**n), + stroke_width = 1./(power_base**n), + **kwargs + ) + for n in range(n_iterations) + ] + def get_vertical_line_to_graph( self, x, graph, diff --git a/topics/characters.py b/topics/characters.py index 56d1b094..64d80e9c 100644 --- a/topics/characters.py +++ b/topics/characters.py @@ -395,7 +395,14 @@ class PiCreatureScene(Scene): self.get_pi_creatures() )) - def introduce_bubble(self, pi_creature, *content, **kwargs): + def introduce_bubble(self, *args, **kwargs): + if isinstance(args[0], PiCreature): + pi_creature = args[0] + content = args[1:] + else: + pi_creature = self.get_primary_pi_creature() + content = args + bubble_class = kwargs.pop("bubble_class", SpeechBubble) target_mode = kwargs.pop( "target_mode", @@ -443,18 +450,16 @@ class PiCreatureScene(Scene): self.play(*anims) - def pi_creature_says(self, pi_creature, *content, **kwargs): + def pi_creature_says(self, *args, **kwargs): self.introduce_bubble( - pi_creature, - *content, + *args, bubble_class = SpeechBubble, **kwargs ) - def pi_creature_thinks(self, pi_creature, *content, **kwargs): + def pi_creature_thinks(self, *args, **kwargs): self.introduce_bubble( - pi_creature, - *content, + *args, bubble_class = ThoughtBubble, **kwargs ) diff --git a/topics/common_scenes.py b/topics/common_scenes.py index d6682e78..73562798 100644 --- a/topics/common_scenes.py +++ b/topics/common_scenes.py @@ -24,12 +24,12 @@ class OpeningQuote(Scene): }, } def construct(self): - quote = self.get_quote() - author = self.get_author(quote) + self.quote = self.get_quote() + self.author = self.get_author(self.quote) - self.play(FadeIn(quote, **self.fade_in_kwargs)) + self.play(FadeIn(self.quote, **self.fade_in_kwargs)) self.dither(2) - self.play(Write(author, run_time = 3)) + self.play(Write(self.author, run_time = 3)) self.dither() def get_quote(self, max_width = 2*SPACE_WIDTH-1): diff --git a/topics/fractals.py b/topics/fractals.py index 6da22322..93825822 100644 --- a/topics/fractals.py +++ b/topics/fractals.py @@ -512,7 +512,6 @@ class UtahFillingCurve(SelfSimilarSpaceFillingCurve): "radius_scale_factor" : 2/(3*np.sqrt(3)), } - class FlowSnake(LindenmayerCurve): CONFIG = { "colors" : [YELLOW, GREEN], diff --git a/topics/objects.py b/topics/objects.py index 51037dcd..f75daa3c 100644 --- a/topics/objects.py +++ b/topics/objects.py @@ -1,17 +1,82 @@ from helpers import * from mobject import Mobject -from mobject.vectorized_mobject import VGroup +from mobject.vectorized_mobject import VGroup, VMobject from mobject.svg_mobject import SVGMobject -from mobject.tex_mobject import TextMobject +from mobject.tex_mobject import TextMobject, TexMobject from animation import Animation from animation.simple_animations import Rotating -from topics.geometry import Circle, Line, Rectangle, Square +from topics.geometry import Circle, Line, Rectangle, Square, Arc, Polygon from topics.three_dimensions import Cube +class Speedometer(VMobject): + CONFIG = { + "arc_angle" : 4*np.pi/3, + "num_ticks" : 8, + "tick_length" : 0.2, + "needle_width" : 0.1, + "needle_height" : 0.8, + "needle_color" : YELLOW, + } + def generate_points(self): + start_angle = np.pi/2 + self.arc_angle/2 + end_angle = np.pi/2 - self.arc_angle/2 + self.add(Arc( + start_angle = start_angle, + angle = -self.arc_angle + )) + tick_angle_range = np.linspace(start_angle, end_angle, self.num_ticks) + for index, angle in enumerate(tick_angle_range): + vect = rotate_vector(RIGHT, angle) + tick = Line((1-self.tick_length)*vect, vect) + label = TexMobject(str(10*index)) + label.scale_to_fit_height(self.tick_length) + label.shift((1+self.tick_length)*vect) + self.add(tick, label) + + needle = Polygon( + LEFT, UP, RIGHT, + stroke_width = 0, + fill_opacity = 1, + fill_color = self.needle_color + ) + needle.stretch_to_fit_width(self.needle_width) + needle.stretch_to_fit_height(self.needle_height) + needle.rotate(start_angle - np.pi/2) + self.add(needle) + self.needle = needle + + self.center_offset = self.get_center() + + def get_center(self): + result = VMobject.get_center(self) + if hasattr(self, "center_offset"): + result -= self.center_offset + return result + + def get_needle_tip(self): + return self.needle.get_anchors()[1] + + def get_needle_angle(self): + return angle_of_vector( + self.get_needle_tip() - self.get_center() + ) + + def rotate_needle(self, angle): + self.needle.rotate(angle, about_point = self.get_center()) + return self + + def move_needle_to_velocity(self, velocity): + max_velocity = 10*(self.num_ticks-1) + proportion = float(velocity) / max_velocity + start_angle = np.pi/2 + self.arc_angle/2 + target_angle = start_angle - self.arc_angle * proportion + self.rotate_needle(target_angle - self.get_needle_angle()) + return self + class PartyHat(SVGMobject): CONFIG = { "file_name" : "party_hat",