from manimlib.imports import * from old_projects.clacks.question import Block from old_projects.clacks.question import Wall from old_projects.clacks.question import ClackFlashes class PositionPhaseSpaceScene(Scene): CONFIG = { "rescale_coordinates": True, "wall_x": -6, "wall_config": { "height": 1.6, "tick_spacing": 0.35, "tick_length": 0.2, }, "wall_height": 1.5, "floor_y": -3.5, "block1_config": { "mass": 10, "distance": 9, "velocity": -1, "width": 1.6, }, "block2_config": { "mass": 1, "distance": 4, }, "axes_config": { "x_min": -0.5, "x_max": 31, "y_min": -0.5, "y_max": 10.5, "x_axis_config": { "unit_size": 0.4, "tick_frequency": 2, }, "y_axis_config": { "unit_size": 0.4, "tick_frequency": 2, }, }, "axes_center": 5 * LEFT + 0.65 * DOWN, "ps_dot_config": { "fill_color": RED, "background_stroke_width": 1, "background_stroke_color": BLACK, "radius": 0.05, }, "ps_d2_label_vect": RIGHT, "ps_x_line_config": { "color": GREEN, "stroke_width": 2, }, "ps_y_line_config": { "color": RED, "stroke_width": 2, }, "clack_sound": "clack", "mirror_line_class": Line, "mirror_line_style": { "stroke_color": WHITE, "stroke_width": 1, }, "d1_eq_d2_line_color": MAROON_B, "d1_eq_d2_tex": "d1 = d2", "trajectory_style": { "stroke_color": YELLOW, "stroke_width": 2, }, "ps_velocity_vector_length": 0.75, "ps_velocity_vector_config": { "color": PINK, "rectangular_stem_width": 0.025, "tip_length": 0.15, }, "block_velocity_vector_length_multiple": 2, "block_velocity_vector_config": { "color": PINK, }, } def setup(self): self.total_sliding_time = 0 self.all_items = [ self.get_floor(), self.get_wall(), self.get_blocks(), self.get_axes(), self.get_phase_space_point(), self.get_phase_space_x_line(), self.get_phase_space_y_line(), self.get_phase_space_dot(), self.get_phase_space_d1_label(), self.get_phase_space_d2_label(), self.get_d1_brace(), self.get_d2_brace(), self.get_d1_label(), self.get_d2_label(), self.get_d1_eq_d2_line(), self.get_d1_eq_d2_label(), self.get_d2_eq_w2_line(), self.get_d2_eq_w2_label(), ] def get_floor_wall_corner(self): return self.wall_x * RIGHT + self.floor_y * UP def get_mass_ratio(self): return op.truediv( self.block1.mass, self.block2.mass, ) def d1_to_x(self, d1): if self.rescale_coordinates: d1 *= np.sqrt(self.block1.mass) return d1 def d2_to_y(self, d2): if self.rescale_coordinates: d2 *= np.sqrt(self.block2.mass) return d2 def ds_to_point(self, d1, d2): return self.axes.coords_to_point( self.d1_to_x(d1), self.d2_to_y(d2), ) def point_to_ds(self, point): x, y = self.axes.point_to_coords(point) if self.rescale_coordinates: x /= np.sqrt(self.block1.mass) y /= np.sqrt(self.block2.mass) return (x, y) def get_d1(self): return self.get_ds()[0] def get_d2(self): return self.get_ds()[1] def get_ds(self): return self.point_to_ds(self.ps_point.get_location()) # Relevant for sliding def tie_blocks_to_ps_point(self): def update_blocks(blocks): d1, d2 = self.point_to_ds(self.ps_point.get_location()) b1, b2 = blocks corner = self.get_floor_wall_corner() b1.move_to(corner + d1 * RIGHT, DL) b2.move_to(corner + d2 * RIGHT, DR) self.blocks.add_updater(update_blocks) def time_to_ds(self, time): # Deals in its own phase space, different # from the one displayed m1 = self.block1.mass m2 = self.block2.mass v1 = self.block1.velocity start_d1 = self.block1_config["distance"] start_d2 = self.block2_config["distance"] w2 = self.block2.width start_d2 += w2 ps_speed = np.sqrt(m1) * abs(v1) theta = np.arctan(np.sqrt(m2 / m1)) def ds_to_ps_point(d1, d2): return np.array([ d1 * np.sqrt(m1), d2 * np.sqrt(m2), 0 ]) def ps_point_to_ds(point): return ( point[0] / np.sqrt(m1), point[1] / np.sqrt(m2), ) ps_point = ds_to_ps_point(start_d1, start_d2) wedge_corner = ds_to_ps_point(w2, w2) ps_point -= wedge_corner # Pass into the mirror worlds ps_point += time * ps_speed * LEFT # Reflect back to the real world angle = angle_of_vector(ps_point) n = int(angle / theta) if n % 2 == 0: ps_point = rotate_vector(ps_point, -n * theta) else: ps_point = rotate_vector( ps_point, -(n + 1) * theta, ) ps_point[1] = abs(ps_point[1]) ps_point += wedge_corner return ps_point_to_ds(ps_point) def get_clack_data(self): # Copying from time_to_ds. Not great, but # maybe I'll factor things out properly later. m1 = self.block1.mass m2 = self.block2.mass v1 = self.block1.velocity w2 = self.block2.get_width() h2 = self.block2.get_height() start_d1, start_d2 = self.get_ds() ps_speed = np.sqrt(m1) * abs(v1) theta = np.arctan(np.sqrt(m2 / m1)) def ds_to_ps_point(d1, d2): return np.array([ d1 * np.sqrt(m1), d2 * np.sqrt(m2), 0 ]) def ps_point_to_ds(point): return ( point[0] / np.sqrt(m1), point[1] / np.sqrt(m2), ) ps_point = ds_to_ps_point(start_d1, start_d2) wedge_corner = ds_to_ps_point(w2, w2) ps_point -= wedge_corner y = ps_point[1] clack_data = [] for k in range(1, int(PI / theta) + 1): clack_ps_point = np.array([ y / np.tan(k * theta), y, 0 ]) time = get_norm(ps_point - clack_ps_point) / ps_speed reflected_point = rotate_vector( clack_ps_point, -2 * np.ceil((k - 1) / 2) * theta ) d1, d2 = ps_point_to_ds(reflected_point + wedge_corner) loc1 = self.get_floor_wall_corner() + h2 * UP / 2 + d2 * RIGHT if k % 2 == 0: loc1 += w2 * LEFT loc2 = self.ds_to_point(d1, d2) clack_data.append((time, loc1, loc2)) return clack_data def tie_ps_point_to_time_tracker(self): if not hasattr(self, "sliding_time_tracker"): self.sliding_time_tracker = self.get_time_tracker() def update_ps_point(p): time = self.sliding_time_tracker.get_value() ds = self.time_to_ds(time) p.move_to(self.ds_to_point(*ds)) self.ps_point.add_updater(update_ps_point) self.add(self.sliding_time_tracker, self.ps_point) def add_clack_flashes(self): if hasattr(self, "flash_anims"): self.add(*self.flash_anims) else: clack_data = self.get_clack_data() self.clack_times = [ time for (time, loc1, loc2) in clack_data ] self.block_flashes = ClackFlashes([ (loc1, time) for (time, loc1, loc2) in clack_data ]) self.ps_flashes = ClackFlashes([ (loc2, time) for (time, loc1, loc2) in clack_data ]) self.flash_anims = [self.block_flashes, self.ps_flashes] for anim in self.flash_anims: anim.get_time = self.sliding_time_tracker.get_value self.add(*self.flash_anims) def get_continually_building_trajectory(self): trajectory = VMobject() self.trajectory = trajectory trajectory.set_style(**self.trajectory_style) def get_point(): return np.array(self.ps_point.get_location()) points = [get_point(), get_point()] trajectory.set_points_as_corners(points) epsilon = 0.001 def update_trajectory(trajectory): new_point = get_point() p1, p2 = trajectory.get_anchors()[-2:] angle = angle_between_vectors( p2 - p1, new_point - p2, ) if angle > epsilon: points.append(new_point) else: points[-1] = new_point trajectory.set_points_as_corners(points) trajectory.add_updater(update_trajectory) return trajectory def begin_sliding(self, show_trajectory=True): self.tie_ps_point_to_time_tracker() self.add_clack_flashes() if show_trajectory: if hasattr(self, "trajectory"): self.trajectory.resume_updating() else: self.add(self.get_continually_building_trajectory()) def end_sliding(self): self.update_mobjects(dt=0) self.ps_point.clear_updaters() if hasattr(self, "sliding_time_tracker"): self.remove(self.sliding_time_tracker) if hasattr(self, "flash_anims"): self.remove(*self.flash_anims) if hasattr(self, "trajectory"): self.trajectory.suspend_updating() old_total_sliding_time = self.total_sliding_time new_total_sliding_time = self.sliding_time_tracker.get_value() self.total_sliding_time = new_total_sliding_time for time in self.clack_times: if old_total_sliding_time < time < new_total_sliding_time: offset = time - new_total_sliding_time self.add_sound( "clack", time_offset=offset, ) def slide(self, time, stop_condition=None): self.begin_sliding() self.wait(time, stop_condition) self.end_sliding() def slide_until(self, stop_condition, max_time=60): self.slide(max_time, stop_condition=stop_condition) def get_ps_point_change_anim(self, d1, d2, **added_kwargs): b1 = self.block1 ps_speed = np.sqrt(b1.mass) * abs(b1.velocity) curr_d1, curr_d2 = self.get_ds() distance = get_norm([curr_d1 - d1, curr_d2 - d2]) # Default kwargs = { "run_time": (distance / ps_speed), "rate_func": linear, } kwargs.update(added_kwargs) return ApplyMethod( self.ps_point.move_to, self.ds_to_point(d1, d2), **kwargs ) # Mobject getters def get_floor(self): floor = self.floor = Line( self.wall_x * RIGHT, FRAME_WIDTH * RIGHT / 2, stroke_color=WHITE, stroke_width=3, ) floor.move_to(self.get_floor_wall_corner(), LEFT) return floor def get_wall(self): wall = self.wall = Wall(**self.wall_config) wall.move_to(self.get_floor_wall_corner(), DR) return wall def get_blocks(self): blocks = self.blocks = VGroup() for n in [1, 2]: config = getattr(self, "block{}_config".format(n)) block = Block(**config) block.move_to(self.get_floor_wall_corner(), DL) block.shift(config["distance"] * RIGHT) block.label.move_to(block) block.label.set_fill(BLACK) block.label.set_stroke(WHITE, 1, background=True) self.blocks.add(block) self.block1, self.block2 = blocks return blocks def get_axes(self): axes = self.axes = Axes(**self.axes_config) axes.set_stroke(LIGHT_GREY, 2) axes.shift( self.axes_center - axes.coords_to_point(0, 0) ) axes.labels = self.get_axes_labels(axes) axes.add(axes.labels) axes.added_lines = self.get_added_axes_lines(axes) axes.add(axes.added_lines) return axes def get_added_axes_lines(self, axes): c2p = axes.coords_to_point x_mult = y_mult = 1 if self.rescale_coordinates: x_mult = np.sqrt(self.block1.mass) y_mult = np.sqrt(self.block2.mass) y_lines = VGroup(*[ Line( c2p(0, 0), c2p(0, axes.y_max * y_mult + 1), ).move_to(c2p(x, 0), DOWN) for x in np.arange(0, axes.x_max) * x_mult ]) x_lines = VGroup(*[ Line( c2p(0, 0), c2p(axes.x_max * x_mult, 0), ).move_to(c2p(0, y), LEFT) for y in np.arange(0, axes.y_max) * y_mult ]) line_groups = VGroup(x_lines, y_lines) for lines in line_groups: lines.set_stroke(BLUE, 1, 0.5) lines[1::2].set_stroke(width=0.5, opacity=0.25) return line_groups def get_axes_labels(self, axes, with_sqrts=None): if with_sqrts is None: with_sqrts = self.rescale_coordinates x_label = TexMobject("x", "=", "d_1") y_label = TexMobject("y", "=", "d_2") labels = VGroup(x_label, y_label) if with_sqrts: additions = map(TexMobject, [ "\\sqrt{m_1}", "\\sqrt{m_2}" ]) for label, addition in zip(labels, additions): addition.move_to(label[2], DL) label[2].next_to( addition, RIGHT, SMALL_BUFF, aligned_edge=DOWN ) addition[2:].set_color(BLUE) label.submobjects.insert(2, addition) x_label.next_to(axes.x_axis.get_right(), DL, MED_SMALL_BUFF) y_label.next_to(axes.y_axis.get_top(), DR, MED_SMALL_BUFF) for label in labels: label.shift_onto_screen() return labels def get_phase_space_point(self): ps_point = self.ps_point = VectorizedPoint() ps_point.move_to(self.ds_to_point( self.block1.distance, self.block2.distance + self.block2.width )) self.tie_blocks_to_ps_point() return ps_point def get_phase_space_x_line(self): def get_x_line(): origin = self.axes.coords_to_point(0, 0) point = self.ps_point.get_location() y_axis_point = np.array(origin) y_axis_point[1] = point[1] return DashedLine( y_axis_point, point, **self.ps_x_line_config, ) self.x_line = always_redraw(get_x_line) return self.x_line def get_phase_space_y_line(self): def get_y_line(): origin = self.axes.coords_to_point(0, 0) point = self.ps_point.get_location() x_axis_point = np.array(origin) x_axis_point[0] = point[0] return DashedLine( x_axis_point, point, **self.ps_y_line_config, ) self.y_line = always_redraw(get_y_line) return self.y_line def get_phase_space_dot(self): self.ps_dot = ps_dot = Dot(**self.ps_dot_config) ps_dot.add_updater(lambda m: m.move_to(self.ps_point)) return ps_dot def get_d_label(self, n, get_d): label = VGroup( TexMobject("d_{}".format(n), "="), DecimalNumber(), ) color = GREEN if n == 1 else RED label[0].set_color_by_tex("d_", color) label.scale(0.7) label.set_stroke(BLACK, 3, background=True) def update_value(label): lhs, rhs = label rhs.set_value(get_d()) rhs.next_to( lhs, RIGHT, SMALL_BUFF, aligned_edge=DOWN, ) label.add_updater(update_value) return label def get_phase_space_d_label(self, n, get_d, line, vect): label = self.get_d_label(n, get_d) label.add_updater( lambda m: m.next_to(line, vect, SMALL_BUFF) ) return label def get_phase_space_d1_label(self): self.ps_d1_label = self.get_phase_space_d_label( 1, self.get_d1, self.x_line, UP, ) return self.ps_d1_label def get_phase_space_d2_label(self): self.ps_d2_label = self.get_phase_space_d_label( 2, self.get_d2, self.y_line, self.ps_d2_label_vect, ) return self.ps_d2_label def get_d_brace(self, get_right_point): line = Line(LEFT, RIGHT).set_width(6) def get_brace(): right_point = get_right_point() left_point = np.array(right_point) left_point[0] = self.wall_x line.put_start_and_end_on(left_point, right_point) return Brace(line, UP, buff=SMALL_BUFF) brace = always_redraw(get_brace) return brace def get_d1_brace(self): self.d1_brace = self.get_d_brace( lambda: self.block1.get_corner(UL) ) return self.d1_brace def get_d2_brace(self): self.d2_brace = self.get_d_brace( lambda: self.block2.get_corner(UR) ) # self.flip_brace_nip() return self.d2_brace def flip_brace_nip(self, brace): nip_index = (len(brace) // 2) - 1 nip = brace[nip_index:nip_index + 2] rect = brace[nip_index - 1] center = rect.get_center() center[0] = nip.get_center()[0] nip.rotate(PI, about_point=center) def get_brace_d_label(self, n, get_d, brace, vect, buff): label = self.get_d_label(n, get_d) label.add_updater( lambda m: m.next_to(brace, vect, buff) ) return label def get_d1_label(self): self.d1_label = self.get_brace_d_label( 1, self.get_d1, self.d1_brace, UP, SMALL_BUFF, ) return self.d1_label def get_d2_label(self): self.d2_label = self.get_brace_d_label( 2, self.get_d2, self.d2_brace, UP, 0 ) return self.d2_label def get_d1_eq_d2_line(self): start = self.ds_to_point(0, 0) end = self.ds_to_point(15, 15) line = self.d1_eq_d2_line = self.mirror_line_class(start, end) line.set_style(**self.mirror_line_style) line.set_color(self.d1_eq_d2_line_color) return self.d1_eq_d2_line def get_d1_eq_d2_label(self): label = TexMobject(self.d1_eq_d2_tex) label.scale(0.75) line = self.d1_eq_d2_line point = interpolate( line.get_start(), line.get_end(), 0.7, ) label.next_to(point, DR, SMALL_BUFF) label.match_color(line) label.set_stroke(BLACK, 5, background=True) self.d1_eq_d2_label = label return label def get_d2_eq_w2_line(self): w2 = self.block2.width start = self.ds_to_point(0, w2) end = np.array(start) end[0] = FRAME_WIDTH / 2 self.d2_eq_w2_line = self.mirror_line_class(start, end) self.d2_eq_w2_line.set_style(**self.mirror_line_style) return self.d2_eq_w2_line def get_d2_eq_w2_label(self): label = TexMobject("d2 = \\text{block width}") label.scale(0.75) label.next_to(self.d2_eq_w2_line, UP, SMALL_BUFF) label.to_edge(RIGHT, buff=MED_SMALL_BUFF) self.d2_eq_w2_label = label return label def get_time_tracker(self, time=0): time_tracker = self.time_tracker = ValueTracker(time) time_tracker.add_updater( lambda m, dt: m.increment_value(dt) ) return time_tracker # Things associated with velocity def get_ps_velocity_vector(self, trajectory): vector = Vector( self.ps_velocity_vector_length * LEFT, **self.ps_velocity_vector_config, ) def update_vector(v): anchors = trajectory.get_anchors() index = len(anchors) - 2 vect = np.array(ORIGIN) while get_norm(vect) == 0 and index > 0: p0, p1 = anchors[index:index + 2] vect = p1 - p0 index -= 1 angle = angle_of_vector(vect) point = self.ps_point.get_location() v.set_angle(angle) v.shift(point - v.get_start()) vector.add_updater(update_vector) self.ps_velocity_vector = vector return vector def get_block_velocity_vectors(self, ps_vect): blocks = self.blocks vectors = VGroup(*[ Vector(LEFT, **self.block_velocity_vector_config) for x in range(2) ]) # TODO: Put in config vectors[0].set_color(GREEN) vectors[1].set_color(RED) def update_vectors(vs): v_2d = ps_vect.get_vector()[:2] v_2d *= self.block_velocity_vector_length_multiple for v, coord, block in zip(vs, v_2d, blocks): v.put_start_and_end_on(ORIGIN, coord * RIGHT) start = block.get_edge_center(v.get_vector()) v.shift(start) vectors.add_updater(update_vectors) self.block_velocity_vectors = vectors return vectors class IntroducePositionPhaseSpace(PositionPhaseSpaceScene): CONFIG = { "rescale_coordinates": False, "d1_eq_d2_tex": "x = y", "block1_config": { "velocity": 1.5, }, "slide_wait_time": 12, } def setup(self): super().setup() self.add( self.floor, self.wall, self.blocks, self.axes, ) def construct(self): self.show_coordinates() self.show_xy_line() self.let_process_play_out() self.show_w2_line() def show_coordinates(self): ps_point = self.ps_point axes = self.axes self.play(Write(axes.added_lines)) self.play(FadeInFromLarge(self.ps_dot, scale_factor=10)) self.play( ShowCreation(self.x_line), GrowFromPoint( self.d1_brace, self.d1_brace.get_left(), ), Indicate(axes.labels[0]), ) self.play( FadeInFromDown(self.ps_d1_label), FadeInFromDown(self.d1_label), ) self.play(ps_point.shift, 0.5 * LEFT) self.play(ps_point.shift, 0.5 * RIGHT) self.wait() self.play( ShowCreation(self.y_line), GrowFromPoint( self.d2_brace, self.d2_brace.get_left(), ), Indicate(axes.labels[1]), ) self.play( FadeInFromDown(self.ps_d2_label), FadeInFromDown(self.d2_label), ) self.play(ps_point.shift, 0.5 * UP) self.play(ps_point.shift, 0.5 * DOWN) self.wait() self.play(Rotating( ps_point, about_point=ps_point.get_location() + 0.5 * RIGHT, run_time=3, rate_func=smooth, )) self.wait() def show_xy_line(self): ps_point = self.ps_point ps_point.save_state() d1, d2 = self.point_to_ds(ps_point.get_location()) xy_line = self.d1_eq_d2_line xy_label = self.d1_eq_d2_label self.play( ShowCreation(xy_line), Write(xy_label), ) self.play( ps_point.move_to, self.ds_to_point(d2, d2), run_time=3 ) self.wait() for d in [3, 7]: self.play( ps_point.move_to, self.ds_to_point(d, d), run_time=2 ) self.wait() self.play(ps_point.restore) self.wait() def let_process_play_out(self): self.begin_sliding() sliding_trajectory = self.get_continually_building_trajectory() self.add(sliding_trajectory, self.ps_dot) self.wait(self.slide_wait_time) def show_w2_line(self): line = self.d2_eq_w2_line label = self.d2_eq_w2_label self.play(ShowCreation(line)) self.play(FadeInFromDown(label)) self.wait(self.slide_wait_time) self.end_sliding() self.wait(self.slide_wait_time) class SpecialShowPassingFlash(ShowPassingFlash): CONFIG = { "max_time_width": 0.1, } def get_bounds(self, alpha): tw = self.time_width max_tw = self.max_time_width upper = interpolate(0, 1 + max_tw, alpha) lower = upper - tw upper = min(upper, 1) lower = max(lower, 0) return (lower, upper) class EqualMassCase(PositionPhaseSpaceScene): CONFIG = { "block1_config": { "mass": 1, "width": 1, "velocity": 1.5, }, "rescale_coordinates": False, "d1_eq_d2_tex": "x = y", } def setup(self): super().setup() self.add( self.floor, self.wall, self.blocks, self.axes, self.d1_eq_d2_line, self.d1_eq_d2_label, self.d2_eq_w2_line, self.d2_eq_w2_label, self.ps_dot, self.x_line, self.y_line, self.ps_d1_label, self.ps_d2_label, ) def construct(self): self.show_same_mass() self.show_first_point() self.up_to_first_collision() self.up_to_second_collision() self.up_to_third_collision() self.fade_distance_indicators() self.show_beam_bouncing() def show_same_mass(self): blocks = self.blocks self.play(LaggedStartMap( Indicate, blocks, lag_ratio=0.8, run_time=1, )) def show_first_point(self): ps_dot = self.ps_dot ps_point = self.ps_point d1, d2 = self.get_ds() self.play(FocusOn(ps_dot)) self.play(ShowCreationThenFadeOut( Circle(color=RED).replace(ps_dot).scale(2), run_time=1 )) self.wait() self.play( ps_point.move_to, self.ds_to_point(d1 - 1, d2), rate_func=wiggle, run_time=3, ) # self.play(ps_point.move_to, self.ds_to_point(d1, d2)) self.wait() def up_to_first_collision(self): ps_point = self.ps_point d1, d2 = self.get_ds() block1 = self.block1 block2 = self.block2 xy_line = self.d1_eq_d2_line xy_line_label = self.d1_eq_d2_label block_arrow = Vector(LEFT, color=RED) block_arrow.block = block1 block_arrow.add_updater( lambda m: m.shift( m.block.get_center() - m.get_start() ) ) ps_arrow = Vector(LEFT, color=RED) ps_arrow.next_to(ps_point, DL, buff=SMALL_BUFF) block_labels = VGroup(block1.label, block2.label) block_label_copies = block_labels.copy() def update_bl_copies(bl_copies): for bc, b in zip(bl_copies, block_labels): bc.move_to(b) block_label_copies.add_updater(update_bl_copies) trajectory = self.get_continually_building_trajectory() self.add(block_arrow, ps_arrow, block_label_copies) self.play( GrowArrow(block_arrow), GrowArrow(ps_arrow), ) self.add(trajectory) self.play(self.get_ps_point_change_anim(d2, d2)) block_arrow.block = block2 ps_arrow.rotate(90 * DEGREES) ps_arrow.next_to(ps_point, DR, SMALL_BUFF) self.add_sound(self.clack_sound) self.play( Flash(ps_point), Flash(block1.get_left()), self.get_ps_point_change_anim(d2, d2 - 1) ) self.play( ShowPassingFlash( xy_line.copy().set_stroke(YELLOW, 3) ), Indicate(xy_line_label), ) trajectory.suspend_updating() self.wait() self.ps_arrow = ps_arrow self.block_arrow = block_arrow def up_to_second_collision(self): trajectory = self.trajectory ps_point = self.ps_point ps_arrow = self.ps_arrow block_arrow = self.block_arrow d1, d2 = self.get_ds() w2 = self.block2.get_width() trajectory.resume_updating() self.play(self.get_ps_point_change_anim(d1, w2)) block_arrow.rotate(PI) ps_arrow.rotate(PI) ps_arrow.next_to(ps_point, UR, SMALL_BUFF) self.add_sound(self.clack_sound) self.play( Flash(self.block2.get_left()), Flash(ps_point), self.get_ps_point_change_anim(d1, w2 + 1) ) trajectory.suspend_updating() self.wait() def up_to_third_collision(self): trajectory = self.trajectory ps_point = self.ps_point ps_arrow = self.ps_arrow block_arrow = self.block_arrow d1, d2 = self.get_ds() trajectory.resume_updating() self.play(self.get_ps_point_change_anim(d1, d1)) block_arrow.block = self.block1 ps_arrow.rotate(-90 * DEGREES) ps_arrow.next_to(ps_point, DR, SMALL_BUFF) self.add_sound(self.clack_sound) self.play( Flash(self.block2.get_left()), Flash(ps_point.get_location()), self.get_ps_point_change_anim(d1 + 10, d1) ) trajectory.suspend_updating() def fade_distance_indicators(self): trajectory = self.trajectory self.play( trajectory.set_stroke, {"width": 1}, *map(FadeOut, [ self.ps_arrow, self.block_arrow, self.x_line, self.y_line, self.ps_d1_label, self.ps_d2_label, ]) ) trajectory.clear_updaters() def show_beam_bouncing(self): d1, d2 = self.get_ds() d1 = int(d1) d2 = int(d2) # w2 = self.block2.get_width() ps_point = self.ps_point points = [] while d1 > d2: points.append(self.ds_to_point(d1, d2)) d1 -= 1 while d2 >= 1: points.append(self.ds_to_point(d1, d2)) d2 -= 1 points += list(reversed(points))[1:] trajectory = VMobject() trajectory.set_points_as_corners(points) flashes = [ SpecialShowPassingFlash( trajectory.copy().set_stroke(YELLOW, width=6 - n), time_width=(0.01 * n), max_time_width=0.05, remover=True ) for n in np.arange(0, 6, 0.25) ] flash_mob = flashes[0].mobject # Lol def update_ps_point_from_flas_mob(ps_point): if len(flash_mob.points) > 0: ps_point.move_to(flash_mob.points[-1]) else: ps_point.move_to(trajectory.points[0]) # Mirror words xy_line = self.d1_eq_d2_line w2_line = self.d2_eq_w2_line lines = VGroup(xy_line, w2_line) for line in lines: word = TextMobject("Mirror") word.next_to(ORIGIN, UP, SMALL_BUFF) word.rotate(line.get_angle(), about_point=ORIGIN) word.shift(line.get_center()) line.word = word for line in lines: line.set_stroke(LIGHT_GREY) line.set_sheen(1, LEFT) self.play( Write(line.word), line.set_sheen, 1, RIGHT, line.set_stroke, {"width": 2}, run_time=1, ) # TODO, clacks? for x in range(3): self.play( UpdateFromFunc( ps_point, update_ps_point_from_flas_mob, ), *flashes, run_time=3, rate_func=linear, ) self.wait() class FailedAngleRelation(PositionPhaseSpaceScene): CONFIG = { "block1_config": { "distance": 10, "velocity": -1.5, }, "block2_config": { "distance": 5, }, "rescale_coordinates": False, "trajectory_style": { "stroke_width": 2, } } def setup(self): super().setup() self.add( self.floor, self.wall, self.blocks, self.axes, self.ps_dot, self.x_line, self.y_line, self.d1_eq_d2_line, self.d1_eq_d2_label, self.d2_eq_w2_line, self.d2_eq_w2_label, ) def construct(self): self.show_first_collision() self.show_angles() def show_first_collision(self): self.slide_until(lambda: self.get_ds()[1] < 2) def show_angles(self): trajectory = self.trajectory arcs = self.get_arcs(trajectory) equation = self.get_word_equation() equation.next_to( trajectory.points[0], UR, MED_SMALL_BUFF, index_of_submobject_to_align=0, ) for arc in arcs: line = Line(ORIGIN, RIGHT) line.set_stroke(WHITE, 2) line.rotate(arc.start_angle) line.shift(arc.arc_center - line.get_start()) arc.line = line arc1, arc2 = arcs arc1.arrow = Arrow( equation[0].get_left(), arc1.get_right(), buff=SMALL_BUFF, color=WHITE, path_arc=0, ) arc2.arrow = Arrow( equation[2].get_corner(DL), arc2.get_left(), path_arc=-120 * DEGREES, buff=SMALL_BUFF, ) arc2.arrow.pointwise_become_partial(arc.arrow, 0, 0.95) arc1.word = equation[0] arc2.word = equation[1:] for arc in arcs: self.play( FadeInFrom(arc.word, LEFT), GrowArrow(arc.arrow, path_arc=arc.arrow.path_arc), ) self.play( ShowCreation(arc), arc.line.rotate, arc.angle, {"about_point": arc.line.get_start()}, UpdateFromAlphaFunc( arc.line, lambda m, a: m.set_stroke( opacity=(there_and_back(a)**0.5) ) ), ) # def get_arcs(self, trajectory): p0, p1, p2 = trajectory.get_anchors()[1:4] arc_config = { "stroke_color": WHITE, "stroke_width": 2, "radius": 0.5, "arc_center": p1, } arc1 = Arc( start_angle=0, angle=45 * DEGREES, **arc_config ) a2_start = angle_of_vector(DL) a2_angle = angle_between_vectors((p2 - p1), DL) arc2 = Arc( start_angle=a2_start, angle=a2_angle, **arc_config ) return VGroup(arc1, arc2) def get_word_equation(self): result = VGroup( TextMobject("Angle of incidence"), TexMobject("\\ne").rotate(90 * DEGREES), TextMobject("Angle of reflection") ) result.arrange(DOWN) result.set_stroke(BLACK, 5, background=True) return result class UnscaledPositionPhaseSpaceMass10(FailedAngleRelation): CONFIG = { "block1_config": { "mass": 10 }, "wait_time": 25, } def construct(self): self.slide(self.wait_time) class UnscaledPositionPhaseSpaceMass100(UnscaledPositionPhaseSpaceMass10): CONFIG = { "block1_config": { "mass": 100 } } class RescaleCoordinates(PositionPhaseSpaceScene, MovingCameraScene): CONFIG = { "rescale_coordinates": False, "ps_d2_label_vect": LEFT, "axes_center": 6 * LEFT + 0.65 * DOWN, "block1_config": {"distance": 7}, "wait_time": 30, } def setup(self): PositionPhaseSpaceScene.setup(self) MovingCameraScene.setup(self) self.add( self.floor, self.wall, self.blocks, self.axes, self.d1_eq_d2_line, self.d1_eq_d2_label, self.d2_eq_w2_line, self.ps_dot, self.x_line, self.y_line, self.ps_d1_label, self.ps_d2_label, self.d1_brace, self.d2_brace, self.d1_label, self.d2_label, ) def construct(self): self.show_rescaling() self.comment_on_ugliness() self.put_into_frame() def show_rescaling(self): axes = self.axes blocks = self.blocks to_stretch = VGroup( axes.added_lines, self.d1_eq_d2_line, self.ps_point, ) m1 = self.block1.mass new_axes_labels = self.get_axes_labels(axes, with_sqrts=True) # Show label def show_label(index, block, vect): self.play( ShowCreationThenFadeAround(axes.labels[index]) ) self.play( Transform( axes.labels[index], VGroup( *new_axes_labels[index][:2], new_axes_labels[index][3] ), ), GrowFromCenter(new_axes_labels[index][2]) ) group = VGroup( new_axes_labels[index][2][-2:].copy(), TexMobject("="), block.label.copy(), ) group.generate_target() group.target.arrange(RIGHT, buff=SMALL_BUFF) group.target.next_to(block, vect) group[1].scale(0) group[1].move_to(group.target[1]) group.target[2].set_fill(WHITE) group.target[2].set_stroke(width=0, background=True) self.play(MoveToTarget( group, rate_func=there_and_back_with_pause, run_time=3 )) self.remove(group) self.wait() show_label(0, self.block1, RIGHT) # The stretch blocks.suspend_updating() self.play( ApplyMethod( to_stretch.stretch, np.sqrt(m1), 0, {"about_point": axes.coords_to_point(0, 0)}, ), self.d1_eq_d2_label.shift, 6 * RIGHT, run_time=2, ) self.rescale_coordinates = True blocks.resume_updating() self.wait() # Show wiggle d1, d2 = self.get_ds() for new_d1 in [d1 - 2, d1]: self.play(self.get_ps_point_change_anim( new_d1, d2, rate_func=smooth, run_time=2, )) self.wait() # Change y-coord show_label(1, self.block2, LEFT) axes.remove(axes.labels) self.remove(axes.labels) axes.labels = new_axes_labels axes.add(axes.labels) self.add(axes) def comment_on_ugliness(self): axes = self.axes randy = Randolph(height=1.7) randy.flip() randy.next_to(self.d2_eq_w2_line, UP, buff=0) randy.to_edge(RIGHT) randy.change("sassy") randy.save_state() randy.fade(1) randy.change("plain") self.play(Restore(randy)) self.play( PiCreatureSays( randy, "Hideous!", bubble_kwargs={"height": 1.5, "width": 2}, target_mode="angry", look_at_arg=axes.labels[0] ) ) self.play(randy.look_at, axes.labels[1]) self.play(Blink(randy)) self.play( RemovePiCreatureBubble( randy, target_mode="confused" ) ) self.play(Blink(randy)) self.play(randy.look_at, axes.labels[0]) self.wait() self.play(FadeOut(randy)) def put_into_frame(self): rect = ScreenRectangle(height=FRAME_HEIGHT + 10) inner_rect = ScreenRectangle(height=FRAME_HEIGHT) rect.add_subpath(inner_rect.points[::-1]) rect.set_fill("#333333", opacity=1) frame = self.camera_frame self.begin_sliding() self.add(rect) self.play( frame.scale, 1.5, {"about_point": frame.get_bottom() + UP}, run_time=2, ) self.wait(self.wait_time) self.end_sliding() # def get_ds(self): if self.rescale_coordinates: return super().get_ds() return ( self.block1_config["distance"], self.block2_config["distance"], ) class RescaleCoordinatesMass16(RescaleCoordinates): CONFIG = { "block1_config": { "mass": 16, "distance": 10, }, "rescale_coordinates": True, "wait_time": 20, } def construct(self): self.put_into_frame() class RescaleCoordinatesMass64(RescaleCoordinatesMass16): CONFIG = { "block1_config": { "mass": 64, "distance": 6, }, "block2_config": {"distance": 3}, } class RescaleCoordinatesMass100(RescaleCoordinatesMass16): CONFIG = { "block1_config": { "mass": 100, "distance": 6, "velocity": 0.5, }, "block2_config": {"distance": 2}, "wait_time": 25, } class IntroduceVelocityVector(PositionPhaseSpaceScene, MovingCameraScene): CONFIG = { "zoom": True, "ps_x_line_config": { "color": WHITE, "stroke_width": 1, "stroke_opacity": 0.5, }, "ps_y_line_config": { "color": WHITE, "stroke_width": 1, "stroke_opacity": 0.5, }, "axes_center": 6 * LEFT + 0.65 * DOWN, "slide_time": 20, "new_vect_config": { "tip_length": 0.1, "rectangular_stem_width": 0.02, } } def setup(self): MovingCameraScene.setup(self) PositionPhaseSpaceScene.setup(self) self.add( self.floor, self.wall, self.blocks, self.axes, self.d1_eq_d2_line, self.d1_eq_d2_label, self.d2_eq_w2_line, self.x_line, self.y_line, self.ps_dot, ) def construct(self): self.show_velocity_vector() self.contrast_with_physical_velocities() self.zoom_in_on_vector() self.break_down_components() self.zoom_out() self.relate_x_dot_y_dot_to_v1_v2() self.calculate_magnitude() self.let_process_play_out() def show_velocity_vector(self): self.slide(2) ps_vect = self.get_ps_velocity_vector(self.trajectory) self.play(GrowArrow(ps_vect)) self.play(ShowCreationThenFadeAround(ps_vect)) self.wait() def contrast_with_physical_velocities(self): ps_vect = self.ps_velocity_vector block_vectors = self.get_block_velocity_vectors(ps_vect) self.play(LaggedStartMap(GrowArrow, block_vectors)) self.play(Rotating( ps_vect, angle=TAU, about_point=ps_vect.get_start(), run_time=5, rate_func=smooth, )) self.wait() self.slide_until(lambda: self.get_d2() < 2.5) def zoom_in_on_vector(self): if not self.zoom: self.wait(3) return ps_vect = self.ps_velocity_vector new_vect = Arrow( ps_vect.get_start(), ps_vect.get_end(), buff=0, **self.new_vect_config ) new_vect.match_style(ps_vect) camera_frame = self.camera_frame camera_frame.save_state() point = self.ps_point.get_location() point += MED_SMALL_BUFF * DOWN self.play( camera_frame.scale, 0.25, {"about_point": point}, Transform(ps_vect, new_vect), run_time=2, ) self.wait() def break_down_components(self): # Create vectors ps_vect = self.ps_velocity_vector start = ps_vect.get_start() end = ps_vect.get_end() ul_corner = np.array(start) dr_corner = np.array(start) ul_corner[0] = end[0] dr_corner[1] = end[1] x_vect = Arrow( start, ul_corner, buff=0, **self.new_vect_config ) y_vect = Arrow( start, dr_corner, buff=0, **self.new_vect_config ) x_vect.set_fill(GREEN, opacity=0.75) y_vect.set_fill(RED, opacity=0.75) vects = VGroup(x_vect, y_vect) # Projection lines x_line, y_line = [ DashedLine( ps_vect.get_end(), vect.get_end(), dash_length=0.01, color=vect.get_color(), ) for vect in (x_vect, y_vect) ] self.projection_lines = VGroup(x_line, y_line) # Vector labels dx_label = TexMobject("\\frac{dx}{dt}") dy_label = TexMobject("\\frac{dy}{dt}") labels = VGroup(dx_label, dy_label) for label, arrow, direction in zip(labels, vects, [UP, RIGHT]): label.scale(0.25) buff = 0.25 * SMALL_BUFF label.next_to(arrow, direction, buff=buff) label.set_stroke(BLACK, 3, background=True) if not self.zoom: self.grow_labels(labels) self.play( TransformFromCopy(ps_vect, x_vect), ShowCreation(x_line), ) self.play(FadeInFrom(dx_label, 0.25 * DOWN)) self.wait() self.play( TransformFromCopy(ps_vect, y_vect), ShowCreation(y_line), ) self.play(FadeInFrom(dy_label, 0.25 * LEFT)) self.wait() # Ask about dx_dt randy = Randolph() randy.match_height(dx_label) randy.next_to(dx_label, LEFT, SMALL_BUFF) randy.change("confused", dx_label) randy.save_state() randy.fade(1) randy.change("plain") self.play(Restore(randy)) self.play(WiggleOutThenIn(dx_label)) self.play(Blink(randy)) self.play(FadeOut(randy)) self.derivative_labels = labels self.component_vectors = vects def zoom_out(self): if not self.zoom: self.wait(2) return labels = self.derivative_labels self.play( Restore(self.camera_frame), ApplyFunction(self.grow_labels, labels), run_time=2 ) def relate_x_dot_y_dot_to_v1_v2(self): derivative_labels = self.derivative_labels.copy() dx_label, dy_label = derivative_labels x_label, y_label = self.axes.labels m_part = x_label[2] block1 = self.block1 block_vectors = self.block_velocity_vectors x_eq = x_label[1] dx_eq = TexMobject("=") dx_eq.next_to( x_eq, DOWN, buff=LARGE_BUFF, aligned_edge=RIGHT, ) for label in derivative_labels: label.generate_target() label.target.scale(1.5) dx_label.target.next_to(dx_eq, LEFT) dx_rhs = TexMobject("\\sqrt{m_1}", "v_1") dx_rhs[0][2:].set_color(BLUE) dx_rhs[1].set_color(GREEN) dx_rhs.next_to(dx_eq, RIGHT) alt_v1 = dx_rhs[1].copy() self.play(ShowCreationThenFadeAround(x_label)) self.play(MoveToTarget(dx_label)) self.play(TransformFromCopy(x_eq, dx_eq)) self.wait() self.play( VGroup(block1, m_part).shift, SMALL_BUFF * UP, rate_func=wiggle, ) self.wait() self.d1_brace.update() self.d1_label.update() self.play( ShowCreationThenFadeAround(x_label[3]), FadeIn(self.d1_brace), FadeIn(self.d1_label), ) self.wait() self.play( TransformFromCopy(x_label[3], dx_rhs[1]), TransformFromCopy(x_label[2], VGroup(dx_rhs[0])), ) block_vectors.suspend_updating() self.play(alt_v1.next_to, block_vectors[0], UP, SMALL_BUFF) self.play( Rotate( block_vectors[0], 10 * DEGREES, about_point=block_vectors[0].get_start(), rate_func=wiggle, run_time=1, ) ) self.play(FadeOut(alt_v1)) block_vectors.resume_updating() self.wait() # dy_label y_eq = y_label[1] dy_eq = TexMobject("=") dy_eq.next_to(y_eq, DOWN, LARGE_BUFF) dy_label.target.next_to(dy_eq, LEFT) dy_rhs = TexMobject("\\sqrt{m_2}", "v_2") dy_rhs[0][2:].set_color(BLUE) dy_rhs[1].set_color(RED) dy_rhs.next_to(dy_eq, RIGHT) VGroup(dy_label.target, dy_eq, dy_rhs).align_to(y_label, LEFT) alt_v2 = dy_rhs[1].copy() self.play(MoveToTarget(dy_label)) self.play( Write(dy_eq), Write(dy_rhs), ) self.play(alt_v2.next_to, block_vectors[1], UP, SMALL_BUFF) self.wait() self.play(FadeOut(alt_v2)) self.wait() self.derivative_equations = VGroup( VGroup(dx_label, dx_eq, dx_rhs), VGroup(dy_label, dy_eq, dy_rhs), ) def calculate_magnitude(self): corner_rect = Rectangle( stroke_color=WHITE, stroke_width=1, fill_color=BLACK, fill_opacity=1, height=2.5, width=8.5, ) corner_rect.to_corner(UR, buff=0) ps_vect = self.ps_velocity_vector big_ps_vect = Arrow( ps_vect.get_start(), ps_vect.get_end(), buff=0, ) big_ps_vect.match_style(ps_vect) big_ps_vect.scale(1.5) magnitude_bars = TexMobject("||", "||") magnitude_bars.match_height( big_ps_vect, stretch=True ) rhs_scale_val = 0.8 rhs = TexMobject( "=\\sqrt{" "\\left( dx/dt \\right)^2 + " "\\left( dy/dt \\right)^2" "}" ) rhs.scale(rhs_scale_val) group = VGroup( magnitude_bars[0], big_ps_vect, magnitude_bars[1], rhs ) group.arrange(RIGHT) group.next_to(corner_rect.get_corner(UL), DR) new_rhs = TexMobject( "=", "\\sqrt", "{m_1(v_1)^2 + m_2(v_2)^2}", tex_to_color_map={ "m_1": BLUE, "m_2": BLUE, "v_1": GREEN, "v_2": RED, } ) new_rhs.scale(rhs_scale_val) new_rhs.next_to(rhs, DOWN, aligned_edge=LEFT) final_rhs = TexMobject( "=", "\\sqrt{2(\\text{Kinetic energy})}" ) final_rhs.scale(rhs_scale_val) final_rhs.next_to(new_rhs, DOWN, aligned_edge=LEFT) self.play( FadeIn(corner_rect), TransformFromCopy(ps_vect, big_ps_vect) ) self.play(Write(magnitude_bars), Write(rhs[0])) self.wait() self.play(Write(rhs[1:])) self.wait() self.play(FadeInFrom(new_rhs, UP)) for equation in self.derivative_equations: self.play(ShowCreationThenFadeAround(equation)) self.wait() self.play(FadeInFrom(final_rhs, UP)) self.wait() def let_process_play_out(self): self.play(*map(FadeOut, [ self.projection_lines, self.derivative_labels, self.component_vectors, self.d1_brace, self.d1_label, ])) self.add(self.blocks, self.derivative_equations) self.blocks.resume_updating() self.slide(self.slide_time) # def grow_labels(self, labels): for label, vect in zip(labels, [DOWN, LEFT]): p = label.get_edge_center(vect) p += SMALL_BUFF * vect label.scale(2.5, about_point=p) return labels class IntroduceVelocityVectorWithoutZoom(IntroduceVelocityVector): CONFIG = { "zoom": False, } class ShowMomentumConservation(IntroduceVelocityVector): CONFIG = { "ps_velocity_vector_length": 1.25, "block_velocity_vector_length_multiple": 1, "block1_config": { "distance": 7, }, "axes_config": { "y_max": 11, }, "axes_center": 6.5 * LEFT + 1.2 * DOWN, "floor_y": -3.75, "wait_time": 15, } def construct(self): self.add_velocity_vectors() self.contrast_d1_d2_line_with_xy_line() self.rearrange_for_slope() self.up_to_first_collision() self.ask_what_next() self.show_conservation_of_momentum() self.show_rate_of_change_vector() self.show_sqrty_m_vector() self.show_dot_product() self.show_same_angles() self.show_horizontal_bounce() self.let_process_play_out() def add_velocity_vectors(self): self.slide(1) self.ps_vect = self.get_ps_velocity_vector(self.trajectory) self.block_vectors = self.get_block_velocity_vectors(self.ps_vect) self.play( GrowArrow(self.ps_vect), LaggedStartMap(GrowArrow, self.block_vectors, run_time=1), ) self.add(self.ps_vect, self.block_vectors) def contrast_d1_d2_line_with_xy_line(self): line = self.d1_eq_d2_line label = self.d1_eq_d2_label label.to_edge(RIGHT, buff=1.1) label.shift(0.65 * DOWN) xy_line = line.copy() xy_line.set_stroke(YELLOW, 3) xy_line.set_angle(45 * DEGREES) xy_label = TexMobject("x = y") xy_label.next_to(ORIGIN, DOWN, SMALL_BUFF) xy_label.rotate(45 * DEGREES, about_point=ORIGIN) xy_label.shift(xy_line.point_from_proportion(0.2)) self.xy_group = VGroup(xy_line, xy_label) self.play( ShowPassingFlash( line.copy().set_stroke(YELLOW, 4) ), Write(label), ) self.play( TransformFromCopy(line, xy_line, run_time=2) ) self.play(Write(xy_label)) self.wait() def rearrange_for_slope(self): eqs = VGroup(*reversed(self.axes.labels)).copy() y_eq, x_eq = eqs for eq in eqs: point = VectorizedPoint(eq[1].get_center()) eq.submobjects.insert(1, point) eq.submobjects[3] = eq[3].submobjects[0] eq.generate_target() eqs_targets = VGroup(*[eq.target for eq in eqs]) new_eqs = VGroup( TexMobject("{y", "\\over", "\\sqrt{m_2}}", "=", "d_2"), TexMobject("{x", "\\over", "\\sqrt{m_1}}", "=", "d_1"), ) new_x_eq, new_y_eq = new_eqs # Shuffle to align with x_eq and y_eq for new_eq in new_eqs: new_eq[2][2:].set_color(BLUE) new_eq.submobjects = [new_eq[i] for i in [0, 1, 3, 2, 4]] eqs_targets.arrange(DOWN, buff=LARGE_BUFF) eqs_targets.move_to(RIGHT).to_edge(UP) for eq, new_eq in zip(eqs_targets, new_eqs): new_eq.move_to(eq) self.play(LaggedStartMap(MoveToTarget, eqs, lag_ratio=0.7)) self.play(*[ Transform( eq, new_eq, path_arc=-90 * DEGREES, ) for eq, new_eq in zip(eqs, new_eqs) ]) self.wait() # Shuffle back for eq in eqs: eq[2][2:].set_color(BLUE) eq.submobjects = [eq[i] for i in [0, 1, 3, 2, 4]] # Set equal equals = TexMobject("=") for eq in eqs: eq.generate_target() VGroup( x_eq.target[4], x_eq.target[3], x_eq.target[:3], ).arrange(RIGHT) for p1, p2 in zip(x_eq, x_eq.target): p2.align_to(p1, DOWN) group = VGroup(y_eq.target, equals, x_eq.target) group.arrange(RIGHT) x_eq.target.align_to(y_eq.target, DOWN) equals.align_to(y_eq.target[3], DOWN) group.to_edge(UP, buff=MED_SMALL_BUFF) group.to_edge(RIGHT, buff=3) self.play( MoveToTarget(y_eq), MoveToTarget(x_eq, path_arc=90 * DEGREES), GrowFromCenter(equals) ) self.wait() # Simplify final_eq = TexMobject( "y", "=", "{\\sqrt{m_2}", "\\over", "\\sqrt{m_1}}", "x", ) for part in final_eq.get_parts_by_tex("sqrt"): part[2:].set_color(BLUE) m_part = final_eq[2:5] final_eq.next_to(group, DOWN) final_eq.shift(0.4 * UP) movers = VGroup( y_eq[0], equals.submobjects[0], y_eq[2], y_eq[1], x_eq[2], x_eq[0] ).copy() for mover, part in zip(movers, final_eq): mover.target = part self.play( LaggedStartMap( MoveToTarget, movers, path_arc=30 * DEGREES, lag_ratio=0.9 ), VGroup(x_eq, equals, y_eq).scale, 0.7, {"about_edge": UP}, ) self.remove(movers) self.add(final_eq) self.wait() # Highlight slope flash_line = self.d1_eq_d2_line.copy() flash_line.set_stroke(YELLOW, 5) self.play(ShowPassingFlash(flash_line)) self.play(ShowCreationThenFadeAround(m_part)) self.wait() # Tuck away slope in mind slope = m_part.copy() slope.generate_target() randy = Randolph(height=1.5) randy.next_to(final_eq, LEFT, MED_SMALL_BUFF) randy.align_to(self.d2_eq_w2_line, DOWN) bubble = ThoughtBubble( height=1.3, width=1.3, direction=RIGHT, ) bubble.pin_to(randy) slope.target.scale(0.5) slope.target.move_to(bubble.get_bubble_center()) randy.change("pondering", slope.target) randy.save_state() randy.change("plane") randy.fade(1) self.play( Restore(randy), Write(bubble), MoveToTarget(slope) ) self.play(Blink(randy)) self.thinking_on_slope_group = VGroup( randy, bubble, slope, ) self.to_fade = VGroup( eqs, equals, final_eq, self.thinking_on_slope_group, self.xy_group, ) def up_to_first_collision(self): self.begin_sliding() self.play(FadeOut(self.to_fade)) self.wait_until( lambda: abs(self.ps_velocity_vector.get_vector()[1]) > 0.01 ) self.end_sliding() self.wait(3 + 3 / 60) # Final cut reasons def ask_what_next(self): ps_vect = self.ps_velocity_vector question = TextMobject("What next?") question.set_background_stroke(color=BLACK, width=3) question.next_to(self.ps_point, UP) self.play(FadeInFrom(question, DOWN)) ps_vect.suspend_updating() angles = [0.75 * PI, -0.5 * PI, -0.25 * PI] for last_angle, angle in zip(np.cumsum([0] + angles), angles): # This is dumb and shouldn't be needed ps_vect.rotate(last_angle, about_point=ps_vect.get_start()) target = ps_vect.copy() target.rotate( angle, about_point=ps_vect.get_start() ) self.play( Transform( ps_vect, target, path_arc=angle ), ) ps_vect.resume_updating() self.whats_next_question = question def show_conservation_of_momentum(self): equation = self.get_momentum_equation() # Main equation self.play(FadeInFromDown(equation)) for part in equation[:2], equation[3:5]: outline = part.copy() outline.set_fill(opacity=0) outline.set_stroke(YELLOW, 3) self.play(ShowPassingFlash( outline, run_time=1.5 )) self.wait(0.5) # Dot product dot_product = self.get_dot_product() dot_product.next_to(equation, DOWN) sqrty_m_array = dot_product[0] x_label, y_label = self.axes.labels self.play( FadeOut(self.whats_next_question), FadeIn(dot_product), Transform( x_label[2].copy(), sqrty_m_array.get_entries()[0], remover=True, ), Transform( y_label[2].copy(), sqrty_m_array.get_entries()[1], remover=True, ), ) self.momentum_equation = equation self.dot_product = dot_product def show_rate_of_change_vector(self): ps_vect = self.ps_velocity_vector original_d_array = self.dot_product[2] d_array = original_d_array.copy() d_array.generate_target() d_array.scale(0.75) d_array.add_updater(lambda m: m.next_to( ps_vect.get_end(), np.sign(ps_vect.get_vector()[0]) * RIGHT, SMALL_BUFF )) self.play(TransformFromCopy(original_d_array, d_array)) self.wait() self.d_array = d_array def show_sqrty_m_vector(self): original_sqrty_m_array = self.dot_product[0] sqrty_m_vector = Arrow( self.ds_to_point(0, 0), # self.ds_to_point(1, 1), self.ds_to_point(2, 2), buff=0, color=YELLOW, ) sqrty_m_array = original_sqrty_m_array.deepcopy() sqrty_m_array.scale(0.75) sqrty_m_array.next_to( sqrty_m_vector.get_end(), UP, SMALL_BUFF ) rise = DashedLine( sqrty_m_vector.get_end(), sqrty_m_vector.get_corner(DR), color=RED, ) run = DashedLine( sqrty_m_vector.get_corner(DR), sqrty_m_vector.get_start(), color=GREEN, ) sqrty_m_array.add_background_to_entries() run_label, rise_label = sqrty_m_array.get_entries().copy() rise_label.next_to(rise, RIGHT, SMALL_BUFF) run_label.next_to(run, DOWN, SMALL_BUFF) randy_group = self.thinking_on_slope_group randy_group.align_to(self.d2_eq_w2_line, DOWN) randy_group.to_edge(LEFT) randy_group.shift(2 * RIGHT) self.play(GrowArrow(sqrty_m_vector)) self.play(TransformFromCopy( original_sqrty_m_array, sqrty_m_array, )) self.play(FadeIn(randy_group)) self.play( ShowCreation(rise), TransformFromCopy( sqrty_m_array.get_entries()[1], rise_label, ), ) self.add(run, randy_group) self.play( ShowCreation(run), TransformFromCopy( sqrty_m_array.get_entries()[0], run_label, ), ) self.wait() self.play(FadeOut(randy_group)) self.play(FadeOut(VGroup( rise, run, rise_label, run_label, ))) # move to ps_point point = self.ps_point.get_location() sqrty_m_vector.generate_target() sqrty_m_vector.target.shift( point - sqrty_m_vector.get_start() ) sqrty_m_array.generate_target() sqrty_m_array.target.next_to( sqrty_m_vector.target.get_end(), RIGHT, SMALL_BUFF, ) sqrty_m_array.target.shift(SMALL_BUFF * UP) self.play( MoveToTarget(sqrty_m_vector), MoveToTarget(sqrty_m_array), run_time=2 ) self.sqrty_m_vector = sqrty_m_vector self.sqrty_m_array = sqrty_m_array def show_dot_product(self): # Highlight arrays d_array = self.d_array big_d_array = self.dot_product[2] m_array = self.sqrty_m_array big_m_array = self.dot_product[0] self.play( ShowCreationThenFadeAround(big_d_array), ShowCreationThenFadeAround(d_array), ) self.play( ShowCreationThenFadeAround(big_m_array), ShowCreationThenFadeAround(m_array), ) # Before and after ps_vect = self.ps_velocity_vector theta = np.arctan(np.sqrt(self.block2.mass / self.block1.mass)) self.theta = theta ps_vect.suspend_updating() kwargs = {"about_point": ps_vect.get_start()} for x in range(2): for u in [-1, 1]: ps_vect.rotate(u * 2 * theta, **kwargs) self.update_mobjects(dt=0) self.wait() ps_vect.resume_updating() # Circle circle = Circle( radius=ps_vect.get_length(), arc_center=ps_vect.get_start(), color=RED, stroke_width=1, ) self.play( Rotating( ps_vect, about_point=ps_vect.get_start(), run_time=5, rate_func=lambda t: smooth(t, 3), ), FadeIn(circle), ) self.wait() self.ps_vect_circle = circle def show_same_angles(self): # circle = self.ps_vect_circle ps_vect = self.ps_velocity_vector ps_point = self.ps_point point = ps_point.get_center() ghost_ps_vect = ps_vect.copy() ghost_ps_vect.clear_updaters() ghost_ps_vect.set_fill(opacity=0.5) theta = self.theta ghost_ps_vect.rotate( -2 * theta, about_point=ghost_ps_vect.get_start(), ) arc1 = Arc( start_angle=PI, angle=theta, arc_center=point, radius=0.5, color=WHITE, ) arc2 = arc1.copy() arc2.rotate(theta, about_point=point) arc3 = arc1.copy() arc3.rotate(PI, about_point=point) arc1.set_color(BLUE) line_pair = VGroup(*[ Line(point, point + LEFT).rotate( angle, about_point=point ) for angle in [0, theta] ]) line_pair.set_stroke(width=0) ps_vect.suspend_updating() self.play( ShowCreation(arc1), FadeIn(ghost_ps_vect) ) self.wait() self.play( Rotate(ps_vect, 2 * theta, about_point=point) ) self.begin_sliding() ps_vect.resume_updating() self.play(GrowFromPoint(arc2, point)) self.wait(0.5) self.end_sliding() self.play( TransformFromCopy(arc1, arc3, path_arc=-PI), Rotate(line_pair, -PI, about_point=point), UpdateFromAlphaFunc( line_pair, lambda m, a: m.set_stroke( width=there_and_back(a)**0.5 ) ), ) # Show light beam along trajectory self.show_trajectory_beam_bounce() self.wait() # TODO: Add labels for angles? self.play(FadeOut(VGroup( self.ps_vect_circle, ghost_ps_vect, arc1 ))) def show_horizontal_bounce(self): self.slide_until( lambda: self.ps_velocity_vector.get_vector()[1] > 0 ) point = self.ps_point.get_location() theta = self.theta arc1 = Arc( start_angle=0, angle=2 * theta, radius=0.5, arc_center=point, ) arc2 = arc1.copy() arc2.rotate(PI - 2 * theta, about_point=point) arcs = VGroup(arc1, arc2) self.slide(0.5) self.play(LaggedStartMap( FadeInFromLarge, arcs, lag_ratio=0.75, )) self.show_trajectory_beam_bounce() def let_process_play_out(self): self.slide(self.wait_time) self.wait(10) # Just to be sure... # def show_trajectory_beam_bounce(self, n_times=2): # Show light beam along trajectory beam = self.trajectory.copy() beam.clear_updaters() beam.set_stroke(YELLOW, 3) for x in range(n_times): self.play(ShowPassingFlash( beam, run_time=2, rate_func=bezier([0, 0, 1, 1]) )) def get_momentum_equation(self): equation = TexMobject( "m_1", "v_1", "+", "m_2", "v_2", "=", "\\text{const.}", tex_to_color_map={ "m_1": BLUE, "m_2": BLUE, "v_1": RED, "v_2": RED, } ) equation.to_edge(UP, buff=MED_SMALL_BUFF) return equation def get_dot_product(self, m1="\\sqrt{m_1}", m2="\\sqrt{m_2}", d1="dx/dt", d2="dy/dt"): sqrty_m = Matrix([[m1], [m2]]) deriv_array = Matrix([[d1], [d2]]) for entry in sqrty_m.get_entries(): if "sqrt" in entry.get_tex_string(): entry[2:].set_color(BLUE) for matrix in sqrty_m, deriv_array: matrix.add_to_back(BackgroundRectangle(matrix)) matrix.get_brackets().scale(0.9) matrix.set_height(1.25) dot = TexMobject("\\cdot") rhs = TexMobject("= \\text{const.}") dot_product = VGroup( sqrty_m, dot, deriv_array, rhs ) dot_product.arrange(RIGHT, buff=SMALL_BUFF) return dot_product class JustTheProcessNew(PositionPhaseSpaceScene): CONFIG = { "block1_config": { "mass": 16, "velocity": -2 }, "wait_time": 10, } def setup(self): super().setup() self.add( self.floor, self.wall, self.blocks, self.axes, self.d1_eq_d2_line, self.d2_eq_w2_line, ) def construct(self): self.slide(self.wait_time)