├── .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 |
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 |
--------------------------------------------------------------------------------