├── .gitignore ├── LICENSE ├── README.md ├── assets └── wheel.svg ├── asteroids.py ├── demo ├── Pendulums.gif ├── Rolling.gif └── Shatter.gif ├── pendulums.py ├── rolling.py └── shatter.py /.gitignore: -------------------------------------------------------------------------------- 1 | media/ 2 | test* 3 | __pycache__ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 sparshg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ManimCE Animations 2 | Animations made using [manim-ce](https://manim.community). 3 | 4 | [See anims on youtube](https://youtube.com/c/Radiium) 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /assets/wheel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | = 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /asteroids.py: -------------------------------------------------------------------------------- 1 | # This animation is for my asteroids training simulation 2 | # https://github.com/sparshg/asteroids-genetic 3 | 4 | from manim import * 5 | import networkx as nx 6 | 7 | from manim.mobject.graph import _determine_graph_layout 8 | from manim.opengl import * 9 | 10 | 11 | brain = [ 12 | [ 13 | -0.009693078, 14 | 0.09939107, 15 | -0.9073774, 16 | -0.57600784, 17 | -0.28215104, 18 | -1.1938038, 19 | 0.7848887, 20 | -0.36527398, 21 | -0.09820301, 22 | 0.35657594, 23 | 0.79946166, 24 | -0.49511307, 25 | 0.6358272, 26 | -0.22616765, 27 | -0.047070365, 28 | -0.2757149, 29 | -0.40778914, 30 | -0.63695973, 31 | 0.57385933, 32 | -0.8935812, 33 | -0.09729913, 34 | 0.22516525, 35 | 0.7222565, 36 | -0.0970098, 37 | 0.68187845, 38 | 0.2298346, 39 | 0.85350573, 40 | -0.6128707, 41 | -0.3337645, 42 | 0.036359284, 43 | ], 44 | [ 45 | 1.282332, 46 | 0.47750735, 47 | -0.6479809, 48 | -0.82879424, 49 | -0.31277427, 50 | 0.33294052, 51 | 0.76130587, 52 | -0.059874874, 53 | 0.09245835, 54 | 0.010224363, 55 | 0.15314236, 56 | -0.54873085, 57 | -0.53317577, 58 | 0.92193705, 59 | -0.257631, 60 | -0.0625739, 61 | 0.67577976, 62 | -0.246336, 63 | -0.015977442, 64 | -1.3203895, 65 | -0.12709938, 66 | -1.6439004, 67 | -0.7283819, 68 | -0.6083856, 69 | -0.19187418, 70 | 0.33709872, 71 | -0.17897944, 72 | -0.47784263, 73 | -0.06966477, 74 | -0.56339085, 75 | -0.56758046, 76 | 0.89432144, 77 | -0.2775634, 78 | 0.59100586, 79 | 0.43255445, 80 | -0.036131572, 81 | ], 82 | [ 83 | -0.32144403, 84 | 0.14525081, 85 | -0.7295116, 86 | -0.11274259, 87 | 1.1643897, 88 | -0.45038012, 89 | -0.09214835, 90 | 0.413071, 91 | -0.68017447, 92 | 1.1750991, 93 | -1.153951, 94 | -0.48123106, 95 | -0.8972865, 96 | 0.3888719, 97 | 0.2741283, 98 | -0.7187085, 99 | 0.4451282, 100 | -1.533827, 101 | -0.016380731, 102 | 1.1047536, 103 | 1.0084127, 104 | 1.501503, 105 | 0.85071784, 106 | 0.52249545, 107 | ], 108 | ] 109 | 110 | 111 | class CursorText(VGroup): 112 | def update(m, dt, text, text_obj): 113 | length = len(text.submobjects) 114 | target = m.get_center() 115 | for i in range(length): 116 | if text.submobjects[length - i - 1].get_fill_opacity() == 1: 117 | initial = m.get_center() 118 | target = ( 119 | m.align_to(text.submobjects[length - i - 1], RIGHT) 120 | .shift( 121 | RIGHT * (-0.2 if text_obj.erase else 0.25 if i != 0 else 0.1) 122 | ) 123 | .get_center() 124 | ) 125 | m.move_to(initial) 126 | break 127 | elif i == length - 1: 128 | initial = m.get_center() 129 | target = m.align_to(text.submobjects[0], LEFT).get_center() 130 | m.move_to(initial) 131 | if np.linalg.norm(target - m.get_center()) < 0.1: 132 | if text_obj.cursor_rising: 133 | m.set_opacity(m.stroke_opacity + (1.1 - m.stroke_opacity) * 4 * dt) 134 | if m.stroke_opacity >= 1: 135 | text_obj.cursor_rising = False 136 | else: 137 | m.set_opacity(m.stroke_opacity + (-0.1 - m.stroke_opacity) * 4 * dt) 138 | if m.stroke_opacity <= 0: 139 | text_obj.cursor_rising = True 140 | else: 141 | text_obj.show_cursor() 142 | m.shift((target - m.get_center()) * 18 * dt) 143 | 144 | def __init__(self, scene, text, font_size=24, sc=1, *args, **kwargs): 145 | self.text = ( 146 | Text( 147 | text, 148 | font_size=font_size, 149 | font="monocraft", 150 | color="#DDDDDD", 151 | *args, 152 | **kwargs, 153 | ) 154 | .set_opacity(0) 155 | .scale(sc) 156 | ) 157 | self.erase = False 158 | self.sc = sc 159 | self.scene = scene 160 | self.cursor_rising = False 161 | self.reveal_speed = 30 162 | self.unreveal_speed = 50 163 | self.reveal_time = len(text) / self.reveal_speed 164 | self.unreveal_time = len(text) / self.unreveal_speed 165 | self.cursor = Line(UP * 0.25, DOWN * 0.25, color="#DDDDDD", stroke_width=6) 166 | super().__init__(self.text, self.cursor) 167 | 168 | def drop_cursor(self): 169 | self.cursor.clear_updaters().add_updater( 170 | lambda m, dt: CursorText.update(m, dt, self.text, self), index=0 171 | ) 172 | self.scene.play(Create(self.cursor), run_time=0.4) 173 | self.show_cursor() 174 | return self 175 | 176 | def show_cursor(self): 177 | self.cursor.set_opacity(1) 178 | self.cursor_rising = False 179 | 180 | def reveal(self, wait=1, added_anims=[]): 181 | self.erase = False 182 | self.scene.play( 183 | AddTextLetterByLetter(self.text, run_time=self.reveal_time), *added_anims 184 | ) 185 | if wait > 0: 186 | self.scene.wait(wait) 187 | return self 188 | 189 | def unreveal(self, added_anims=[], hide_cursor=False): 190 | self.erase = True 191 | self.scene.play( 192 | RemoveTextLetterByLetter(self.text), 193 | run_time=self.unreveal_time, 194 | *added_anims, 195 | ) 196 | if hide_cursor: 197 | self.cursor.clear_updaters() 198 | self.scene.play(FadeOut(self.cursor), run_time=0.4) 199 | return self 200 | 201 | def rev_unrev(self, wait_rev, added_anims=[], hide_cursor=False): 202 | self.reveal(wait_rev, added_anims) 203 | self.unreveal(hide_cursor=hide_cursor) 204 | return self 205 | 206 | def switch_text(self, text, font_size=24, wait=0.4, *args, **kwargs): 207 | self.text = ( 208 | Text( 209 | text, 210 | font_size=font_size, 211 | font=self.text.font, 212 | color="#DDDDDD", 213 | *args, 214 | **kwargs, 215 | ) 216 | .set_opacity(0) 217 | .scale(self.sc) 218 | .move_to(self.text) 219 | ) 220 | self.reveal_time = len(text) / self.reveal_speed 221 | self.unreveal_time = len(text) / self.unreveal_speed 222 | self.scene.wait(wait) 223 | return self 224 | 225 | 226 | class NeuralNetwork(Graph): 227 | def __init__( 228 | self, 229 | *layers, 230 | sx=1, 231 | sy=1, 232 | edge_stroke=1.5, 233 | radius=0.3, 234 | stroke_width=2, 235 | randomize=False, 236 | **kwargs 237 | ): 238 | self.sx = sx 239 | self.sy = sy 240 | self.layers = layers 241 | self.edge_stroke = edge_stroke 242 | self.randomize = randomize 243 | self.v, self.p, self.e, self.partitions = self.get_positioning(*layers) 244 | 245 | vertex_config = { 246 | "radius": radius, 247 | "fill_color": BLACK, 248 | "stroke_width": stroke_width, 249 | } 250 | 251 | super().__init__( 252 | self.v, 253 | self.e, 254 | layout={v: p for v, p in zip(self.v, self.p)}, 255 | vertex_config=vertex_config, 256 | edge_config=self.edge_config, 257 | **kwargs, 258 | ) 259 | self.flip(RIGHT) 260 | 261 | def get_positioning(self, *layers): 262 | edges = [] 263 | partitions = [] 264 | self.edge_config = {} 265 | c = 0 266 | 267 | for i in layers: 268 | partitions.append(list(range(c + 1, c + i + 1))) 269 | c += i 270 | for i, v in enumerate(layers[:-1]): 271 | last = sum(layers[: i + 1]) 272 | l = 0 273 | for j in range(v): 274 | for k in range(layers[i + 1]): 275 | edges.append((j + last - layers[i] + 1, k + last + 1)) 276 | r = ( 277 | random.random() * 2 - 1 278 | if self.randomize 279 | else np.clip(brain[i][l], -1, 1) 280 | ) 281 | l += 1 282 | ec = { 283 | "stroke_width": self.edge_stroke, 284 | "stroke_color": PURE_RED if r < 0 else WHITE, 285 | "stroke_opacity": abs(r), 286 | } 287 | self.edge_config[(j + last - layers[i] + 1, k + last + 1)] = ec 288 | 289 | vertices = np.arange(1, sum(layers) + 1) 290 | 291 | nx_graph = nx.Graph() 292 | nx_graph.add_nodes_from(vertices) 293 | nx_graph.add_edges_from(edges) 294 | positions = np.dot( 295 | np.array( 296 | list( 297 | _determine_graph_layout( 298 | nx_graph, 299 | layout="partite", 300 | partitions=partitions, 301 | layout_scale=3, 302 | root_vertex=None, 303 | ).values() 304 | ) 305 | ), 306 | np.array([[self.sx, 0, 0], [0, self.sy, 0], [0, 0, 1]]), 307 | ) 308 | return vertices, positions, edges, partitions 309 | 310 | 311 | class OpenGLScene(ThreeDScene): 312 | def construct(self): 313 | img = OpenGLImageMobject( 314 | "out.png", height=self.camera.height, width=self.camera.width 315 | ) 316 | self.add(img) 317 | self.move_camera( 318 | phi=PI * 0.6, 319 | theta=-PI * 0.3, 320 | zoom=0.8, 321 | frame_center=OUT * 7 + UP + LEFT * 2, 322 | ) 323 | 324 | 325 | n = NeuralNetwork( 326 | 5, 327 | 6, 328 | 6, 329 | 4, 330 | sx=1.29, 331 | sy=0.435, 332 | radius=0.088, 333 | stroke_width=1.5, 334 | ).shift(1.86 * DOWN + 4.01 * RIGHT) 335 | 336 | 337 | class AlphaScene(Scene): 338 | def construct(self): 339 | img = ImageMobject("out.png").scale_to_fit_height(self.camera.frame_height) 340 | self.play(Write(n)) 341 | for _ in range(4): 342 | self.play( 343 | LaggedStart( 344 | *( 345 | ShowPassingFlash(i.copy().set_stroke(width=3), time_width=0.8) 346 | for i in list(n.edges.values()) 347 | ), 348 | lag_ratio=0.022, 349 | ) 350 | ) 351 | self.play(n.animate.scale(1.5).move_to(ORIGIN), run_time=1) 352 | 353 | 354 | class NeuralScene(MovingCameraScene): 355 | def construct(self): 356 | ship = ( 357 | VGroup( 358 | Polygram([[0, 2, 0], [-1, -1, 0], [1, -1, 0]], color=WHITE), 359 | Line([-1, -1, 0], [-1.2667, -1.8, 0]), 360 | Line([1, -1, 0], [1.2667, -1.8, 0]), 361 | ) 362 | .scale(0.2) 363 | .set_stroke(WHITE, 1.7) 364 | .shift(RIGHT * 10) 365 | .rotate(-PI / 1.7) 366 | ) 367 | bullet = ( 368 | Dot(radius=0.03) 369 | .set_opacity(0.7) 370 | .move_to(ship[0].get_center_of_mass() + RIGHT) 371 | .rotate(-PI / 1.7, about_point=ship.get_center_of_mass()) 372 | .shift(4 * RIGHT) 373 | ) 374 | asteroid = ( 375 | RegularPolygon(7, color=WHITE, stroke_width=1, radius=0.8) 376 | .rotate(PI * 0.2) 377 | .move_to(ship) 378 | .shift(RIGHT * 1.5 + DOWN * 2) 379 | ) 380 | asteroid2 = ( 381 | RegularPolygon(5, color=WHITE, stroke_width=1, radius=0.5) 382 | .rotate(PI * 0.3) 383 | .move_to(ship) 384 | .shift(RIGHT * 4 + UP * 1.5) 385 | ) 386 | asteroid3 = ( 387 | RegularPolygon(3, color=WHITE, stroke_width=1, radius=0.4) 388 | .rotate(PI * 0.4) 389 | .move_to(ship) 390 | .shift(RIGHT + UP * 2) 391 | ) 392 | 393 | self.add(n.scale(1.5).move_to(ORIGIN)) 394 | in_arrows = VGroup( 395 | *( 396 | Arrow(ORIGIN, RIGHT * 0.9) 397 | .set_opacity(0.7) 398 | .move_to(x.get_center() + LEFT * 0.5) 399 | for x in list(n.vertices.values())[:5] 400 | ) 401 | ) 402 | out_arrows = VGroup( 403 | *( 404 | Arrow(ORIGIN, RIGHT * 0.9) 405 | .set_opacity(0.7) 406 | .move_to(x.get_center() + RIGHT * 0.5) 407 | for x in list(n.vertices.values())[17:] 408 | ) 409 | ) 410 | title = Text( 411 | "Neural Networks", font="monocraft", font_size=36, color="#DDDDDD" 412 | ).shift(UP * 3.2) 413 | text = ( 414 | CursorText(self, "This is called a neural network") 415 | .move_to(self.camera.frame_center) 416 | .shift(DOWN * 3.5) 417 | ) 418 | 419 | self.play(AddTextLetterByLetter(title), rate_func=rush_from) 420 | self.wait() 421 | text.drop_cursor() 422 | text.rev_unrev(3) 423 | text.switch_text("It's just a complex mathematical function") 424 | text.rev_unrev(3) 425 | text.switch_text("It takes some game information as inputs...") 426 | text.rev_unrev(3.5, [FadeIn(in_arrows, shift=RIGHT, lag_ratio=0.1)]) 427 | text.switch_text("...and outputs tell what actions to take") 428 | text.rev_unrev(4, [FadeIn(out_arrows, shift=RIGHT, lag_ratio=0.1)], True) 429 | 430 | world = VGroup(ship, asteroid, asteroid2, asteroid3, bullet) 431 | self.play( 432 | self.camera.frame.animate.shift(2.5 * RIGHT + DOWN).scale(1.2), 433 | world.animate.shift(LEFT * 4), 434 | FadeOut(title, shift=UP), 435 | ) 436 | 437 | direc = ship[0].get_center_of_mass() - asteroid.get_center() 438 | distance = DashedLine( 439 | ship[0].get_center_of_mass(), 440 | asteroid.get_center() + direc / np.linalg.norm(direc) * 0.8, 441 | ).set_stroke(RED, 2) 442 | heading = DashedLine( 443 | ship[0].get_center_of_mass(), 444 | ship[0].get_center_of_mass() 445 | + (ship[0].get_vertices()[0] - ship[0].get_center_of_mass()) * 5, 446 | ).set_stroke(GREEN, 2) 447 | 448 | theta = Angle(heading, distance, radius=0.5, other_angle=True) 449 | theta.points = np.concatenate( 450 | [ 451 | theta.points, 452 | [ship[0].get_center_of_mass()], 453 | theta.points[0].reshape(1, 3), 454 | ] 455 | ) 456 | theta.set_points_as_corners(theta.points).set_fill(BLUE).set_stroke( 457 | BLUE, width=0 458 | ).set_opacity(0.5) 459 | 460 | right = ( 461 | DashedLine(ORIGIN, RIGHT * 2) 462 | .set_stroke(BLUE, 2) 463 | .shift(ship[0].get_center_of_mass()) 464 | ) 465 | phi = Angle(heading, right, radius=0.5, other_angle=False) 466 | phi.points = np.concatenate( 467 | [ 468 | phi.points, 469 | [ship[0].get_center_of_mass()], 470 | phi.points[0].reshape(1, 3), 471 | ] 472 | ) 473 | phi.set_points_as_corners(phi.points).set_fill(YELLOW).set_stroke( 474 | YELLOW, width=0 475 | ).set_opacity(0.5) 476 | 477 | text = ( 478 | CursorText(self, "This network takes 5 inputs", sc=1.2) 479 | .move_to(self.camera.frame_center) 480 | .shift(DOWN * 4) 481 | ) 482 | text.drop_cursor() 483 | text.rev_unrev(2.5) 484 | 485 | label = ( 486 | MathTex(r"\ell") 487 | .move_to(distance) 488 | .shift(UP * 0.15 + RIGHT * 0.15) 489 | .set_color(RED) 490 | .scale(0.7) 491 | ) 492 | text.switch_text("The distance to the nearest asteroid...") 493 | text.reveal(1.5, [Write(distance)]) 494 | self.play( 495 | Write(label), 496 | Transform( 497 | in_arrows[0], 498 | MathTex(r"\ell").set_opacity(0.7).move_to(in_arrows[0]).scale(0.9), 499 | ), 500 | ) 501 | self.wait(3.5) 502 | text.unreveal([Unwrite(label)]) 503 | 504 | text.switch_text("...the angle to this asteroid") 505 | text.reveal(1.5, [LaggedStart(Write(heading), Write(theta), lag_ratio=0.5)]) 506 | label = ( 507 | MathTex(r"\theta") 508 | .move_to(theta) 509 | .shift(DOWN * 0.2 + RIGHT * 0.35) 510 | .set_color(BLUE) 511 | .scale(0.7) 512 | ) 513 | self.play( 514 | Write(label), 515 | Transform( 516 | in_arrows[1], 517 | MathTex(r"\theta").set_opacity(0.7).move_to(in_arrows[1]).scale(0.9), 518 | ), 519 | ) 520 | self.wait(3.5) 521 | text.unreveal( 522 | ( 523 | Unwrite(distance), 524 | Unwrite(theta), 525 | Unwrite(label), 526 | ), 527 | ) 528 | text.switch_text("...angle of the ship itself") 529 | text.reveal(1.5, [LaggedStart(Write(right), Write(phi), lag_ratio=0.5)]) 530 | 531 | label = ( 532 | MathTex(r"\phi") 533 | .move_to(theta) 534 | .shift(RIGHT * 0.6) 535 | .set_color(YELLOW) 536 | .scale(0.6) 537 | ) 538 | self.play( 539 | Write(label), 540 | Transform( 541 | in_arrows[4], 542 | MathTex(r"\phi").set_opacity(0.7).move_to(in_arrows[4]).scale(0.9), 543 | ), 544 | ) 545 | self.wait(3.5) 546 | text.unreveal( 547 | ( 548 | Unwrite(heading), 549 | Unwrite(right), 550 | Unwrite(phi), 551 | Unwrite(label), 552 | ), 553 | ) 554 | v1 = Arrow( 555 | ship[0].get_center_of_mass(), 556 | ship[0].get_center_of_mass() 557 | + (heading.get_end() - heading.get_start()) * 0.5, 558 | buff=0, 559 | max_stroke_width_to_length_ratio=2, 560 | max_tip_length_to_length_ratio=0.2, 561 | color="#FF2222", 562 | ) 563 | v2 = Arrow( 564 | asteroid.get_center(), 565 | asteroid.get_center() + UP, 566 | buff=0, 567 | max_stroke_width_to_length_ratio=2, 568 | max_tip_length_to_length_ratio=0.2, 569 | color="#FF2222", 570 | ) 571 | label1 = ( 572 | MathTex(r"\overrightarrow{v_1}") 573 | .move_to(v1) 574 | .shift(UP * 0.3) 575 | .set_color("#FF2222") 576 | .scale(0.6) 577 | ) 578 | label2 = ( 579 | MathTex(r"\overrightarrow{v_2}") 580 | .move_to(v2) 581 | .shift(RIGHT * 0.3) 582 | .set_color("#FF2222") 583 | .scale(0.6) 584 | ) 585 | 586 | text.switch_text("...and the relative velocity of the asteroid") 587 | text.reveal(1.5, [Write(v1), Write(v2)]) 588 | self.play( 589 | Write(label1), 590 | Write(label2), 591 | Transform( 592 | in_arrows[2], 593 | MathTex(r"(\overrightarrow{v_2} - \overrightarrow{v_1})_x") 594 | .set_opacity(0.7) 595 | .move_to(in_arrows[2]) 596 | .shift(LEFT * 0.5) 597 | .scale(0.6), 598 | ), 599 | Transform( 600 | in_arrows[3], 601 | MathTex(r"(\overrightarrow{v_2} - \overrightarrow{v_1})_y") 602 | .set_opacity(0.7) 603 | .move_to(in_arrows[3]) 604 | .shift(LEFT * 0.5) 605 | .scale(0.6), 606 | ), 607 | ) 608 | self.wait(3.5) 609 | 610 | text.unreveal( 611 | ( 612 | Unwrite(v1), 613 | Unwrite(v2), 614 | Unwrite(label1), 615 | Unwrite(label2), 616 | ), 617 | hide_cursor=True, 618 | ) 619 | 620 | self.play( 621 | self.camera.frame.animate.shift(2.5 * LEFT + UP).scale(1 / 1.2), 622 | world.animate.shift(RIGHT * 4), 623 | Transform( 624 | out_arrows[0], 625 | Text("RIGHT", font="monocraft", font_size=14) 626 | .set_opacity(0.7) 627 | .move_to(out_arrows[0]) 628 | .align_to(out_arrows[0], LEFT), 629 | ), 630 | Transform( 631 | out_arrows[1], 632 | Text("LEFT", font="monocraft", font_size=14) 633 | .set_opacity(0.7) 634 | .move_to(out_arrows[1]) 635 | .align_to(out_arrows[1], LEFT), 636 | ), 637 | Transform( 638 | out_arrows[2], 639 | Text("SHOOT", font="monocraft", font_size=14) 640 | .set_opacity(0.7) 641 | .move_to(out_arrows[2]) 642 | .align_to(out_arrows[2], LEFT), 643 | ), 644 | Transform( 645 | out_arrows[3], 646 | Text("THROTTLE", font="monocraft", font_size=14) 647 | .set_opacity(0.7) 648 | .move_to(out_arrows[3]) 649 | .align_to(out_arrows[3], LEFT), 650 | ), 651 | ) 652 | 653 | weight_anim = LaggedStart( 654 | *( 655 | ShowPassingFlash(i.copy().set_stroke(width=4), time_width=0.5) 656 | for i in list(n.edges.values()) 657 | ), 658 | lag_ratio=0.02, 659 | ) 660 | text = ( 661 | CursorText(self, "The connections between the nodes are called weights") 662 | .move_to(self.camera.frame_center) 663 | .shift(DOWN * 3.5) 664 | ) 665 | text.drop_cursor() 666 | text.rev_unrev(3, [weight_anim]) 667 | text.switch_text("These define the behaviour of our spaceship") 668 | text.rev_unrev(3, [weight_anim]) 669 | text.switch_text("But how do we find the correct weights?") 670 | text.rev_unrev(3.5, hide_cursor=True) 671 | title = Text( 672 | "Genetic Algorithm", font="monocraft", font_size=36, color="#DDDDDD" 673 | ).shift(UP * 3.2) 674 | self.play( 675 | AddTextLetterByLetter(title, rate_func=rush_from), 676 | LaggedStart( 677 | Unwrite(in_arrows, reverse=False), 678 | Unwrite(out_arrows, reverse=False), 679 | lag_ratio=0.1, 680 | ), 681 | ) 682 | self.wait() 683 | 684 | 685 | class GeneticScene(Scene): 686 | def construct(self): 687 | title = Text( 688 | "Genetic Algorithm", font="monocraft", font_size=36, color="#DDDDDD" 689 | ).shift(UP * 3.2) 690 | 691 | n = ( 692 | NeuralNetwork( 693 | 5, 694 | 6, 695 | 6, 696 | 4, 697 | sx=1.29, 698 | sy=0.435, 699 | radius=0.088, 700 | stroke_width=1.5, 701 | ) 702 | .scale(1.5) 703 | .move_to(ORIGIN) 704 | ) 705 | 706 | self.add(title, n) 707 | 708 | nn_grid = ( 709 | VGroup( 710 | *( 711 | NeuralNetwork( 712 | 5, 713 | 6, 714 | 6, 715 | 4, 716 | sx=1.29, 717 | sy=0.435, 718 | radius=0.088, 719 | randomize=False if i == 4 else True, 720 | ) 721 | for i in range(9) 722 | ) 723 | ) 724 | .arrange_in_grid(3, 3) 725 | .scale(0.4) 726 | .set_stroke(width=0.8) 727 | .move_to(ORIGIN) 728 | ) 729 | group = VGroup(*(n.copy().set_opacity(0.25) for _ in range(9))) 730 | text = ( 731 | CursorText(self, "Instead of training a single ship...") 732 | .move_to(self.camera.frame_center) 733 | .shift(DOWN * 3.5) 734 | ) 735 | text.drop_cursor() 736 | text.rev_unrev(3) 737 | text.switch_text("...we start with a population of many") 738 | self.remove(n) 739 | self.add(group) 740 | text.rev_unrev(3, [ReplacementTransform(group, nn_grid)]) 741 | 742 | text.switch_text("After they all are dead, we rate them...").rev_unrev(3.5) 743 | copy0 = nn_grid[0].copy().set_z_index(2) 744 | copy4 = nn_grid[4].copy().set_z_index(2) 745 | for i in copy0.vertices.values(): 746 | i.set_z_index(3) 747 | for i in copy4.vertices.values(): 748 | i.set_z_index(3) 749 | sr = SurroundingRectangle(nn_grid, color=BLACK).set_opacity(0.5).set_z_index(1) 750 | text.switch_text("...then select two 'fitter' brains").rev_unrev( 751 | 3.5, 752 | [FadeIn(sr), FadeIn(copy0), FadeIn(copy4)], 753 | ) 754 | nn_grid2 = ( 755 | VGroup( 756 | *( 757 | NeuralNetwork( 758 | 5, 759 | 6, 760 | 6, 761 | 4, 762 | sx=1.29, 763 | sy=0.435, 764 | radius=0.088, 765 | randomize=False if i == 4 else True, 766 | ) 767 | for i in range(9) 768 | ) 769 | ) 770 | .arrange_in_grid(3, 3) 771 | .scale(0.4) 772 | .set_stroke(width=0.8) 773 | .move_to(ORIGIN) 774 | .shift(RIGHT * 3.5) 775 | ) 776 | text.switch_text("and merge their weights to create a better brain!") 777 | text.reveal( 778 | 0, 779 | [ 780 | nn_grid.animate.shift(LEFT * 3.5), 781 | sr.animate.shift(LEFT * 3.5), 782 | Transform(copy0, nn_grid2[0].copy(), rate_func=rush_from), 783 | Transform( 784 | copy4, nn_grid2[0].copy().set_opacity(0), rate_func=rush_from 785 | ), 786 | ], 787 | ) 788 | self.remove(copy4) 789 | nn_grid2[0].set_opacity(0) 790 | self.wait(4) 791 | text.unreveal() 792 | 793 | text.switch_text("Fill the next generation with new brains like this") 794 | text.reveal( 795 | 3, 796 | [ 797 | LaggedStart(*(FadeIn(x, shift=LEFT) for x in nn_grid2), lag_ratio=0.1), 798 | ], 799 | ) 800 | nn_grid2[0].become(copy0) 801 | self.remove(copy0) 802 | text.unreveal() 803 | text.switch_text("...and repeat to see the population evolve!") 804 | text.reveal() 805 | for _ in range(4): 806 | self.play( 807 | nn_grid2.animate.shift(LEFT * 7), 808 | FadeOut( 809 | nn_grid, 810 | shift=LEFT * 7, 811 | ), 812 | FadeOut( 813 | sr, 814 | shift=LEFT * 7, 815 | ), 816 | run_time=0.8, 817 | ) 818 | self.play( 819 | FadeIn(sr), 820 | LaggedStart( 821 | *(FadeIn(x, shift=LEFT) for x in nn_grid.move_to(RIGHT * 3.5)), 822 | lag_ratio=0.1, 823 | ), 824 | run_time=0.8, 825 | ) 826 | nn_grid, nn_grid2 = nn_grid2, nn_grid 827 | text.unreveal() 828 | text.switch_text("Let's plug this back and see it in action!") 829 | text.reveal( 830 | 0, 831 | [ 832 | nn_grid2.animate.shift(LEFT * 3.5).scale(4 / 3), 833 | FadeOut( 834 | nn_grid, 835 | shift=LEFT * 3.5, 836 | ), 837 | FadeOut( 838 | sr, 839 | shift=LEFT * 3.5, 840 | ), 841 | ], 842 | ) 843 | 844 | copy1 = nn_grid2[4].copy() 845 | nn_grid2[4].set_opacity(0) 846 | 847 | self.play( 848 | Transform(copy1, n), 849 | FadeOut(nn_grid2), 850 | ) 851 | self.wait(3) 852 | text.unreveal([Unwrite(title, run_time=1.5)], True) 853 | 854 | 855 | class ScriptScene(Scene): 856 | def construct(self): 857 | text = CursorText(self, "Hey There!").drop_cursor().rev_unrev(3) 858 | 859 | text.switch_text( 860 | "This is my interactive simulation to train your own AI to play asteroids!", 861 | 18, 862 | ).rev_unrev(4, hide_cursor=True) 863 | self.wait() 864 | 865 | text = ( 866 | CursorText(self, "The AI is pretty dumb right now...") 867 | .drop_cursor() 868 | .rev_unrev(4) 869 | ) 870 | 871 | text.switch_text("It doesn't know what to do...").rev_unrev(2.5) 872 | text.switch_text("But how is it making these decisions?").rev_unrev( 873 | 5, hide_cursor=True 874 | ) 875 | self.wait(3) 876 | 877 | text = ( 878 | CursorText(self, "This can be considered as the 'brain' of our AI") 879 | .drop_cursor() 880 | .rev_unrev(3) 881 | ) 882 | text.switch_text("Let's pause & see how it works!").rev_unrev( 883 | 3, hide_cursor=True 884 | ) 885 | 886 | text = ( 887 | CursorText(self, "Here we have a population of 100 ships") 888 | .drop_cursor() 889 | .rev_unrev(3) 890 | ) 891 | text.switch_text("You can drag it to vary the population size").rev_unrev( 892 | 3, hide_cursor=True 893 | ) 894 | 895 | text = ( 896 | CursorText(self, "Click on a ship to track it").drop_cursor().rev_unrev(5) 897 | ) 898 | text.switch_text("Let's track the best one alive...").rev_unrev(4) 899 | text.switch_text("This is what the ship is currently looking at").rev_unrev( 900 | 5, hide_cursor=True 901 | ) 902 | 903 | text = ( 904 | CursorText(self, "You can also change the number of layers...") 905 | .drop_cursor() 906 | .rev_unrev(9) 907 | ) 908 | text.switch_text("...and other parameters of the neural network").rev_unrev(3) 909 | text.switch_text("Let's speedup the simulation").rev_unrev(3, hide_cursor=True) 910 | 911 | text = ( 912 | CursorText(self, "Seems like it found a good strategy to survive") 913 | .drop_cursor() 914 | .rev_unrev(6) 915 | ) 916 | text.switch_text("Asteroids are coded to aim towards the player").rev_unrev(3) 917 | text.switch_text("Remaining stationary seems to be the best").rev_unrev(3) 918 | text.switch_text( 919 | "You can save this model as a file and load it later", 920 | ).rev_unrev(3, hide_cursor=True) 921 | 922 | text = ( 923 | CursorText(self, "...or track another ship and save that model") 924 | .drop_cursor() 925 | .rev_unrev(5) 926 | ) 927 | text.switch_text("But let's train it further").rev_unrev(3, hide_cursor=True) 928 | 929 | text = ( 930 | CursorText(self, "Changing the inputs or the asteroid behaviour...") 931 | .drop_cursor() 932 | .rev_unrev(3) 933 | ) 934 | 935 | for _text in ( 936 | "...can change the strategy the AI uses", 937 | "It also depends on the fitness function that rates the network", 938 | "You can modify the code and try out different things", 939 | "This is written in Rust without any ML libraries", 940 | ): 941 | text.switch_text(_text).rev_unrev(3) 942 | text.switch_text( 943 | "Source code and download link are in description..." 944 | ).rev_unrev(3, hide_cursor=True) 945 | 946 | text = ( 947 | CursorText(self, "Thanks for watching!") 948 | .drop_cursor() 949 | .rev_unrev(5, hide_cursor=True) 950 | ) 951 | 952 | footer = Text( 953 | "github.com/sparshg", font="SF Mono Powerline", font_size=32 954 | ).set_color("#333333") 955 | footer_sub = ( 956 | Text("made with Manim", font="SF Mono Powerline", font_size=18) 957 | .set_color("#333333") 958 | .to_edge(DOWN, buff=0.1) 959 | ) 960 | self.add(footer, footer_sub) 961 | self.wait(2) 962 | self.play(Unwrite(footer), Unwrite(footer_sub)) 963 | self.wait() 964 | -------------------------------------------------------------------------------- /demo/Pendulums.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparshg/animations/24d287d9de9dc22198809a1b977d516c5a85bace/demo/Pendulums.gif -------------------------------------------------------------------------------- /demo/Rolling.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparshg/animations/24d287d9de9dc22198809a1b977d516c5a85bace/demo/Rolling.gif -------------------------------------------------------------------------------- /demo/Shatter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sparshg/animations/24d287d9de9dc22198809a1b977d516c5a85bace/demo/Shatter.gif -------------------------------------------------------------------------------- /pendulums.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | from manim.opengl import * 3 | import numpy as np 4 | 5 | BLACK = "#343434" 6 | SLATE = "#a2a2a2" 7 | WHITE = "#ece6e2" 8 | W = config.frame_width 9 | H = config.frame_height 10 | config.background_color = WHITE 11 | 12 | 13 | ceil_len = 3 14 | w1 = 6 15 | w2 = w1 * 1.1 # w2 > w1 16 | # A1 = 0.2 17 | # A2 = 0.2 18 | p1 = 0 19 | p2 = 0 20 | L = 4 21 | l = 1.5 22 | sep = 2 23 | T = 4 * PI / (w1 + w2) 24 | 25 | 26 | class Spring(VMobject): 27 | def __init__(self, start=ORIGIN, length=2, bumps=14): 28 | self.length = length 29 | self.empty = 0.4 30 | self.step = 0.07 31 | self.bump = 0.18 32 | super().__init__(color=BLACK) 33 | vertices = np.array( 34 | [ 35 | [0, 0, 0], 36 | [self.empty, 0, 0], 37 | [self.empty + self.step, self.bump, 0], 38 | *[ 39 | [ 40 | self.empty + self.step + self.step * 2 * i, 41 | self.bump * (1 - (i % 2) * 2), 42 | 0, 43 | ] 44 | for i in range(1, bumps) 45 | ], 46 | [self.empty + self.step * 2 * bumps, 0, 0], 47 | [self.empty * 2 + self.step * 2 * bumps, 0, 0], 48 | ] 49 | ) 50 | vertices = vertices * [self.length / 51 | (1 + 0.2 * bumps), 1, 0] + np.array(start) 52 | 53 | self.start_new_path(np.array(start)) 54 | self.add_points_as_corners( 55 | [*(np.array(vertex) for vertex in vertices)]) 56 | 57 | 58 | class Test(ThreeDScene): 59 | def construct(self): 60 | self.renderer.background_color = WHITE 61 | # Setup 62 | a = Circle(radius=0.4, fill_opacity=1).shift(LEFT * sep + DOWN) 63 | b = Circle(radius=0.4, fill_opacity=1).shift(RIGHT * sep + DOWN) 64 | l1 = Line(a.get_center() + UP * L, a.get_center()).set_color(BLACK) 65 | l2 = Line(b.get_center() + UP * L, b.get_center()).set_color(BLACK) 66 | ceil = VGroup( 67 | DashedLine( 68 | start=ceil_len * LEFT, 69 | end=(ceil_len) * RIGHT, 70 | dashed_ratio=0.4, 71 | dash_length=0.2, 72 | color=BLACK, 73 | ).shift(l1.get_start()[1] * UP) 74 | ) 75 | [i.rotate(PI / 4, about_point=i.get_start()) 76 | for i in ceil[0].submobjects] 77 | ceil.add( 78 | Line(ceil_len * LEFT, ceil_len * RIGHT, 79 | color=BLACK).align_to(ceil, DOWN) 80 | ) 81 | spring = Spring(l1.get_start() + DOWN * l, 2 * sep) 82 | d1 = Dot(color=BLACK).move_to(spring.get_start()) 83 | d2 = Dot(color=BLACK).move_to(spring.get_end()) 84 | paint1 = Dot(color=BLACK).move_to(a.shift(DOWN)) 85 | paint2 = Dot(color=BLACK).move_to(b.shift(DOWN)) 86 | 87 | # Physics 88 | t = ValueTracker() 89 | A1 = ValueTracker(0.4) 90 | A2 = ValueTracker(0) 91 | l1.add_updater( 92 | lambda m: m.set_angle( 93 | A1.get_value() * np.cos(w1 * t.get_value() + p1) 94 | + A2.get_value() * np.cos(w2 * t.get_value() + p2) 95 | - PI / 2 96 | ) 97 | ) 98 | l2.add_updater( 99 | lambda m: m.set_angle( 100 | A1.get_value() * np.cos(w1 * t.get_value() + p1) 101 | - A2.get_value() * np.cos(w2 * t.get_value() + p2) 102 | - PI / 2 103 | ) 104 | ) 105 | a.add_updater(lambda m: m.move_to(l1.get_end())) 106 | b.add_updater(lambda m: m.move_to(l2.get_end())) 107 | 108 | def springupdater(m: Spring): 109 | # Modified Mobject.put_start_and_end_on 110 | curr_start, curr_end = m.get_start_and_end() 111 | curr_vect = curr_end - curr_start 112 | target_vect = ( 113 | l2.get_start() 114 | + (l2.get_end() - l2.get_start()) * l / L 115 | - l1.get_start() 116 | - (l1.get_end() - l1.get_start()) * l / L 117 | ) 118 | axis = ( 119 | normalize(np.cross(curr_vect, target_vect)) 120 | if np.linalg.norm(np.cross(curr_vect, target_vect)) != 0 121 | else OUT 122 | ) 123 | m.stretch( 124 | np.linalg.norm(target_vect) / np.linalg.norm(curr_vect), 125 | 0, 126 | about_point=curr_start, 127 | ) 128 | m.rotate( 129 | angle_between_vectors(curr_vect, target_vect), 130 | about_point=curr_start, 131 | axis=axis, 132 | ) 133 | m.move_to( 134 | l1.get_start() + (l1.get_end() - l1.get_start()) * l / L, 135 | aligned_edge=LEFT, 136 | ) 137 | 138 | spring.add_updater(springupdater) 139 | d1.add_updater(lambda m: m.move_to(spring.get_start())) 140 | d2.add_updater(lambda m: m.move_to(spring.get_end())) 141 | 142 | paint1.add_updater(lambda m: m.set_x(a.get_x())) 143 | paint2.add_updater(lambda m: m.set_x(b.get_x())) 144 | trails = VGroup() 145 | 146 | def add_trail(): 147 | self.play(FadeIn(paint1), FadeIn(paint2), run_time=0.4) 148 | trails.add( 149 | VGroup( 150 | VMobject() 151 | .start_new_path(paint1.get_center()) 152 | .set_stroke(color=[WHITE, BLACK]) 153 | .set_sheen_direction(UP), 154 | VMobject() 155 | .start_new_path(paint2.get_center()) 156 | .set_stroke(color=[WHITE, BLACK]) 157 | .set_sheen_direction(UP), 158 | ) 159 | ) 160 | trails[-1][0].add_updater( 161 | lambda m, dt: m.shift(DOWN * 0.25 * dt).add_points_as_corners( 162 | [paint1.get_center()] 163 | ) 164 | ) 165 | trails[-1][1].add_updater( 166 | lambda m, dt: m.shift(DOWN * 0.25 * dt).add_points_as_corners( 167 | [paint2.get_center()] 168 | ) 169 | ) 170 | 171 | def remove_trail(play=True): 172 | if play: 173 | self.play(FadeOut(paint1), FadeOut(paint2), run_time=0.4) 174 | for i in trails[-1]: 175 | i.clear_updaters().add_updater( 176 | lambda m, dt: m.shift( 177 | DOWN * 0.25 * dt 178 | ) # .set_opacity(m.get_stroke_opacity() - 0.2*dt) 179 | ) 180 | 181 | config = {"stroke_color": SLATE, 182 | "stroke_width": 2, "stroke_opacity": 0.2} 183 | grid = NumberPlane( 184 | background_line_style=config, 185 | axis_config={"stroke_color": WHITE, "stroke_opacity": 0}, 186 | x_range=(-W, W, 1.5), 187 | y_range=(-H / 2, H, 1.5), 188 | ) 189 | 190 | rect = Rectangle(WHITE, H, W, fill_opacity=1) 191 | self.add(l1, l2, spring, ceil, d1, 192 | d2, a, b, trails, rect, grid) 193 | self.play(Create(grid)) 194 | self.play(FadeOut(rect)) 195 | 196 | def simulate(time): 197 | self.play( 198 | t.animate.increment_value(time), 199 | trails[-1][0].animate.set_stroke(color=[BLACK, WHITE]), 200 | trails[-1][1].animate.set_stroke(color=[BLACK, WHITE]), 201 | rate_func=linear, 202 | run_time=time, 203 | ) 204 | 205 | add_trail() 206 | simulate(10 * T) 207 | remove_trail() 208 | self.play(A1.animate.set_value(0), 209 | A2.animate.set_value(0.4), run_time=2) 210 | add_trail() 211 | simulate(10 * T) 212 | remove_trail() 213 | self.play(A1.animate.set_value(0.2), 214 | A2.animate.set_value(0.2), run_time=2) 215 | add_trail() 216 | simulate(25 * T) 217 | remove_trail(False) 218 | self.play( 219 | t.animate.increment_value(1), 220 | rate_func=lambda x: 2*x-x*x, 221 | run_time=2, 222 | ) 223 | 224 | banner = ManimBanner(dark_theme=False).scale( 225 | 0.8).move_to([0, 5, 5.5]).rotate(PI / 2, axis=RIGHT) 226 | banner.anim.rotate(PI / 2, axis=RIGHT) 227 | self.add(banner) 228 | 229 | self.move_camera( 230 | phi=PI / 2, 231 | theta=-PI / 2, 232 | frame_center=[0, 0, 5], 233 | added_anims=[grid.animate.set_opacity(0)], 234 | ) 235 | 236 | line = Text("", color="#a6a6a6").next_to(banner, 237 | IN, 2).scale(0.6).rotate(PI / 2, axis=RIGHT) 238 | 239 | self.play( 240 | LaggedStart( 241 | banner.expand(), 242 | Write(line), 243 | lag_ratio=0.2, 244 | ) 245 | ) 246 | self.wait() 247 | self.play(Unwrite(banner), Unwrite(line)) 248 | -------------------------------------------------------------------------------- /rolling.py: -------------------------------------------------------------------------------- 1 | from manim import * 2 | 3 | from manim._config.utils import ManimConfig 4 | 5 | ManimConfig.background_color = "#ece6e2" 6 | radius = 2 7 | ground_len = 5 8 | FONT = "product sans" 9 | BLACK = "#343434" 10 | WHITE = "#ece6e2" 11 | SPEED = 25 12 | 13 | 14 | def time_adj(k): 15 | return 1 + 1 / k 16 | 17 | 18 | def speed_adj(k): 19 | return (k - 1) / (k + 1) 20 | 21 | 22 | def get_slow_mo(k): # |k| > 1 23 | return lambda t: (t / k) * (k + 1 - t) 24 | 25 | 26 | class Test(MovingCameraScene): 27 | def construct(self): 28 | wheel = SVGMobject("assets/wheel.svg").scale(1.25).shift(1.22 * DOWN) 29 | wheel.speed = SPEED 30 | 31 | ground = VGroup( 32 | # Dashed lines 33 | DashedLine( 34 | start=ground_len * LEFT, 35 | end=(ground_len + 12) * RIGHT, 36 | dashed_ratio=0.4, 37 | dash_length=0.2, 38 | color=BLACK, 39 | ).shift(2.5 * DOWN) 40 | ) 41 | [i.rotate(PI / 4) for i in ground[0].submobjects] 42 | ground.add( 43 | # Add simple line for ground 44 | Line(ground_len * LEFT, ground_len * RIGHT, color=BLACK).align_to( 45 | ground, UP, DOWN 46 | ), 47 | # Mask for left side 48 | Rectangle( 49 | fill_opacity=1, 50 | color=WHITE, 51 | height=0.2, 52 | width=config.frame_x_radius - ground_len, 53 | ) 54 | .move_to(ground, aligned_edge=UP) 55 | .to_edge(LEFT, 0), 56 | # Mask for right side 57 | Rectangle( 58 | fill_opacity=1, 59 | color=WHITE, 60 | height=0.2, 61 | width=config.frame_x_radius - ground_len, 62 | ) 63 | .move_to(ground, aligned_edge=UP) 64 | .to_edge(RIGHT, 0), 65 | ) 66 | head = VGroup( 67 | Text( 68 | "Translational", 69 | font=FONT, 70 | color=BLACK, 71 | ), 72 | Text( 73 | "Motion", 74 | font=FONT, 75 | color=BLACK, 76 | ), 77 | ) 78 | head1 = VGroup( 79 | Text( 80 | "Rotational", 81 | font=FONT, 82 | color=BLACK, 83 | ), 84 | Text( 85 | "Motion", 86 | font=FONT, 87 | color=BLACK, 88 | ), 89 | ) 90 | head2 = VGroup( 91 | Text( 92 | "Rolling", 93 | font=FONT, 94 | color=BLACK, 95 | ), 96 | Text( 97 | "Motion", 98 | font=FONT, 99 | color=BLACK, 100 | ), 101 | ) 102 | [i.arrange().scale(1.2).shift(3 * UP) for i in [head, head1, head2]] 103 | head2[0].shift(0.1 * DOWN) 104 | 105 | self.play( 106 | AnimationGroup(FadeIn(ground), Write(wheel), run_time=2, lag_ratio=0.5) 107 | ) 108 | self.wait(0.5) 109 | self.play(AnimationGroup(Write(head), ShiftWiggle(wheel), lag_ratio=0.5)) 110 | self.play( 111 | wheel.animate.shift(4 * LEFT), 112 | ground[0].animate.shift(4 * LEFT), 113 | ) 114 | 115 | # Start slow-mo 116 | d = 3 117 | k = 1.01 118 | self.play( 119 | wheel.animate.shift(d * RIGHT), 120 | self.camera.frame.animate.scale(0.8).shift(DOWN), 121 | rate_func=get_slow_mo(k), 122 | run_time=d * time_adj(k) / wheel.speed, 123 | ) 124 | wheel.speed *= speed_adj(k) 125 | # End slow-mo 126 | 127 | v = VGroup( 128 | Vector(RIGHT / 2), 129 | Vector(RIGHT / 2).shift(DR * 0.7), 130 | Vector(RIGHT / 2).shift(DL * 0.75), 131 | Vector(RIGHT / 2).shift(UL * 0.75), 132 | Vector(RIGHT / 2).shift(UR * 0.75), 133 | Vector(RIGHT / 2).shift(LEFT), 134 | Vector(RIGHT / 2).shift(RIGHT), 135 | Vector(RIGHT / 2).shift(DOWN * 1.1), 136 | Vector(RIGHT / 2).shift(UP * 1.1), 137 | ).set_color(RED) 138 | v[0].set_color(BLUE_D) 139 | v.add_updater( 140 | lambda v: v.move_to(wheel.get_center()).shift( 141 | v.get_center() - v[0].get_start() 142 | ) 143 | ) 144 | Vcom = MathTex("V_{Center\ of\ mass}", color=BLACK).shift(0.4 * UP) 145 | wheel.add_updater(lambda m, dt: m.shift(RIGHT * m.speed * dt)) 146 | self.play(Create(v)) 147 | self.wait() 148 | self.play( 149 | Succession( 150 | Write(Vcom), 151 | Wait(), 152 | Transform(Vcom, MathTex("V_{c}", color=BLACK).move_to(Vcom)), 153 | ), 154 | ) 155 | self.wait(3) 156 | wheel.clear_updaters() 157 | 158 | # Reverse start slow-mo 159 | d = 4 160 | k *= -1 161 | self.play( 162 | wheel.animate.shift(d * RIGHT), 163 | FadeOut(v), 164 | FadeOut(Vcom), 165 | self.camera.frame.animate.shift(UP).scale(1.25), 166 | rate_func=get_slow_mo(k), 167 | run_time=d * time_adj(k) / wheel.speed, 168 | ) 169 | wheel.speed *= speed_adj(k) 170 | # Reverse end slow-mo 171 | 172 | self.wait() 173 | self.play( 174 | wheel.animate.shift(wheel.get_center()[0] * LEFT), 175 | ground[0].animate.shift(wheel.get_center()[0] * LEFT), 176 | ) 177 | self.play(Transform(head, head1)) 178 | self.play(Rotate(wheel, TAU - PI / 4)) 179 | self.wait(0.5) 180 | 181 | wheel.speed = SPEED 182 | wheel.w = wheel.speed / 1.2 183 | 184 | # Start slow-mo 185 | d = PI / 1.7 186 | k = 1.05 187 | self.play( 188 | Rotate(wheel, -d), 189 | self.camera.frame.animate.scale(0.8).shift(DOWN), 190 | rate_func=get_slow_mo(k), 191 | run_time=d * time_adj(k) / wheel.w, 192 | ) 193 | wheel.w *= speed_adj(k) 194 | # End slow-mo 195 | wheel.add_updater(lambda m, dt: m.rotate(-m.w * dt)) 196 | 197 | v = VGroup( 198 | Dot(fill_opacity=0), 199 | *[ 200 | Dot(color=WHITE) 201 | .shift(RIGHT / (i % 2 + 1) * 1.04) 202 | .rotate_about_origin(PI / 3 * i) 203 | for i in range(6) 204 | ], 205 | *[ 206 | DashedLine( 207 | ORIGIN, 208 | RIGHT / (i % 2 + 1) * 1.04, 209 | positive_space_ratio=0.3, 210 | color=WHITE, 211 | ).rotate_about_origin(PI / 3 * i) 212 | for i in range(6) 213 | ], 214 | *[ 215 | Vector(RIGHT / (i % 2 + 1), color=RED) 216 | .shift(RIGHT / (i % 2 + 1) * 1.04) 217 | .rotate(-PI / 2, about_point=(RIGHT / (i % 2 + 1) * 1.04)) 218 | .rotate_about_origin(PI / 3 * i) 219 | for i in range(6) 220 | ], 221 | ).move_to(wheel.get_center()) 222 | v.shift(v.get_center() - v[0].get_center()) 223 | v.add_updater( 224 | lambda m, dt: m.rotate(-wheel.w * dt, about_point=wheel.get_center()) 225 | ) 226 | omega = MathTex(r"\omega", font=FONT, color=BLACK).shift(0.4 * UP) 227 | self.play(Create(v)) 228 | self.play(Write(omega)) 229 | 230 | self.wait(5) 231 | wheel.clear_updaters() 232 | 233 | # Reverse start slow-mo 234 | d = TAU * 0.75 235 | k *= -1 236 | self.play(Uncreate(v), Unwrite(omega), run_time=0.2) 237 | self.play( 238 | Rotating(wheel, OUT, -d), 239 | self.camera.frame.animate.shift(UP).scale(1.25), 240 | rate_func=get_slow_mo(k), 241 | run_time=d * time_adj(k) / wheel.w, 242 | ) 243 | wheel.w *= speed_adj(k) 244 | # Reverse end slow-mo 245 | 246 | self.wait(0.5) 247 | 248 | h1 = ( 249 | VGroup( 250 | Text( 251 | "Translational + Rotational \n when", 252 | color=BLACK, 253 | line_spacing=0.8, 254 | ), 255 | Text( 256 | "V꜀=Rω", 257 | color=BLACK, 258 | ).shift(0.5 * DOWN), 259 | ) 260 | .scale(0.6) 261 | .shift(2 * UP) 262 | ) 263 | self.play(LaggedStart(Transform(head, head2), Write(h1), lag_ratio=0.4)) 264 | self.wait() 265 | 266 | self.play( 267 | wheel.animate.shift(4 * LEFT), 268 | ground[0].animate.shift(4 * LEFT), 269 | ) 270 | self.wait() 271 | wheel.speed = SPEED 272 | wheel.w = wheel.speed / 1.2 273 | 274 | # Start slow-mo 275 | d = 3.8 276 | k = 1.005 277 | self.play( 278 | RotatingAndShifting(wheel, d * RIGHT, -d / 1.2), 279 | self.camera.frame.animate.scale(0.7).shift(1.5 * DOWN), 280 | rate_func=get_slow_mo(k), 281 | run_time=d * time_adj(k) / wheel.speed, 282 | ) 283 | wheel.speed *= speed_adj(k) 284 | wheel.w *= speed_adj(k) 285 | # End slow-mo 286 | self.remove(h1[0]) 287 | h1[1].scale(1.4).shift(0.3 * UP) 288 | 289 | v = VGroup( 290 | Vector(RIGHT / 2, color=BLUE_D), 291 | *[ 292 | Vector(RIGHT / 2, color=BLUE_D) 293 | .shift(RIGHT * 1.06) 294 | .rotate_about_origin(-PI / 2 * i + PI / 1.8) 295 | for i in [0, 1, 2, 2.5] 296 | ], 297 | *[ 298 | Vector(RIGHT / 2, color=RED) 299 | .shift(RIGHT * 1.06) 300 | .rotate(-PI / 2, about_point=(RIGHT * 1.06)) 301 | .rotate_about_origin(-PI / 2 * i + PI / 1.8) 302 | for i in [0, 1, 2, 2.5] 303 | ], 304 | Dot(color=WHITE).set_z_index(2), 305 | *[ 306 | Dot(color=WHITE) 307 | .set_z_index(2) 308 | .shift(RIGHT * 1.06) 309 | .rotate_about_origin(-PI / 2 * i + PI / 1.8) 310 | for i in [0, 1, 2, 2.5] 311 | ], 312 | ) 313 | wheel.add_updater(lambda m, dt: m.shift(RIGHT * m.speed * dt).rotate(-m.w * dt)) 314 | v.add_updater( 315 | lambda v, dt: v.move_to(wheel.get_center()) 316 | .shift(v.get_center() - v[9].get_center()) 317 | .rotate(-wheel.w * dt, about_point=v[9].get_center()) 318 | ) 319 | 320 | for i in v[:5]: 321 | i.add_updater(lambda m: m.set_angle(0)) 322 | 323 | self.play(Create(v), run_time=0.5) 324 | self.wait(3) 325 | v.clear_updaters() 326 | wheel.clear_updaters() 327 | t1 = Text("Point at rest", font=FONT, color=BLACK).scale(0.5).shift(3 * DOWN) 328 | t2 = ( 329 | MathTex("V_{c}", color=BLACK) 330 | .scale(0.6) 331 | .shift(3.05 * DOWN) 332 | .set_stroke(width=1) 333 | ) 334 | t3 = ( 335 | MathTex("V_{c}", "-", "R\omega", color=BLACK) 336 | .scale(0.6) 337 | .shift(3.05 * DOWN) 338 | .set_stroke(width=1) 339 | ) 340 | t4 = ( 341 | MathTex("V_{c}", "-", "R\omega", "=", "0", color=BLACK) 342 | .scale(0.6) 343 | .shift(3.05 * DOWN) 344 | .set_stroke(width=1) 345 | ) 346 | t5 = ( 347 | MathTex("V_{c}", "=", "R\omega", color=BLACK) 348 | .scale(0.6) 349 | .shift(3.05 * DOWN) 350 | .set_stroke(width=1) 351 | ) 352 | 353 | vv1 = v[-2].copy() 354 | self.play(LaggedStart(Write(t1), Indicate(vv1), lag_ratio=0.5)) 355 | self.remove(vv1) 356 | self.play(t1.animate.shift(2 * LEFT), run_time=0.5) 357 | vv2 = v[3].copy().set_color(YELLOW) 358 | self.play(LaggedStart(Write(t2), Wiggle(vv2), lag_ratio=0.5)) 359 | self.remove(vv2) 360 | vv3 = v[7].copy().set_color(YELLOW) 361 | self.play(LaggedStart(TransformMatchingTex(t2, t3), Wiggle(vv3), lag_ratio=0.5)) 362 | self.remove(vv3) 363 | self.wait(0.5) 364 | self.play(TransformMatchingTex(t3, t4)) 365 | self.wait(0.5) 366 | self.play(FadeOut(t1), Transform(t4, t5)) 367 | self.wait(2) 368 | vv5 = v[5].copy() 369 | vv2 = v[2].copy() 370 | vv6 = v[6].copy() 371 | fv26 = Vector(DR / 2, color=YELLOW_E) 372 | fv26.shift(v[-3].get_center() - fv26.get_start()) 373 | vv4 = v[4].copy() 374 | vv8 = v[8].copy() 375 | fv48 = Vector(RIGHT * 0.293 / 2.2 + UP * 0.707 / 2.2, color=YELLOW_E) 376 | fv48.shift(v[-1].get_center() - fv48.get_start()) 377 | self.remove(v[2], v[6], v[4], v[8]) 378 | self.play( 379 | Uncreate(v[3]), 380 | Uncreate(v[7]), 381 | Uncreate(v[1]), 382 | Uncreate(v[5]), 383 | Transform(vv2, fv26), 384 | Transform(vv6, fv26), 385 | Transform(vv4, fv48), 386 | Transform(vv8, fv48), 387 | vv5.animate.scale( 388 | 2, scale_tips=True, about_point=v[5].get_start() 389 | ).set_color(YELLOW_E), 390 | ) 391 | 392 | self.wait(3) 393 | 394 | # Reverse start slow-mo 395 | d = TAU * 0.75 396 | k *= -1 397 | self.play( 398 | FadeOut(vv5), 399 | FadeOut(vv2), 400 | FadeOut(vv4), 401 | FadeOut(v[0]), 402 | FadeOut(vv6), 403 | FadeOut(vv8), 404 | FadeOut(t4), 405 | # run_time=0.4, 406 | RotatingAndShifting(wheel, d * RIGHT, -d / 1.2), 407 | self.camera.frame.animate.shift(1.5 * UP).scale(1 / 0.7), 408 | rate_func=get_slow_mo(k), 409 | run_time=d * time_adj(k) / wheel.speed, 410 | ) 411 | self.remove(v) 412 | wheel.speed *= speed_adj(k) 413 | wheel.w *= speed_adj(k) 414 | # Reverse end slow-mo 415 | self.wait() 416 | banner = ManimBanner(dark_theme=False).move_to(8 * DOWN).scale(0.6) 417 | l = ( 418 | Text("", font=FONT, color="#a6a6a6") 419 | .next_to(banner, DOWN, 1.5) 420 | .scale(0.5) 421 | ) 422 | self.wait() 423 | self.play( 424 | LaggedStart( 425 | self.camera.frame.animate.shift(8 * DOWN), 426 | banner.create(), 427 | Write(l), 428 | lag_ratio=0.2, 429 | ) 430 | ) 431 | self.play(banner.expand()) 432 | self.wait() 433 | self.play(Unwrite(banner), Unwrite(l)) 434 | 435 | 436 | class RotatingAndShifting(Rotating): 437 | def __init__(self, mobject, shift, radians, **kwargs): 438 | self.shift = shift 439 | self.radians = radians 440 | super().__init__(mobject, OUT, radians, **kwargs) 441 | 442 | def interpolate_mobject(self, alpha): 443 | self.mobject.become(self.starting_mobject) 444 | self.mobject.rotate(alpha * self.radians).shift(alpha * self.shift) 445 | 446 | 447 | class ShiftWiggle(Wiggle): 448 | def __init__( 449 | self, 450 | mobject, 451 | amp: float = 0.4, 452 | direction: np.ndarray = RIGHT, 453 | n_wiggles: int = 6, 454 | run_time: float = 2, 455 | **kwargs 456 | ): 457 | self.amp = amp 458 | self.direction = direction 459 | self.n_wiggles = n_wiggles 460 | super().__init__(mobject, 1, 0, run_time=run_time, **kwargs) 461 | 462 | def interpolate_submobject(self, submobject, starting_submobject, alpha): 463 | submobject.points[:, :] = starting_submobject.points 464 | submobject.shift(wiggle(alpha, self.n_wiggles) * self.amp * self.direction) 465 | -------------------------------------------------------------------------------- /shatter.py: -------------------------------------------------------------------------------- 1 | import random 2 | from manim import * 3 | from manim_physics import * 4 | 5 | from manim.utils.rate_functions import ( 6 | ease_in_circ, 7 | ease_out_quad, 8 | ease_out_quart, 9 | ) 10 | 11 | FONT = "product sans" 12 | BLACK = "#343434" 13 | SLATE = "#a2a2a2" 14 | WHITE = "#ece6e2" 15 | W = config.frame_width 16 | H = config.frame_height 17 | config.background_color = WHITE 18 | 19 | 20 | class Test(SpaceScene, MovingCameraScene): 21 | def setup(self): 22 | self.add(self.space) 23 | ChangeSpeed.add_updater(self.space, lambda space, dt: space.space.step(dt)) 24 | 25 | def construct(self): 26 | config = {"stroke_color": SLATE, "stroke_width": 2, "stroke_opacity": 0.2} 27 | grid = NumberPlane( 28 | background_line_style=config, 29 | axis_config={"stroke_color": WHITE, "stroke_opacity": 0}, 30 | x_range=(-W, W * 20, 1.5), 31 | y_range=(-H, H * 19, 1.5), 32 | ) 33 | 34 | ground = Line(LEFT * 4, RIGHT * 4).shift(DOWN * 1.53).set_color(BLACK) 35 | 36 | square = ( 37 | Square(1, fill_opacity=1) 38 | .set_color(BLACK) 39 | .shift(UP * 3) 40 | .rotate(PI / 15) 41 | .shift(LEFT) 42 | ) 43 | 44 | self.make_static_body(ground) 45 | self.make_rigid_body(square, elasticity=0.2, friction=1) 46 | 47 | self.add(grid, square) 48 | self.wait(3) 49 | 50 | self.remove(square) 51 | square = ( 52 | Square(1, fill_opacity=1).set_color(BLACK).shift(DOWN) 53 | ) # .move_to(square) 54 | self.add(grid, square) 55 | 56 | self.play( 57 | Rotating( 58 | square, 59 | radians=PI / 4, 60 | about_point=square.get_corner(DL), 61 | run_time=0.6, 62 | rate_func=ease_out_quart, 63 | ), 64 | ) 65 | # square.rotate(PI / 4, about_point=square.get_corner(DL)) 66 | 67 | def lerp(a, b, t): 68 | return a * (1 - pow(t, 2)) + b * pow(t, 2) 69 | 70 | def test(x): 71 | test.t += 0.015 72 | if test.t > 1: 73 | test.t = 1 74 | p = square.body.position 75 | x.move_to(lerp(x.get_center(), np.array([p.x, p.y, 0]), test.t)) 76 | 77 | test.t = 0 78 | self.camera.frame.add_updater(test) 79 | self.make_rigid_body(square) 80 | self.play(self.camera.frame.animate.scale(1), run_time=0.1) 81 | square.body.apply_impulse_at_local_point((30, 4), (0.06, 0.06)) 82 | self.wait() 83 | self.camera.frame.clear_updaters() 84 | 85 | v = square.body.velocity 86 | 87 | pos = square.body.position 88 | self.camera.frame.move_to(square) 89 | 90 | def update_grid(x, dt): 91 | update_grid.y -= dt * 5 92 | return x.shift(-np.array((v.x, update_grid.y, 0)) * dt) 93 | 94 | update_grid.y = v.y 95 | 96 | ChangeSpeed.add_updater(grid, update_grid) 97 | 98 | def update_square(x): 99 | x.body.position = pos 100 | x.body.velocity = 0, 0 101 | 102 | square.add_updater(update_square) 103 | self.play( 104 | self.camera.frame.animate(run_time=0.5).scale(0.35), 105 | rate_func=ease_in_circ, 106 | ) 107 | # grid.clear_updaters() 108 | self.play( 109 | ChangeSpeed( 110 | AnimationGroup( 111 | Wait(2), 112 | self.camera.frame.animate( 113 | run_time=0.2, rate_func=ease_out_quad 114 | ).scale(1 / 0.35), 115 | lag_ratio=1, 116 | ), 117 | {0: 0.1, 0.5: 0.5, 0.8: 0.5, 1: 1}, 118 | ), 119 | ) 120 | 121 | wall = Rectangle(color=BLACK, fill_opacity=1, height=2 * H, width=4).move_to( 122 | self.camera.frame.get_center() + (W / 2 + 2) * RIGHT 123 | ) 124 | 125 | self.play( 126 | ChangeSpeed( 127 | wall.animate(rate_func=linear, run_time=4 / v.x).shift(LEFT * 4), 128 | {0.4: 0.05, 0.7: 0.05, 1: 1}, 129 | ), 130 | ) 131 | grid.clear_updaters() 132 | square.remove_updater(update_square) 133 | square.body.velocity = v.x, 0 134 | square.body.position = ( 135 | square.body.position.x + v.x / 60, 136 | square.body.position.y, 137 | ) 138 | # self.add(wall.shift(LEFT * 4)) 139 | self.wait(0.15) 140 | # self.play(ChangeSpeed(Wait(0.15), {0.2: 0.05, 0.5: 0.05, 1: 1})) 141 | 142 | particles = VGroup( 143 | *[ 144 | Poly( 145 | [-0.2, -0.12, 0], 146 | [0.2, -0.12, 0], 147 | [(random.random() - 0.5) * 0.4, 0.12, 0], 148 | color=BLACK, 149 | fill_opacity=1, 150 | ) 151 | .scale(random.random() * 1.2) 152 | .move_to(square) 153 | .shift( 154 | [ 155 | random.random() * 2, 156 | 1.5 * (random.random() * 2 - 1), 157 | 0, 158 | ] 159 | ) 160 | for i in range(40) 161 | ] 162 | ).rotate( 163 | angle_between_vectors(square.get_corner(UR), RIGHT), 164 | about_point=square.get_center(), 165 | ) 166 | self.add(particles) 167 | self.make_rigid_body(particles) 168 | for i in particles: 169 | i.body.apply_impulse_at_local_point((0, 0.01), (1, 0)) 170 | i.body.velocity = ( 171 | (random.random() - 1) * 40, 172 | (random.random() - 0.5) * 20, 173 | ) 174 | self.play(ChangeSpeed(Wait(), {0: 2, 0.2: 0.05, 0.4: 0.05, 0.5: 1})) 175 | 176 | self.wait(3) 177 | banner = ( 178 | ManimBanner(False) 179 | .scale(0.5) 180 | .move_to(self.camera.frame.get_center() + 4 * LEFT) 181 | .shift(UP * 0.5) 182 | ) 183 | l = ( 184 | Text("", font=FONT, color="#a6a6a6") 185 | .next_to(banner, DOWN, 1.5) 186 | .scale(0.5) 187 | ) 188 | self.play( 189 | banner.create(), self.camera.frame.animate.shift(LEFT * 4), FadeOut(grid) 190 | ) 191 | self.play(banner.expand(), Write(l)) 192 | self.wait() 193 | self.play(Unwrite(banner), Unwrite(l)) 194 | 195 | 196 | class Poly(Polygon): 197 | def __init__(self, *vertices, **kwargs): 198 | if len(vertices) == 0: 199 | vertices = [[-0.2, -0.12, 0], [0.2, -0.12, 0], [0, 0.12, 0]] 200 | super().__init__(*vertices, **kwargs) 201 | --------------------------------------------------------------------------------