3b1b-manim/for_3b1b_videos/common_scenes.py

233 lines
7.6 KiB
Python
Raw Normal View History

from __future__ import absolute_import
import random
2017-03-08 15:18:09 -08:00
from constants import *
2017-03-08 15:18:09 -08:00
from animation.animation import Animation
from animation.composition import LaggedStart
from animation.creation import DrawBorderThenFill
from animation.creation import Write
from animation.creation import FadeIn
from animation.creation import FadeOut
from mobject.svg.tex_mobject import TextMobject
from mobject.types.vectorized_mobject import VGroup
from scene.scene import Scene
from for_3b1b_videos.pi_creature_animations import Blink
from for_3b1b_videos.pi_creature import Mortimer
from for_3b1b_videos.pi_creature import Randolph
from mobject.geometry import DashedLine
from mobject.geometry import Rectangle
from mobject.geometry import Square
from mobject.svg.drawings import PatreonLogo
2017-03-08 15:18:09 -08:00
2017-03-08 15:18:09 -08:00
class OpeningQuote(Scene):
CONFIG = {
"quote": [],
"quote_arg_separator": " ",
"highlighted_quote_terms": {},
"author": "",
"fade_in_kwargs": {
"submobject_mode": "lagged_start",
"rate_func": None,
"lag_factor": 4,
"run_time": 5,
2017-03-08 15:18:09 -08:00
},
2018-04-11 16:58:49 +02:00
"text_size" : "\\Large",
"use_quotation_marks": True,
"top_buff" : 1.0,
"author_buff": 1.0,
2017-03-08 15:18:09 -08:00
}
2017-03-08 15:18:09 -08:00
def construct(self):
2017-04-12 09:06:04 -07:00
self.quote = self.get_quote()
self.author = self.get_author(self.quote)
2017-03-08 15:18:09 -08:00
2017-04-12 09:06:04 -07:00
self.play(FadeIn(self.quote, **self.fade_in_kwargs))
2018-01-15 19:15:05 -08:00
self.wait(2)
2018-04-11 16:58:49 +02:00
self.play(Write(self.author, run_time = 3))
2018-01-15 19:15:05 -08:00
self.wait()
2017-03-08 15:18:09 -08:00
def get_quote(self, max_width=FRAME_WIDTH - 1):
2017-03-22 15:06:17 -07:00
text_mobject_kwargs = {
"alignment": "",
"arg_separator": self.quote_arg_separator,
2017-03-22 15:06:17 -07:00
}
2017-03-08 15:18:09 -08:00
if isinstance(self.quote, str):
2018-04-11 16:58:49 +02:00
if self.use_quotation_marks:
quote = TextMobject("``%s''" %
self.quote.strip(), **text_mobject_kwargs)
else:
quote = TextMobject("%s" %
self.quote.strip(), **text_mobject_kwargs)
2017-03-08 15:18:09 -08:00
else:
2018-04-11 16:58:49 +02:00
if self.use_quotation_marks:
words = [self.text_size + " ``"] + list(self.quote) + ["''"]
else:
words = [self.text_size] + list(self.quote)
2017-03-22 15:06:17 -07:00
quote = TextMobject(*words, **text_mobject_kwargs)
# TODO, make less hacky
if self.quote_arg_separator == " ":
quote[0].shift(0.2 * RIGHT)
quote[-1].shift(0.2 * LEFT)
2018-04-11 16:58:49 +02:00
for term, color in self.highlighted_quote_terms:
2018-03-30 11:51:31 -07:00
quote.set_color_by_tex(term, color)
2018-04-11 16:58:49 +02:00
quote.to_edge(UP, buff = self.top_buff)
2017-03-08 15:18:09 -08:00
if quote.get_width() > max_width:
quote.scale_to_fit_width(max_width)
return quote
def get_author(self, quote):
2018-04-11 16:58:49 +02:00
author = TextMobject(self.text_size + " --" + self.author)
author.next_to(quote, DOWN, buff = self.author_buff)
2018-03-30 11:51:31 -07:00
author.set_color(YELLOW)
2017-03-08 15:18:09 -08:00
return author
2017-03-08 15:18:09 -08:00
class PatreonThanks(Scene):
CONFIG = {
"specific_patrons": [],
"max_patron_group_size": 20,
"patron_scale_val": 0.8,
2017-03-08 15:18:09 -08:00
}
2017-03-08 15:18:09 -08:00
def construct(self):
morty = Mortimer()
morty.next_to(ORIGIN, DOWN)
patreon_logo = PatreonLogo()
patreon_logo.to_edge(UP)
2017-03-08 15:18:09 -08:00
n_patrons = len(self.specific_patrons)
2017-04-14 23:30:29 -07:00
patrons = map(TextMobject, self.specific_patrons)
num_groups = float(len(patrons)) / self.max_patron_group_size
2017-04-20 13:30:51 -07:00
proportion_range = np.linspace(0, 1, num_groups + 1)
indices = (len(patrons) * proportion_range).astype('int')
2017-04-20 13:30:51 -07:00
patron_groups = [
VGroup(*patrons[i:j])
for i, j in zip(indices, indices[1:])
]
2017-04-20 13:30:51 -07:00
for i, group in enumerate(patron_groups):
left_group = VGroup(*group[:len(group) / 2])
right_group = VGroup(*group[len(group) / 2:])
for subgroup, vect in (left_group, LEFT), (right_group, RIGHT):
subgroup.arrange_submobjects(DOWN, aligned_edge=LEFT)
subgroup.scale(self.patron_scale_val)
subgroup.to_edge(vect)
last_group = None
2017-04-14 23:30:29 -07:00
for i, group in enumerate(patron_groups):
anims = []
if last_group is not None:
self.play(
FadeOut(last_group),
morty.look, UP + LEFT
)
else:
anims += [
DrawBorderThenFill(patreon_logo),
]
self.play(
LaggedStart(
FadeIn, group,
run_time=2,
2017-04-14 23:30:29 -07:00
),
morty.change, "gracious", group.get_corner(UP + LEFT),
*anims
)
self.play(morty.look_at, group.get_corner(DOWN + LEFT))
self.play(morty.look_at, group.get_corner(UP + RIGHT))
self.play(morty.look_at, group.get_corner(DOWN + RIGHT))
2017-04-14 23:30:29 -07:00
self.play(Blink(morty))
last_group = group
2017-03-08 15:18:09 -08:00
2017-12-08 17:57:32 -08:00
class PatreonEndScreen(PatreonThanks):
CONFIG = {
"n_patron_columns": 3,
"max_patron_width": 3,
"run_time": 20,
"randomize_order": True,
2017-12-08 17:57:32 -08:00
}
2017-12-08 17:57:32 -08:00
def construct(self):
2018-03-04 10:43:35 -08:00
if self.randomize_order:
random.shuffle(self.specific_patrons)
2017-12-08 17:57:32 -08:00
self.add_title()
self.scroll_through_patrons()
def add_title(self):
2018-03-14 14:52:33 -07:00
title = self.title = TextMobject("Clicky Stuffs")
2017-12-08 17:57:32 -08:00
title.scale(1.5)
title.to_edge(UP, buff=MED_SMALL_BUFF)
2017-12-08 17:57:32 -08:00
2018-03-22 11:06:20 -07:00
randy, morty = self.pi_creatures = VGroup(Randolph(), Mortimer())
2017-12-08 17:57:32 -08:00
for pi, vect in (randy, LEFT), (morty, RIGHT):
pi.scale_to_fit_height(title.get_height())
pi.change_mode("thinking")
pi.look(DOWN)
pi.next_to(title, vect, buff=MED_LARGE_BUFF)
2017-12-08 17:57:32 -08:00
self.add_foreground_mobjects(title, randy, morty)
def scroll_through_patrons(self):
logo_box = Square(side_length=2.5)
logo_box.to_corner(DOWN + LEFT, buff=MED_LARGE_BUFF)
total_width = FRAME_X_RADIUS - logo_box.get_right()[0]
2017-12-08 17:57:32 -08:00
black_rect = Rectangle(
fill_color=BLACK,
fill_opacity=1,
stroke_width=0,
width=FRAME_WIDTH,
height=1.1 * FRAME_Y_RADIUS
2017-12-08 17:57:32 -08:00
)
black_rect.to_edge(UP, buff=0)
line = DashedLine(FRAME_X_RADIUS * LEFT, FRAME_X_RADIUS * RIGHT)
2017-12-08 17:57:32 -08:00
line.move_to(black_rect, DOWN)
line.shift(SMALL_BUFF * SMALL_BUFF * DOWN)
2017-12-08 17:57:32 -08:00
self.add(line)
patrons = VGroup(*map(TextMobject, self.specific_patrons))
patrons.scale(self.patron_scale_val)
for patron in patrons:
if patron.get_width() > self.max_patron_width:
patron.scale_to_fit_width(self.max_patron_width)
columns = VGroup(*[
VGroup(
*patrons[i::self.n_patron_columns]
).arrange_submobjects(DOWN, buff=MED_SMALL_BUFF)
2017-12-08 17:57:32 -08:00
for i in range(self.n_patron_columns)
])
columns.arrange_submobjects(
RIGHT, buff=LARGE_BUFF,
aligned_edge=UP,
2017-12-08 17:57:32 -08:00
)
columns.scale_to_fit_width(total_width - 1)
columns.next_to(black_rect, DOWN, 3 * LARGE_BUFF)
2017-12-08 17:57:32 -08:00
columns.to_edge(RIGHT)
self.play(
columns.next_to, FRAME_Y_RADIUS * DOWN, UP, LARGE_BUFF,
columns.to_edge, RIGHT,
2017-12-08 17:57:32 -08:00
Animation(black_rect),
rate_func=None,
run_time=self.run_time,
2017-12-08 17:57:32 -08:00
)
2017-06-20 14:05:48 -07:00
class ExternallyAnimatedScene(Scene):
def construct(self):
raise Exception("Don't actually run this class.")
2017-03-08 15:18:09 -08:00
2017-08-05 20:47:06 -07:00
class TODOStub(Scene):
CONFIG = {
"message": ""
2017-08-05 20:47:06 -07:00
}
2017-08-05 20:47:06 -07:00
def construct(self):
self.add(TextMobject("TODO: %s" % self.message))
2018-01-15 19:15:05 -08:00
self.wait()