From a187a409fabf3dc0220d06e6ce6f27f705bda77d Mon Sep 17 00:00:00 2001 From: Grant Sanderson Date: Thu, 19 Aug 2021 09:00:53 -0700 Subject: [PATCH] Add various scenes that had been disorganized --- custom/backdrops.py | 14 ++ outside_videos/misc_thumbnails.py | 252 ++++++++++++++++++++++++++++++ outside_videos/mug_to_torus.py | 74 +++++++++ outside_videos/podcast.py | 84 ++++++++++ outside_videos/sudanese_band.py | 138 ++++++++++++++++ 5 files changed, 562 insertions(+) create mode 100644 outside_videos/misc_thumbnails.py create mode 100644 outside_videos/mug_to_torus.py create mode 100644 outside_videos/podcast.py create mode 100644 outside_videos/sudanese_band.py diff --git a/custom/backdrops.py b/custom/backdrops.py index 665e834..0c4a6f7 100644 --- a/custom/backdrops.py +++ b/custom/backdrops.py @@ -2,6 +2,7 @@ from manimlib.constants import WHITE from manimlib.constants import BLACK from manimlib.constants import DOWN from manimlib.constants import UP +from manimlib.constants import BLUE from manimlib.scene.scene import Scene from manimlib.mobject.frame import FullScreenRectangle from manimlib.mobject.frame import ScreenRectangle @@ -29,3 +30,16 @@ class Spotlight(Scene): animated_screen = AnimatedBoundary(screen) self.add(screen, animated_screen) self.wait(16) + + +class VideoWrapper(Scene): + def construct(self): + self.add(FullScreenRectangle()) + screen = ScreenRectangle() + screen.set_fill(BLACK, 1) + screen.set_stroke(BLUE, 0) + screen.set_height(6) + screen.to_edge(DOWN) + + self.add(screen, AnimatedBoundary(screen)) + self.wait(32) diff --git a/outside_videos/misc_thumbnails.py b/outside_videos/misc_thumbnails.py new file mode 100644 index 0000000..33ce70c --- /dev/null +++ b/outside_videos/misc_thumbnails.py @@ -0,0 +1,252 @@ +from manim_imports_ext import * + + +class LinalgThumbnail(ThreeDScene): + CONFIG = { + "camera_config": { + "anti_alias_width": 0, + } + } + + def construct(self): + grid = NumberPlane((-10, 10), (-10, 10), faded_line_ratio=1) + grid.set_stroke(width=6) + grid.faded_lines.set_stroke(width=1) + grid.apply_matrix([[3, 2], [1, -1]]) + # self.add(grid) + + frame = self.camera.frame + frame.reorient(0, 75) + + cube = Cube() + cube.set_color(BLUE) + cube.set_opacity(0.5) + + edges = VGroup() + for vect in [OUT, RIGHT, UP, LEFT, DOWN, IN]: + face = Square() + face.shift(OUT) + face.apply_matrix(z_to_vector(vect)) + edges.add(face) + for sm in edges.family_members_with_points(): + sm.flat_stroke = False + sm.joint_type = "round" + + edges.set_stroke(WHITE, 4) + edges.replace(cube) + edges.apply_depth_test() + cube = Group(cube, edges) + + cube2 = cube.copy().apply_matrix( + [[1, 0, 1], [0, 1, 0], [0, 0, 1]] + ) + # cube2.match_height(cube) + arrow = Vector(RIGHT) + arrow.rotate(PI / 2, RIGHT) + group = Group(cube, arrow, cube2) + group.arrange(RIGHT, buff=MED_LARGE_BUFF) + self.add(group) + + # kw ={ + # "thickness": 0.1, + # # "max_tip_length_to_length_ratio": 0.2, + # } + # self.add(Vector(grid.c2p(1, 0), fill_color=GREEN, **kw)) + # self.add(Vector(grid.c2p(0, 1), fill_color=RED, **kw)) + + # self.add(FullScreenFadeRectangle(fill_opacity=0.1)) + + +class CSThumbnail(Scene): + def construct(self): + self.add(self.get_background()) + + def get_background(self, n=12, k=50, zero_color=GREY_C, one_color=GREY_B): + choices = (Integer(0, color=zero_color), Integer(1, color=one_color)) + background = VGroup(*( + random.choice(choices).copy() + for x in range(n * k) + )) + background.arrange_in_grid(n, k) + background.set_height(FRAME_HEIGHT) + return background + + +class GroupThumbnail(ThreeDScene): + def construct(self): + cube = Cube() + cubes = Group(cube) + for axis in [[1, 1, 1], [1, 1, -1], [1, -1, 1], [1, -1, -1]]: + for angle in [60 * DEGREES]: + cubes.add(cube.copy().rotate(angle, axis)) + + cubes.rotate(95 * DEGREES, RIGHT) + cubes.rotate(30 * DEGREES, UP) + cubes.set_height(6) + cubes.center() + # cubes.set_y(-0.5) + cubes.set_color(BLUE_D) + cubes.set_shadow(0.65) + cubes.set_gloss(0.5) + self.add(cubes) + + +class BaselThumbnail(Scene): + def construct(self): + # Lake + lake_radius = 6 + lake_center = ORIGIN + + lake = Circle( + fill_color=BLUE, + fill_opacity=0.0, + radius=lake_radius, + stroke_color=BLUE_D, + stroke_width=3, + ) + lake.move_to(lake_center) + + R = 2 + light_template = VGroup() + rs = np.linspace(0, 1, 100) + for r1, r2 in zip(rs, rs[1:]): + dot1 = Dot(radius=R * r1).flip() + dot2 = Dot(radius=R * r2) + dot2.append_vectorized_mobject(dot1) + dot2.insert_n_curves(100) + dot2.set_fill(YELLOW, opacity=0.5 * (1 - r1)**2) + dot2.set_stroke(width=0) + light_template.add(dot2) + + houses = VGroup() + lights = VGroup() + for i in range(16): + theta = -TAU / 4 + (i + 0.5) * TAU / 16 + pos = lake_center + lake_radius * np.array([np.cos(theta), np.sin(theta), 0]) + house = Lighthouse() + house.set_fill(GREY_B) + house.set_stroke(width=0) + house.set_height(0.5) + house.move_to(pos) + light = light_template.copy() + light.move_to(pos) + houses.add(house) + lights.add(light) + + self.add(lake) + self.add(houses) + self.add(lights) + + # Equation + equation = Tex( + "1", "+", "{1 \\over 4}", "+", + "{1 \\over 9}", "+", "{1 \\over 16}", "+", + "{1 \\over 25}", "+", "\\cdots" + ) + equation.scale(1.8) + equation.move_to(2 * UP) + answer = Tex("= \\frac{\\pi^2}{6}", color=YELLOW) + answer.scale(3) + answer.move_to(1.25 * DOWN) + equation.add(answer) + + shadow = VGroup() + for w in np.linspace(20, 0, 50): + shadow.add(equation.copy().set_fill(opacity=0).set_stroke(BLACK, width=w, opacity=0.02)) + self.add(shadow) + self.add(equation) + + self.wait() + + +class Eola1Thumbnail(Scene): + def construct(self): + plane = NumberPlane( + x_range=(-2, 2), + y_range=(-5, 5), + ) + plane.set_width(FRAME_WIDTH / 3) + plane.to_edge(LEFT, buff=0) + plane.shift(1.5 * DOWN) + vect = Arrow( + plane.get_origin(), plane.c2p(1, 2), + buff=0, + thickness=0.1, + ) + vect.set_color(YELLOW) + self.add(plane, vect) + + coords = IntegerMatrix([[1], [2]]) + coords.set_height(3) + coords.set_color(TEAL) + coords.center() + coords.match_y(vect) + self.add(coords) + + symbol = Tex("\\vec{\\textbf{v} } \\in V") + symbol.set_color(BLUE) + symbol.set_width(FRAME_WIDTH / 3 - 1) + symbol.set_x(FRAME_WIDTH / 3) + symbol.match_y(vect) + self.add(symbol) + + lines = VGroup(*(Line(DOWN, UP) for x in range(2))) + lines.set_height(FRAME_HEIGHT) + lines.arrange(RIGHT, buff=FRAME_WIDTH / 3) + lines.set_stroke(GREY_A, 5) + self.add(lines) + + title = Text("Vectors", font_size=120) + title.to_edge(UP, buff=MED_SMALL_BUFF) + shadow = VGroup() + for w in np.linspace(50, 0, 100): + shadow.add(title.copy().set_fill(opacity=0).set_stroke(BLACK, width=w, opacity=0.01)) + self.add(shadow) + self.add(title) + + +def pendulum_vector_field_func(theta, omega, mu=0.3, g=9.8, L=3): + return [omega, -np.sqrt(g / L) * np.sin(theta) - mu * omega] + + +class ODEThumbnail(Scene): + def construct(self): + plane = NumberPlane() + field = VectorField( + pendulum_vector_field_func, plane, + step_multiple=0.5, + magnitude_range=(0, 5), + length_func=lambda norm: 0.35 * sigmoid(norm), + ) + field.set_opacity(0.75) + + # self.add(plane) + # self.add(field) + # return + + # Solution curve + + dt = 0.1 + t = 0 + total_time = 50 + + def func(point): + return plane.c2p(*pendulum_vector_field_func(*plane.p2c(point))) + + points = [plane.c2p(-4 * TAU / 4, 4.0)] + while t < total_time: + t += dt + points.append(points[-1] + dt * func(points[-1])) + + line = VMobject() + line.set_points_smoothly(points, true_smooth=True) + line.set_stroke([WHITE, WHITE, BLACK], width=[5, 1]) + # line.set_stroke((BLUE_C, BLUE_E), width=(10, 1)) + + line_fuzz = VGroup() + N = 50 + for width in np.linspace(50, 0, N): + line_fuzz.add(line.copy().set_stroke(BLACK, width=width, opacity=2 / N)) + + self.add(line_fuzz) + self.add(line) diff --git a/outside_videos/mug_to_torus.py b/outside_videos/mug_to_torus.py new file mode 100644 index 0000000..f88f687 --- /dev/null +++ b/outside_videos/mug_to_torus.py @@ -0,0 +1,74 @@ +from manim_imports_ext import * + + +class MugToTorus(ThreeDScene): + def construct(self): + frame = self.camera.frame + frame.reorient(-20, 60) + + R1, R2 = (2, 0.75) + + def torus_func(u, v): + v1 = np.array([-math.sin(u), 0, math.cos(u)]) + v2 = math.cos(v) * v1 + math.sin(v) * UP + return R1 * v1 + R2 * v2 + + def cylinder_func(u, v): + return (math.cos(v), math.sin(v), u) + + left_half_torus = ParametricSurface( + torus_func, + u_range=(-PI / 2, PI + PI / 2), + v_range=(0, TAU), + ) + right_half_torus = ParametricSurface( + torus_func, + u_range=(PI, TAU), + v_range=(0, TAU), + ) + cylinder = ParametricSurface( + cylinder_func, + u_range=(PI, TAU), + v_range=(0, TAU), + ) + cylinder.set_width(3) + cylinder.set_depth(5, stretch=True) + cylinder.move_to(ORIGIN, LEFT) + + disk = Disk3D(resolution=(2, 50)) + disk.match_width(cylinder) + disk.move_to(cylinder, IN) + disk.scale(1.001) + low_disk = disk.copy() + + for mob in (left_half_torus, right_half_torus, cylinder, low_disk, disk): + mob.set_color(GREY) + mob.set_gloss(0.7) + + left_half_torus.save_state() + left_half_torus.set_depth(3, about_point=ORIGIN) + + self.add(left_half_torus) + self.add(cylinder) + self.add(low_disk, disk) + self.add(frame) + + self.play(disk.animate.move_to(cylinder, OUT), run_time=2) + + for mob in (disk, low_disk): + mob.generate_target() + mob.target.rotate(90 * DEGREES, DOWN) + mob.target.set_depth(2 * R2) + disk.target.move_to(right_half_torus, OUT + LEFT) + low_disk.target.rotate(PI, UP) + low_disk.target.move_to(right_half_torus, IN + LEFT) + + self.play( + MoveToTarget(disk), + MoveToTarget(low_disk), + Transform(cylinder, right_half_torus), + Restore(left_half_torus, rate_func=squish_rate_func(smooth, 0, 0.75)), + frame.animate.reorient(-10, 80), + run_time=5, + ) + self.wait() diff --git a/outside_videos/podcast.py b/outside_videos/podcast.py new file mode 100644 index 0000000..ae4690e --- /dev/null +++ b/outside_videos/podcast.py @@ -0,0 +1,84 @@ +from manim_imports_ext import * + + +class PodcastIntro(Scene): + def construct(self): + tower = self.get_radio_tower() + + n_rings = 15 + min_radius = 0.5 + max_radius = 9 + max_width = 20 + min_width = 0 + max_opacity = 1 + min_opacity = 0 + rings = VGroup(*( + self.get_circle(radius=r) + for r in np.linspace(min_radius, max_radius, n_rings) + )) + tuples = zip( + rings, + np.linspace(max_width, min_width, n_rings), + np.linspace(max_opacity, min_opacity, n_rings), + ) + for ring, width, opacity in tuples: + ring.set_stroke(width=width, opacity=opacity) + ring.save_state() + ring.scale(0) + ring.set_stroke(WHITE, width=2) + + self.play( + ShowCreation(tower[0], lag_ratio=0.1), + run_time=3 + ) + self.play( + FadeIn(tower[1], scale=10, run_time=1), + LaggedStart( + *( + Restore(ring, rate_func=linear) + for ring in reversed(rings) + ), + run_time=4, + lag_ratio=0.08 + ) + ) + + def get_radio_tower(self): + base = VGroup() + line1 = Line(DL, UP) + line2 = Line(DR, UP) + base.add(line1, line2) + base.set_width(2, stretch=True) + base.set_height(4, stretch=True) + base.to_edge(DOWN, buff=1.5) + # alphas = [0, 0.2, 0.4, 0.6, 0.7, 0.8, 0.85] + values = np.array([0, *(1 / n for n in range(1, 11))]) + alphas = np.cumsum(values) + alphas /= alphas[-1] + for a1, a2 in zip(alphas, alphas[1:]): + base.add( + Line(line1.pfp(a1), line2.pfp(a2)), + Line(line2.pfp(a1), line1.pfp(a2)), + ) + base.set_stroke(GREY_A, width=2) + VGroup(line1, line2).set_stroke(width=4) + + dot = Dot(line1.get_end(), radius=0.125) + dot.set_color(WHITE) + dot.set_gloss(0.5) + tower = VGroup(base, dot) + tower.set_height(3) + tower.shift(-line1.get_end()) + tower.set_stroke(background=True) + + return tower + + def get_circle(self, center=ORIGIN, radius=1): + arc1 = Arc(PI, 3 * PI / 2) + arc2 = Arc(PI / 2, PI / 2) + arc1.set_color(BLUE) + arc2.set_color(GREY_BROWN) + circle = VGroup(arc1, arc2) + circle.set_width(2 * radius) + circle.move_to(center) + return circle diff --git a/outside_videos/sudanese_band.py b/outside_videos/sudanese_band.py new file mode 100644 index 0000000..bfe0a9f --- /dev/null +++ b/outside_videos/sudanese_band.py @@ -0,0 +1,138 @@ +from manim_imports_ext import * + + +def stereo_project_point(point, axis=0, r=1, max_norm=10000): + point = fdiv(point * r, point[axis] + r) + point[axis] = 0 + norm = get_norm(point) + if norm > max_norm: + point *= max_norm / norm + return point + + +def sudanese_band_func(eta, phi): + z1 = math.sin(eta) * np.exp(complex(0, phi)) + z2 = math.cos(eta) * np.exp(complex(0, phi / 2)) + r4_point = np.array([z1.real, z1.imag, z2.real, z2.imag]) + r4_point[:3] = rotate_vector(r4_point[:3], PI / 3, axis=[1, 1, 1]) + return stereo_project_point(r4_point, axis=0)[1:] + + +def mobius_strip_func(u, phi): + vect = rotate_vector(RIGHT, phi / 2, axis=UP) + vect = rotate_vector(vect, phi, axis=OUT) + ref_point = np.array([np.cos(phi), np.sin(phi), 0]) + return ref_point + 0.7 * (u - 0.5) * vect + + +def reversed_band(band_func): + return lambda x, phi: band_func(x, -phi) + + +def get_full_surface(band_func, x_range): + surface = ParametricSurface( + band_func, x_range, (0, TAU), + ) + surface.set_color(BLUE_D) + surface.set_shadow(0.5) + surface.add_updater(lambda m: m.sort_faces_back_to_front(DOWN)) + # surface = TexturedSurface(surface, "EarthTextureMap", "NightEarthTextureMap") + # surface = TexturedSurface(surface, "WaterColor") + # inv_surface = ParametricSurface( + # reversed_band(band_func), x_range[::-1], (0, TAU), + # ) + m1, m2 = meshes = VGroup( + SurfaceMesh(surface, normal_nudge=1e-3), + SurfaceMesh(surface, normal_nudge=-1e-3), + ) + bound = VGroup( + ParametricCurve(lambda t: band_func(x_range[0], t), (0, TAU)), + ParametricCurve(lambda t: band_func(x_range[1], t), (0, TAU)), + ) + bound.set_stroke(RED, 3) + bound.apply_depth_test() + meshes.set_stroke(WHITE, 0.5, 0.5) + return Group(surface, m1, m2, bound) + return Group(surface, bound) + + +def get_sudanese_band(circle_on_xy_plane=False): + s_band = get_full_surface( + sudanese_band_func, + (0, PI), + ) + angle = angle_of_vector(s_band[-1][0].get_start() - s_band[-1][1].get_start()) + s_band.rotate(PI / 2 - angle) + if circle_on_xy_plane: + s_band.rotate(90 * DEGREES, DOWN) + s_band.shift(-s_band[-1][0].get_start()) + return s_band + + +class SudaneseBand(ThreeDScene): + circle_on_xy_plane = True + + def construct(self): + frame = self.camera.frame + frame.reorient(-45, 70) + frame.add_updater( + lambda m, dt: m.increment_theta(2 * dt * DEGREES) + ) + self.add(frame) + + s_band = get_sudanese_band(self.circle_on_xy_plane) + m_band = get_full_surface(mobius_strip_func, (0, 1)) + for band in s_band, m_band: + band.set_height(6) + + # self.play(ShowCreation(m_band[0])) + # self.play( + # FadeIn(m_band[1]), + # FadeIn(m_band[2]), + # ShowCreation(m_band[3]), + # ) + self.add(m_band) + self.wait() + m_band.save_state() + self.play( + Transform(m_band, s_band), + run_time=8, + ) + # self.wait() + self.play(frame.animate.reorient(-30, 110), run_time=4) + # self.play(frame.animate.reorient(-30, 70), run_time=3) + self.wait(2) + frame.clear_updaters() + self.play( + m_band.animate.restore(), + frame.animate.reorient(-45, 70), + run_time=8, + ) + + # self.embed() + + +class SudaneseBandToKleinBottle(ThreeDScene): + def construct(self): + frame = self.camera.frame + frame.reorient(-70, 70) + # frame.add_updater( + # lambda m, dt: m.increment_theta(2 * dt * DEGREES) + # ) + # self.add(frame) + + s_band = get_sudanese_band() + s_band[1:3].set_opacity(0) + circ = s_band[-1] + s_band.shift(-circ.get_center()) + sb_copy = s_band.copy() + + self.add(s_band) + self.play( + Rotate(sb_copy, PI, axis=RIGHT, about_point=ORIGIN), + run_time=4 + ) + self.play(frame.animate.reorient(360 - 70, 70), run_time=15) + self.wait() + + self.embed()