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 topics.complex_numbers 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 * def get_composite_rotation_angle_and_axis(angles, axes): matrices = [ rotation_matrix(angle = angle, axis = axis) for angle, axis in zip(angles, axes) ] result_matrix = reduce( np.dot, reversed(matrices), np.identity(3) ) eigenvalues, eigenvectors = np.linalg.eig(result_matrix) axis_index = np.argmin(np.abs(eigenvalues-1)) axis = np.round(eigenvectors[axis_index].astype('float')) possible_angles = [ np.angle(eigenvalues[(axis_index+i)%3]) for i in 1, 2 ] angle_index = np.argmin([ np.linalg.norm( rotation_matrix(angle = angle, axis = axis) -\ result_matrix ) for angle in possible_angles ]) angle = possible_angles[angle_index] return angle, axis class ConfettiSpiril(Animation): CONFIG = { "x_start" : 0, "spiril_radius" : 1, "num_spirils" : 4, "run_time" : 10, "rate_func" : None, } def __init__(self, mobject, **kwargs): digest_config(self, kwargs) mobject.next_to(self.x_start*RIGHT + SPACE_HEIGHT*UP, UP) self.total_vert_shift = \ 2*SPACE_HEIGHT + mobject.get_height() + 2*MED_SMALL_BUFF Animation.__init__(self, mobject, **kwargs) def update_submobject(self, submobject, starting_submobject, alpha): submobject.points = np.array(starting_submobject.points) def update_mobject(self, alpha): Animation.update_mobject(self, alpha) angle = alpha*self.num_spirils*2*np.pi vert_shift = alpha*self.total_vert_shift start_center = self.mobject.get_center() self.mobject.shift(self.spiril_radius*OUT) self.mobject.rotate(angle, axis = UP, about_point = start_center) self.mobject.shift(vert_shift*DOWN) class Anniversary(TeacherStudentsScene): CONFIG = { "num_confetti_squares" : 50, } def construct(self): self.celebrate() self.complain() def celebrate(self): title = TextMobject("2 year Anniversary!") title.scale(1.5) title.to_edge(UP) first_video = Rectangle( height = 2, width = 2*(16.0/9), stroke_color = WHITE, fill_color = "#111111", fill_opacity = 0.75, ) first_video.next_to(self.get_teacher(), UP+LEFT) first_video.shift(RIGHT) formula = TexMobject("e^{\\pi i} = -1") formula.move_to(first_video) first_video.add(formula) hats = self.get_party_hats() confetti_spirils = self.get_confetti_animations() self.play( Write(title, run_time = 2), *[ ApplyMethod(pi.change_mode, "hooray") for pi in self.get_pi_creatures() ] ) self.play( DrawBorderThenFill( hats, submobject_mode = "lagged_start", rate_func = None, run_time = 2, ), *confetti_spirils + [ Succession( Animation(pi, run_time = 2), ApplyMethod(pi.look, UP+LEFT), ApplyMethod(pi.look, UP+RIGHT), Animation(pi), ApplyMethod(pi.look_at, first_video), rate_func = None ) for pi in self.get_students() ] + [ Succession( Animation(self.get_teacher(), run_time = 2), Blink(self.get_teacher()), Animation(self.get_teacher(), run_time = 2), ApplyMethod(self.get_teacher().change_mode, "raise_right_hand"), rate_func = None ), DrawBorderThenFill( first_video, run_time = 10, rate_func = squish_rate_func(smooth, 0.5, 0.7) ) ] ) self.change_student_modes(*["confused"]*3) def complain(self): self.student_says( "Why were you \\\\ talking so fast?", student_index = 0, target_mode = "sassy", ) self.change_student_modes(*["sassy"]*3) self.play(self.get_teacher().change_mode, "shruggie") self.dither(2) def get_party_hats(self): hats = VGroup(*[ PartyHat( pi_creature = pi, height = 0.5*pi.get_height() ) for pi in self.get_pi_creatures() ]) max_angle = np.pi/6 for hat in hats: hat.rotate( random.random()*2*max_angle - max_angle, about_point = hat.get_bottom() ) return hats def get_confetti_animations(self): colors = [RED, YELLOW, GREEN, BLUE, PURPLE, RED] confetti_squares = [ Square( side_length = 0.2, stroke_width = 0, fill_opacity = 0.5, fill_color = random.choice(colors), ) for x in range(self.num_confetti_squares) ] confetti_spirils = [ ConfettiSpiril( square, x_start = 2*random.random()*SPACE_WIDTH - SPACE_WIDTH, rate_func = squish_rate_func(lambda t : t, a, a+0.5) ) for a, square in zip( np.linspace(0, 0.5, self.num_confetti_squares), confetti_squares ) ] return confetti_spirils class WatchingScreen(PiCreatureScene): CONFIG = { "screen_height" : 5.5 } def create_pi_creatures(self): randy = Randolph().to_corner(DOWN+LEFT) return VGroup(randy) def construct(self): screen = Rectangle(height = 9, width = 16) screen.scale_to_fit_height(self.screen_height) screen.to_corner(UP+RIGHT) self.add(screen) for mode in "erm", "pondering", "confused": self.dither() self.change_mode(mode) self.play(Animation(screen)) self.dither() class LetsStudyTheBasics(TeacherStudentsScene): def construct(self): self.teacher_says("Let's learn some \\\\ group theory.") self.change_student_modes(*["happy"]*3) self.dither(2) class QuickExplanation(ComplexTransformationScene): CONFIG = { "plane_config" : { "x_line_frequency" : 1, "y_line_frequency" : 1, "secondary_line_ratio" : 1, "space_unit_to_x_unit" : 1.5, "space_unit_to_y_unit" : 1.5, }, "background_fade_factor" : 0.2, "background_label_scale_val" : 0.7, "velocity_color" : RED, "position_color" : YELLOW, } def construct(self): # self.add_transformable_plane() self.add_equation() self.add_explanation() self.add_vectors() def add_equation(self): equation = TexMobject( "\\frac{d(e^{it})}{dt}", "=", "i", "e^{it}" ) equation[0].highlight(self.velocity_color) equation[-1].highlight(self.position_color) equation.add_background_rectangle() brace = Brace(equation, UP) equation.add(brace) brace_text = TextMobject( "Velocity vector", "is a", "$90^\\circ$ \\\\ rotation", "of", "position vector" ) brace_text[0].highlight(self.velocity_color) brace_text[-1].highlight(self.position_color) brace_text.add_background_rectangle() brace_text.scale(0.8) brace_text.to_corner(UP+LEFT, buff = MED_SMALL_BUFF) equation.next_to(brace_text, DOWN) self.add_foreground_mobjects(brace_text, equation) self.brace_text = brace_text def add_explanation(self): words = TextMobject(""" Only a walk around the unit circle at rate 1 satisfies both this property and e^0 = 1. """) words.scale(0.8) words.add_background_rectangle() words.to_corner(UP+RIGHT, buff = MED_SMALL_BUFF) arrow = Arrow(RIGHT, 1.5*LEFT, color = WHITE) arrow.to_edge(UP) self.add(words, arrow) def add_vectors(self): right = self.z_to_point(1) s_vector = Arrow( ORIGIN, right, tip_length = 0.2, buff = 0, color = self.position_color, ) v_vector = s_vector.copy().rotate(np.pi/2) v_vector.highlight(self.velocity_color) circle = Circle( radius = self.z_to_point(1)[0], color = self.position_color ) self.dither(2) self.play(ShowCreation(s_vector)) self.play(ReplacementTransform( s_vector.copy(), v_vector, path_arc = np.pi/2 )) self.dither() self.play(v_vector.shift, right) self.dither() self.vectors = VGroup(s_vector, v_vector) kwargs = { "rate_func" : None, "run_time" : 5, } rotation = Rotating(self.vectors, about_point = ORIGIN, **kwargs) self.play( ShowCreation(circle, **kwargs), rotation ) self.play(rotation) self.play(rotation) class SymmetriesOfSquare(ThreeDScene): CONFIG = { "square_config" : { "side_length" : 2, "stroke_width" : 0, "fill_color" : BLUE, "fill_opacity" : 0.75, }, "dashed_line_config" : {}, } def construct(self): self.add_title() self.ask_about_square_symmetry() self.talk_through_90_degree_rotation() self.talk_through_vertical_flip() self.confused_by_lack_of_labels() self.add_labels() self.show_full_group() self.show_top_actions() self.show_bottom_actions() self.name_dihedral_group() def add_title(self): title = TextMobject("Groups", "$\\leftrightarrow$", "Symmetry") title.to_edge(UP) for index in 0, 2: self.play(Write(title[index], run_time = 1)) self.play(GrowFromCenter(title[1])) self.dither() self.title = title def ask_about_square_symmetry(self): brace = Brace(self.title[-1]) q_marks = brace.get_text("???") self.square = Square(**self.square_config) self.play(DrawBorderThenFill(self.square)) self.play(GrowFromCenter(brace), Write(q_marks)) self.rotate_square() self.dither() for axis in UP, UP+RIGHT: self.flip_square(axis) self.dither() self.rotate_square(-np.pi) self.dither() self.play(*map(FadeOut, [brace, q_marks])) def talk_through_90_degree_rotation(self): arcs = self.get_rotation_arcs(self.square, np.pi/2) self.play(*map(ShowCreation, arcs)) self.dither() self.rotate_square(np.pi/2, run_time = 2) self.dither() self.play(FadeOut(arcs)) self.dither() def talk_through_vertical_flip(self): self.flip_square(UP, run_time = 2) self.dither() def confused_by_lack_of_labels(self): randy = Randolph(mode = "confused") randy.next_to(self.square, LEFT, buff = LARGE_BUFF) randy.to_edge(DOWN) self.play(FadeIn(randy)) for axis in OUT, RIGHT, UP: self.rotate_square( angle = np.pi, axis = axis, added_anims = [randy.look_at, self.square.points[0]] ) self.play(Blink(randy)) self.dither() self.randy = randy def add_labels(self): self.add_labels_and_dots(self.square) self.play( Write(self.square.labels), Write(self.square.dots), self.randy.change_mode, "happy", self.randy.look_at, self.square.labels[0] ) self.play(Blink(self.randy)) self.play(FadeOut(self.randy)) self.rotate_square(run_time = 2) self.dither() def show_full_group(self): new_title = TextMobject("Group", "of", "symmetries") new_title.move_to(self.title) all_squares = VGroup(*[ self.square.copy().scale(0.5) for x in range(8) ]) all_squares.arrange_submobjects(RIGHT, buff = LARGE_BUFF) top_squares = VGroup(*all_squares[:4]) bottom_squares = VGroup(*all_squares[4:]) bottom_squares.next_to(top_squares, DOWN, buff = LARGE_BUFF) all_squares.scale_to_fit_width(2*SPACE_WIDTH-2*LARGE_BUFF) all_squares.center() all_squares.to_edge(DOWN, buff = LARGE_BUFF) self.play(ReplacementTransform(self.square, all_squares[0])) self.play(ReplacementTransform(self.title, new_title)) self.title = new_title self.play(*[ ApplyMethod(mob.highlight, GREY) for mob in self.title[1:] ]) for square, angle in zip(all_squares[1:4], [np.pi/2, np.pi, -np.pi/2]): arcs = self.get_rotation_arcs(square, angle, MED_SMALL_BUFF) if angle == np.pi: arcs = VGroup(arcs[0], arcs[2]) self.play(*map(FadeIn, [square, arcs])) square.rotation_kwargs = {"angle" : angle} self.rotate_square(square = square, **square.rotation_kwargs) square.add(arcs) for square, axis in zip(bottom_squares, [RIGHT, RIGHT+UP, UP, UP+LEFT]): axis_line = self.get_axis_line(square, axis) self.play(FadeIn(square)) self.play(ShowCreation(axis_line)) square.rotation_kwargs = {"angle" : np.pi, "axis" : axis} self.rotate_square(square = square, **square.rotation_kwargs) square.add(axis_line) self.dither() self.all_squares = all_squares def show_top_actions(self): all_squares = self.all_squares self.play(Indicate(all_squares[0])) self.dither() rotations = [ Rotate( square, rate_func = lambda t : -there_and_back(t), run_time = 3, about_point = square.get_center(), **square.rotation_kwargs ) for square in all_squares[1:4] ] for rotation in rotations: for label in rotation.target_mobject.labels: label.rotate_in_place(-rotation.angle) self.play(*rotations) self.dither() def show_bottom_actions(self): for square in self.all_squares[4:]: self.rotate_square( square = square, rate_func = there_and_back, run_time = 2, **square.rotation_kwargs ) self.dither() def name_dihedral_group(self): new_title = TextMobject( "``Dihedral group'' of order 8" ) new_title.to_edge(UP) self.play(FadeOut(self.title)) self.play(FadeIn(new_title)) self.dither() ########## def rotate_square( self, angle = np.pi/2, axis = OUT, square = None, show_axis = False, added_anims = None, **kwargs ): if square is None: assert hasattr(self, "square") square = self.square added_anims = added_anims or [] rotation = Rotate( square, angle = angle, axis = axis, about_point = square.get_center(), **kwargs ) if hasattr(square, "labels"): for label in rotation.target_mobject.labels: label.rotate_in_place(-angle, axis) if show_axis: axis_line = self.get_axis_line(square, axis) self.play( ShowCreation(axis_line), Animation(square) ) self.play(rotation, *added_anims) if show_axis: self.play( FadeOut(axis_line), Animation(square) ) def flip_square(self, axis = UP, **kwargs): self.rotate_square( angle = np.pi, axis = axis, show_axis = True, **kwargs ) def get_rotation_arcs(self, square, angle, angle_buff = SMALL_BUFF): square_radius = np.linalg.norm( square.points[0] - square.get_center() ) arc = Arc( radius = square_radius + SMALL_BUFF, start_angle = np.pi/4 + np.sign(angle)*angle_buff, angle = angle - np.sign(angle)*2*angle_buff, color = YELLOW ) arc.add_tip() if abs(angle) < np.pi: angle_multiple_range = range(1, 4) else: angle_multiple_range = [2] arcs = VGroup(arc, *[ arc.copy().rotate(i*np.pi/2) for i in angle_multiple_range ]) arcs.move_to(square[0]) return arcs def get_axis_line(self, square, axis): axis_line = DashedLine(2*axis, -2*axis, **self.dashed_line_config) axis_line.replace(square, dim_to_match = np.argmax(np.abs(axis))) axis_line.scale_in_place(1.2) return axis_line def add_labels_and_dots(self, square): labels = VGroup() dots = VGroup() for tex, vertex in zip("ABCD", square.get_anchors()): label = TexMobject(tex) label.add_background_rectangle() label.next_to(vertex, vertex-square.get_center(), SMALL_BUFF) labels.add(label) dot = Dot(vertex, color = WHITE) dots.add(dot) square.add(labels, dots) square.labels = labels square.dots = dots class ManyGroupsAreInfinite(TeacherStudentsScene): def construct(self): self.teacher_says("Many groups are infinite") self.change_student_modes(*["pondering"]*3) self.dither(2) class CircleSymmetries(Scene): CONFIG = { "circle_radius" : 2, } def construct(self): self.add_circle_and_title() self.show_range_of_angles() self.associate_rotations_with_points() def add_circle_and_title(self): title = TextMobject("Group of rotations") title.to_edge(UP) circle = self.get_circle() self.play(Write(title), ShowCreation(circle, run_time = 2)) self.dither() angles = [ np.pi/2, -np.pi/3, 5*np.pi/6, 3*np.pi/2 + 0.1 ] angles.append(-sum(angles)) for angle in angles: self.play(Rotate(circle, angle = angle)) self.dither() self.circle = circle def show_range_of_angles(self): self.add_radial_line() arc_circle = self.get_arc_circle() theta = TexMobject("\\theta = ") theta_value = DecimalNumber(0.00) theta_value.next_to(theta, RIGHT) theta_group = VGroup(theta, theta_value) theta_group.next_to(arc_circle, UP) def theta_value_update(theta_value, alpha): new_theta_value = DecimalNumber(alpha*2*np.pi) new_theta_value.scale_to_fit_height(theta.get_height()) new_theta_value.next_to(theta, RIGHT) Transform(theta_value, new_theta_value).update(1) return new_theta_value self.play(FadeIn(theta_group)) for rate_func in smooth, lambda t : smooth(1-t): self.play( Rotate(self.circle, 2*np.pi-0.001), ShowCreation(arc_circle), UpdateFromAlphaFunc(theta_value, theta_value_update), run_time = 7, rate_func = rate_func ) self.dither() self.play(FadeOut(theta_group)) self.dither() def associate_rotations_with_points(self): zero_dot = Dot(self.circle.point_from_proportion(0)) zero_dot.highlight(RED) zero_arrow = Arrow(UP+RIGHT, ORIGIN) zero_arrow.highlight(zero_dot.get_color()) zero_arrow.next_to(zero_dot, UP+RIGHT, buff = SMALL_BUFF) self.play( ShowCreation(zero_arrow), DrawBorderThenFill(zero_dot) ) self.circle.add(zero_dot) self.dither() for alpha in 0.2, 0.6, 0.4, 0.8: point = self.circle.point_from_proportion(alpha) dot = Dot(point, color = YELLOW) vect = np.sign(point) arrow = Arrow(vect, ORIGIN) arrow.next_to(dot, vect, buff = SMALL_BUFF) arrow.highlight(dot.get_color()) angle = alpha*2*np.pi self.play( ShowCreation(arrow), DrawBorderThenFill(dot) ) self.play( Rotate(self.circle, angle, run_time = 2), Animation(dot) ) self.dither() self.play( Rotate(self.circle, -angle, run_time = 2), FadeOut(dot), FadeOut(arrow), ) self.dither() #### def get_circle(self): circle = Circle(color = MAROON_B, radius = self.circle_radius) circle.ticks = VGroup() for alpha in np.arange(0, 1, 1./8): point = circle.point_from_proportion(alpha) tick = Line((1 - 0.05)*point, (1 + 0.05)*point) circle.ticks.add(tick) circle.add(circle.ticks) return circle def add_radial_line(self): radius = Line( self.circle.get_center(), self.circle.point_from_proportion(0) ) static_radius = radius.copy().highlight(GREY) self.play(ShowCreation(radius)) self.add(static_radius, radius) self.circle.radius = radius self.circle.static_radius = static_radius self.circle.add(radius) def get_arc_circle(self): arc_radius = self.circle_radius/5.0 arc_circle = Circle( radius = arc_radius, color = WHITE ) return arc_circle class GroupOfCubeSymmetries(ThreeDScene): CONFIG = { "cube_opacity" : 0.9, "cube_colors" : [RED, RED, GREEN, GREEN, BLUE, BLUE] } def construct(self): title = TextMobject("Group of cube symmetries") title.to_edge(UP) self.add(title) cube = self.get_cube() face_centers = np.array([ face.get_center() for face in cube[::2] ]) angle_axis_pairs = [] for axis in face_centers: angle_axis_pairs.append((np.pi/2, axis)) for i in range(3): ones = np.ones(3) ones[i] = -1 axis = np.dot(ones, face_centers) angle_axis_pairs.append((2*np.pi/3, axis)) for angle, axis in angle_axis_pairs: self.play(Rotate( cube, angle = angle, axis = axis, run_time = 2 )) self.dither() def get_cube(self): cube = Cube(fill_opacity = self.cube_opacity) cube.gradient_highlight(*self.cube_colors) pose_matrix = self.get_pose_matrix() cube.apply_function( lambda p : np.dot(p, pose_matrix.T), maintain_smoothness = False ) return cube def get_pose_matrix(self): return np.dot( rotation_matrix(np.pi/8, UP), rotation_matrix(np.pi/24, RIGHT) ) class HowDoSymmetriesPlayWithEachOther(TeacherStudentsScene): def construct(self): self.teacher_says( "How do symmetries \\\\ play with each other?", target_mode = "hesitant", ) self.change_student_modes("pondering", "maybe", "confused") self.dither(2) class AddSquareSymmetries(SymmetriesOfSquare): def construct(self): square = Square(**self.square_config) square.flip(RIGHT) square.shift(DOWN) self.add_labels_and_dots(square) alt_square = square.copy() equals = TexMobject("=") equals.move_to(square) equation_square = Square(**self.square_config) equation = VGroup( equation_square, TexMobject("+"), equation_square.copy(), TexMobject("="), equation_square.copy(), ) equation[0].add(self.get_rotation_arcs( equation[0], np.pi/2, )) equation[2].add(self.get_axis_line(equation[4], UP)) equation[4].add(self.get_axis_line(equation[4], UP+RIGHT)) for mob in equation[::2]: mob.scale(0.5) equation.arrange_submobjects(RIGHT) equation.to_edge(UP) arcs = self.get_rotation_arcs(square, np.pi/2) self.add(square) self.play(FadeIn(arcs)) self.rotate_square( square = square, angle = np.pi/2, added_anims = map(FadeIn, equation[:2]) ) self.dither() self.play(FadeOut(arcs)) self.flip_square( square = square, axis = UP, added_anims = map(FadeIn, equation[2:4]) ) self.dither() alt_square.next_to(equals, RIGHT, buff = LARGE_BUFF) alt_square.save_state() alt_square.move_to(square) alt_square.set_fill(opacity = 0) self.play( square.next_to, equals, LEFT, LARGE_BUFF, alt_square.restore, Write(equals) ) self.flip_square( square = alt_square, axis = UP+RIGHT, added_anims = map(FadeIn, equation[4:]), ) self.dither(2) ## Reiterate composition self.rotate_square(square = square, angle = np.pi/2) self.flip_square(square = square, axis = UP) self.dither() self.flip_square(square = alt_square, axis = UP+RIGHT) self.dither() class AddCircleSymmetries(CircleSymmetries): def construct(self): circle = self.circle = self.get_circle() arc_circle = self.get_arc_circle() angles = [3*np.pi/2, 2*np.pi/3, np.pi/6] arcs = [ arc_circle.copy().scale(scalar) for scalar in [1, 1.2, 1.4] ] equation = TexMobject( "270^\\circ", "+", "120^\\circ", "=", "30^\\circ", ) equation.to_edge(UP) colors = [BLUE, YELLOW, GREEN] for color, arc, term in zip(colors, arcs, equation[::2]): arc.highlight(color) term.highlight(color) self.play(FadeIn(circle)) self.add_radial_line() alt_radius = circle.radius.copy() alt_radius.highlight(GREY) alt_circle = circle.copy() equals = TexMobject("=") equals.move_to(circle) def rotate(circle, angle, arc, terms): self.play( Rotate(circle, angle, in_place = True), ShowCreation( arc, rate_func = lambda t : (angle/(2*np.pi))*smooth(t) ), Write(VGroup(*terms)), run_time = 2, ) rotate(circle, angles[0], arcs[0], equation[:2]) self.dither() circle.add(alt_radius) rotate(circle, angles[1], arcs[1], equation[2:4]) self.play(FadeOut(alt_radius)) circle.remove(alt_radius) self.dither() circle.add(circle.static_radius) circle.add(*arcs[:2]) alt_static_radius = circle.static_radius.copy() alt_circle.add(alt_static_radius) alt_circle.next_to(equals, RIGHT, buff = LARGE_BUFF) alt_circle.save_state() alt_circle.move_to(circle) alt_circle.set_stroke(width = 0) self.play( circle.next_to, equals, LEFT, LARGE_BUFF, alt_circle.restore, Write(equals) ) arcs[2].shift(alt_circle.get_center()) alt_circle.remove(alt_static_radius) self.dither() rotate(alt_circle, angles[2], arcs[2], equation[4:]) self.dither() self.play( Rotate(arcs[1], angles[0], about_point = circle.get_center()) ) self.dither(2) for term, arc in zip(equation[::2], arcs): self.play(*[ ApplyMethod(mob.scale_in_place, 1.2, rate_func = there_and_back) for mob in term, arc ]) self.dither() class AddCubeSymmetries(GroupOfCubeSymmetries): CONFIG = { "angle_axis_pairs" : [ (np.pi/2, RIGHT), (np.pi/2, UP) ], "cube_opacity" : 0.5, "cube_colors" : [BLUE], } def construct(self): angle_axis_pairs = list(self.angle_axis_pairs) angle_axis_pairs.append( self.get_composition_angle_and_axis() ) self.pose_matrix = self.get_pose_matrix() cube = self.get_cube() equation = cube1, plus, cube2, equals, cube3 = VGroup( cube, TexMobject("+"), cube.copy(), TexMobject("="), cube.copy() ) equation.arrange_submobjects(RIGHT, buff = MED_LARGE_BUFF) equation.center() self.add(cube1) self.rotate_cube(cube1, *angle_axis_pairs[0]) cube_copy = cube1.copy() cube_copy.set_fill(opacity = 0) self.play( cube_copy.move_to, cube2, cube_copy.set_fill, None, self.cube_opacity, Write(plus) ) self.rotate_cube(cube_copy, *angle_axis_pairs[1]) self.play(Write(equals)) self.play(DrawBorderThenFill(cube3, run_time = 1)) self.rotate_cube(cube3, *angle_axis_pairs[2]) self.dither(2) times = TexMobject("\\times") times.scale(1.5) times.move_to(plus) times.highlight(RED) self.dither() self.play(ReplacementTransform(plus, times)) self.play(Indicate(times)) self.dither() for cube, (angle, axis) in zip([cube1, cube_copy, cube3], angle_axis_pairs): self.rotate_cube(cube, angle, axis, add_arrows = False) self.dither() # def get_pose_matrix(self): # return np.identity(3) def rotate_cube(self, cube, angle, axis, add_arrows = True): axis = np.dot(axis, self.pose_matrix.T) anims = [] if add_arrows: arrows = VGroup(*[ Arc( start_angle = np.pi/12+a, angle = 5*np.pi/6, color = YELLOW ).add_tip() for a in 0, np.pi ]) arrows.scale_to_fit_height(1.5*cube.get_height()) z_to_axis = z_to_vector(axis) arrows.apply_function( lambda p : np.dot(p, z_to_axis.T), maintain_smoothness = False ) arrows.move_to(cube) arrows.shift(-axis*cube.get_height()/2/np.linalg.norm(axis)) anims += map(ShowCreation, arrows) anims.append( Rotate(cube, axis = axis, angle = angle, in_place = True) ) self.play(*anims, run_time = 1.5) self.add(cube) def get_composition_angle_and_axis(self): return get_composite_rotation_angle_and_axis( *zip(*self.angle_axis_pairs) ) class DihedralGroupStructure(SymmetriesOfSquare): CONFIG = { "dashed_line_config" : { "dashed_segment_length" : 0.1 }, "filed_sum_scale_factor" : 0.45, "num_rows" : 5, } def construct(self): angle_axis_pairs = [ (np.pi/2, OUT), (np.pi, OUT), (-np.pi/2, OUT), # (np.pi, RIGHT), # (np.pi, UP+RIGHT), (np.pi, UP), (np.pi, UP+LEFT), ] pair_pairs = list(it.combinations(angle_axis_pairs, 2)) random.shuffle(pair_pairs) for pair_pair in pair_pairs[:4]: sum_expression = self.demonstrate_sum(pair_pair) self.file_away_sum(sum_expression) for pair_pair in pair_pairs[4:]: should_skip_animstions = self.skip_animations self.skip_animations = True sum_expression = self.demonstrate_sum(pair_pair) self.file_away_sum(sum_expression) self.skip_animations = should_skip_animstions self.play(FadeIn(sum_expression)) self.dither(3) def demonstrate_sum(self, angle_axis_pairs): angle_axis_pairs = list(angle_axis_pairs) + [ get_composite_rotation_angle_and_axis( *zip(*angle_axis_pairs) ) ] prototype_square = Square(**self.square_config) prototype_square.flip(RIGHT) self.add_labels_and_dots(prototype_square) prototype_square.scale(0.7) expression = s1, plus, s2, equals, s3 = VGroup( prototype_square, TexMobject("+"), prototype_square.copy(), TexMobject("="), prototype_square.copy() ) final_expression = VGroup() for square, (angle, axis) in zip([s1, s2, s3], angle_axis_pairs): if np.argmax(np.abs(axis)) == 2: ##Axis is in z direction square.action_illustration = self.get_rotation_arcs( square, angle ) else: square.action_illustration = self.get_axis_line( square, axis ) square.add(square.action_illustration) final_expression.add(square.action_illustration) square.rotation_kwargs = { "square" : square, "angle" : angle, "axis" : axis, } expression.arrange_submobjects() expression.to_edge(RIGHT) for square in s1, s2, s3: square.remove(square.action_illustration) self.play(FadeIn(s1)) self.play(*map(ShowCreation, s1.action_illustration)) self.rotate_square(**s1.rotation_kwargs) self.play( FadeIn(s2), Write(plus) ) self.play(*map(ShowCreation, s2.action_illustration)) self.rotate_square(**s2.rotation_kwargs) self.play( Write(equals), FadeIn(s3) ) self.play(*map(ShowCreation, s3.action_illustration)) self.rotate_square(**s3.rotation_kwargs) self.dither() final_expression.add(*expression) return final_expression def file_away_sum(self, sum_expression): if not hasattr(self, "num_sum_expressions"): self.num_sum_expressions = 0 target = sum_expression.copy() target.scale(self.filed_sum_scale_factor) y_index = self.num_sum_expressions%self.num_rows y_prop = float(y_index)/(self.num_rows-1) y = interpolate(SPACE_HEIGHT-LARGE_BUFF, -SPACE_HEIGHT+LARGE_BUFF, y_prop) x_index = self.num_sum_expressions//self.num_rows x_spacing = 2*SPACE_WIDTH/3 x = (x_index-1)*x_spacing target.move_to(x*RIGHT + y*UP) self.play(Transform(sum_expression, target)) self.dither() self.num_sum_expressions += 1 self.last_sum_expression = sum_expression