Merge pull request #752 from 3b1b/hyperdarts

Hyperdarts
This commit is contained in:
Grant Sanderson
2019-10-09 21:33:02 -07:00
committed by GitHub
10 changed files with 5929 additions and 13 deletions

View File

@ -0,0 +1,920 @@
from manimlib.imports import *
OUTPUT_DIRECTORY = "hyperdarts"
class HyperdartScene(Scene):
CONFIG = {
"square_width": 6,
"square_style": {
"stroke_width": 2,
"fill_color": BLUE,
"fill_opacity": 0.5,
},
"circle_style": {
"fill_color": RED_E,
"fill_opacity": 1,
"stroke_width": 0,
},
"circle_center_dot_radius": 0.025,
"default_line_style": {
"stroke_width": 2,
"stroke_color": WHITE,
},
"default_dot_config": {
"fill_color": WHITE,
"background_stroke_width": 1,
"background_stroke_color": BLACK,
"radius": 0.5 * DEFAULT_DOT_RADIUS,
},
"dart_sound": "dart_low",
"default_bullseye_shadow_opacity": 0.35,
}
def setup(self):
self.square = self.get_square()
self.circle = self.get_circle()
self.circle_center_dot = self.get_circle_center_dot()
self.add(self.square)
self.add(self.circle)
self.add(self.circle_center_dot)
def get_square(self):
return Square(
side_length=self.square_width,
**self.square_style
)
def get_circle(self, square=None):
square = square or self.square
circle = Circle(**self.circle_style)
circle.replace(square)
return circle
def get_circle_center_dot(self, circle=None):
circle = circle or self.circle
return Dot(
circle.get_center(),
radius=self.circle_center_dot_radius,
fill_color=BLACK,
)
def get_number_plane(self):
square = self.square
unit_size = square.get_width() / 2
plane = NumberPlane(
axis_config={
"unit_size": unit_size,
}
)
plane.add_coordinates()
plane.shift(square.get_center() - plane.c2p(0, 0))
return plane
def get_random_points(self, n):
square = self.square
points = np.random.uniform(-1, 1, 3 * n).reshape((n, 3))
points[:, 0] *= square.get_width() / 2
points[:, 1] *= square.get_height() / 2
points[:, 2] = 0
points += square.get_center()
return points
def get_random_point(self):
return self.get_random_points(1)[0]
def get_dot(self, point):
return Dot(point, **self.default_dot_config)
# Hit transform rules
def is_inside(self, point, circle=None):
circle = circle or self.circle
return get_norm(point - circle.get_center()) <= circle.get_width() / 2
def get_new_radius(self, point, circle=None):
circle = circle or self.circle
center = circle.get_center()
radius = circle.get_width() / 2
p_dist = get_norm(point - center)
return np.sqrt(radius**2 - p_dist**2)
def get_hit_distance_line(self, point, circle=None):
circle = circle or self.circle
line = Line(
circle.get_center(), point,
**self.default_line_style
)
return line
def get_chord(self, point, circle=None):
circle = circle or self.circle
center = circle.get_center()
p_angle = angle_of_vector(point - center)
chord = Line(DOWN, UP)
new_radius = self.get_new_radius(point, circle)
chord.scale(new_radius)
chord.rotate(p_angle)
chord.move_to(point)
chord.set_style(**self.default_line_style)
return chord
def get_radii_to_chord(self, chord, circle=None):
circle = circle or self.circle
center = circle.get_center()
radii = VGroup(*[
DashedLine(center, point)
for point in chord.get_start_and_end()
])
radii.set_style(**self.default_line_style)
return radii
def get_all_hit_lines(self, point, circle=None):
h_line = self.get_hit_distance_line(point, circle)
chord = self.get_chord(point, circle)
radii = self.get_radii_to_chord(chord, circle)
return VGroup(h_line, chord, radii)
def get_dart(self, length=1.5):
dart = SVGMobject(file_name="dart")
dart.rotate(135 * DEGREES)
dart.set_width(length)
dart.rotate(45 * DEGREES, UP)
dart.rotate(-10 * DEGREES)
dart.set_fill(GREY)
dart.set_sheen(2, UL)
dart.set_stroke(BLACK, 0.5, background=True)
return dart
# New circle
def get_new_circle_from_point(self, point, circle=None):
return self.get_new_circle(
self.get_new_radius(point, circle),
circle,
)
def get_new_circle_from_chord(self, chord, circle=None):
return self.get_new_circle(
chord.get_length() / 2,
circle,
)
def get_new_circle(self, new_radius, circle=None):
circle = circle or self.circle
new_circle = self.get_circle()
new_circle.set_width(2 * new_radius)
new_circle.move_to(circle)
return new_circle
# Sound
def add_dart_sound(self, time_offset=0, gain=-20, **kwargs):
self.add_sound(
self.dart_sound,
time_offset=time_offset,
gain=-20,
**kwargs,
)
# Animations
def show_full_hit_process(self, point, pace="slow", with_dart=True):
assert(pace in ["slow", "fast"])
to_fade = VGroup()
if with_dart:
dart, dot = self.show_hit_with_dart(point)
to_fade.add(dart, dot)
else:
dot = self.show_hit(point)
to_fade.add(dot)
if pace == "slow":
self.wait(0.5)
# TODO, automatically act based on hit or miss?
lines = self.show_geometry(point, pace)
chord_and_shadow = self.show_circle_shrink(lines[1], pace=pace)
to_fade.add_to_back(chord_and_shadow, lines)
self.play(
FadeOut(to_fade),
run_time=(1 if pace == "slow" else 0.5)
)
def show_hits_with_darts(self, points, run_time=0.5, added_anims=None):
if added_anims is None:
added_anims = []
darts = VGroup(*[
self.get_dart().move_to(point, DR)
for point in points
])
dots = VGroup(*[
self.get_dot(point)
for point in points
])
for dart in darts:
dart.save_state()
dart.set_x(-(FRAME_WIDTH + dart.get_width()) / 2)
dart.rotate(20 * DEGREES)
n_points = len(points)
self.play(
ShowIncreasingSubsets(
dots,
rate_func=squish_rate_func(linear, 0.5, 1),
),
LaggedStart(*[
Restore(
dart,
path_arc=-20 * DEGREES,
rate_func=linear,
run_time=run_time,
)
for dart in darts
], lag_ratio=(1 / n_points)),
*added_anims,
run_time=run_time
)
for n in range(n_points):
self.add_dart_sound(
time_offset=(-n / (2 * n_points))
)
return darts, dots
def show_hit_with_dart(self, point, run_time=0.25, **kwargs):
darts, dots = self.show_hits_with_darts([point], run_time, **kwargs)
return darts[0], dots[0]
def show_hit(self, point, pace="slow", added_anims=None):
assert(pace in ["slow", "fast"])
if added_anims is None:
added_anims = []
dot = self.get_dot(point)
if pace == "slow":
self.play(
FadeInFromLarge(dot, rate_func=rush_into),
*added_anims,
run_time=0.5,
)
elif pace == "fast":
self.add(dot)
# self.add_dart_sound()
return dot
def show_geometry(self, point, pace="slow"):
assert(pace in ["slow", "fast"])
lines = self.get_all_hit_lines(point, self.circle)
h_line, chord, radii = lines
if pace == "slow":
self.play(
ShowCreation(h_line),
GrowFromCenter(chord),
)
self.play(*map(ShowCreation, radii))
elif pace == "fast":
self.play(
ShowCreation(h_line),
GrowFromCenter(chord),
*map(ShowCreation, radii),
run_time=0.5
)
return lines
def show_circle_shrink(self, chord, pace="slow", shadow_opacity=None):
circle = self.circle
chord_copy = chord.copy()
new_circle = self.get_new_circle_from_chord(chord)
to_fade = VGroup(chord_copy)
if shadow_opacity is None:
shadow_opacity = self.default_bullseye_shadow_opacity
if shadow_opacity > 0:
shadow = circle.copy()
shadow.set_opacity(shadow_opacity)
to_fade.add_to_back(shadow)
if circle in self.mobjects:
index = self.mobjects.index(circle)
self.mobjects.insert(index, shadow)
else:
self.add(shadow, self.circle_center_dot)
outline = VGroup(*[
VMobject().pointwise_become_partial(new_circle, a, b)
for (a, b) in [(0, 0.5), (0.5, 1)]
])
outline.rotate(chord.get_angle())
outline.set_fill(opacity=0)
outline.set_stroke(YELLOW, 2)
assert(pace in ["slow", "fast"])
if pace == "slow":
self.play(
chord_copy.move_to, circle.get_center(),
circle.set_opacity, 0.5,
)
self.play(
Rotating(
chord_copy,
radians=PI,
),
ShowCreation(
outline,
lag_ratio=0
),
run_time=1,
rate_func=smooth,
)
self.play(
Transform(circle, new_circle),
FadeOut(outline),
)
elif pace == "fast":
outline = new_circle.copy()
outline.set_fill(opacity=0)
outline.set_stroke(YELLOW, 2)
outline.move_to(chord)
outline.generate_target()
outline.target.move_to(circle)
self.play(
chord_copy.move_to, circle,
Transform(circle, new_circle),
# MoveToTarget(
# outline,
# remover=True
# )
)
# circle.become(new_circle)
# circle.become(new_circle)
# self.remove(new_circle)
return to_fade
def show_miss(self, point, with_dart=True):
square = self.square
miss = TextMobject("Miss!")
miss.next_to(point, UP)
to_fade = VGroup(miss)
if with_dart:
dart, dot = self.show_hit_with_dart(point)
to_fade.add(dart, dot)
else:
dot = self.show_hit(point)
to_fade.add(dot)
self.play(
ApplyMethod(
square.set_color, YELLOW,
rate_func=lambda t: (1 - t),
),
GrowFromCenter(miss),
run_time=0.25
)
return to_fade
def show_game_over(self):
game_over = TextMobject("GAME OVER")
game_over.set_width(FRAME_WIDTH - 1)
rect = FullScreenFadeRectangle(opacity=0.25)
self.play(
FadeIn(rect),
FadeInFromLarge(game_over),
)
return VGroup(rect, game_over)
class Dartboard(VGroup):
CONFIG = {
"radius": 3,
"n_sectors": 20,
}
def __init__(self, **kwargs):
super().__init__(**kwargs)
n_sectors = self.n_sectors
angle = TAU / n_sectors
segments = VGroup(*[
VGroup(*[
AnnularSector(
inner_radius=in_r,
outer_radius=out_r,
start_angle=n * angle,
angle=angle,
color=color,
)
for n, color in zip(
range(n_sectors),
it.cycle(colors)
)
])
for colors, in_r, out_r in [
([LIGHT_GREY, DARKER_GREY], 0, 1),
([GREEN_E, RED_E], 0.5, 0.55),
([GREEN_E, RED_E], 0.95, 1),
]
])
segments.rotate(-angle / 2)
bullseyes = VGroup(*[
Circle(radius=r)
for r in [0.07, 0.035]
])
bullseyes.set_fill(opacity=1)
bullseyes.set_stroke(width=0)
bullseyes[0].set_color(GREEN_E)
bullseyes[1].set_color(RED_E)
self.add(*segments, *bullseyes)
self.scale(self.radius)
# Problem statement
class IntroduceGame(HyperdartScene):
CONFIG = {
"random_seed": 0,
"square_width": 5,
"num_darts_in_initial_flurry": 5,
}
def construct(self):
self.show_flurry_of_points()
self.show_board_dimensions()
self.introduce_bullseye()
self.show_miss_example()
self.show_shrink_rule()
def show_flurry_of_points(self):
square = self.square
circle = self.circle
title = TextMobject("Hyperdarts")
title.scale(1.5)
title.to_edge(UP)
n = self.num_darts_in_initial_flurry
points = np.random.normal(size=n * 3).reshape((n, 3))
points[:, 2] = 0
points *= 0.75
board = Dartboard()
board.match_width(square)
board.move_to(square)
pre_square = Circle(color=WHITE)
pre_square.replace(square)
self.remove(circle, square)
self.add(board)
darts, dots = self.show_hits_with_darts(
points,
added_anims=[FadeInFromDown(title)]
)
self.wait()
def func(p):
theta = angle_of_vector(p) % (TAU / 4)
if theta > TAU / 8:
theta = TAU / 4 - theta
p *= 1 / np.cos(theta)
return p
self.play(
*[
ApplyPointwiseFunction(func, pieces, run_time=1)
for pieces in [*board[:3], *dots]
],
*[
MaintainPositionRelativeTo(dart, dot)
for dart, dot in zip(darts, dots)
]
)
self.flurry_dots = dots
self.darts = darts
self.title = title
self.board = board
def show_board_dimensions(self):
square = self.square
labels = VGroup(*[
TextMobject("2 ft").next_to(
square.get_edge_center(vect), vect,
)
for vect in [DOWN, RIGHT]
])
labels.set_color(YELLOW)
h_line, v_line = lines = VGroup(*[
DashedLine(
square.get_edge_center(v1),
square.get_edge_center(-v1),
).next_to(label, v2)
for label, v1, v2 in zip(labels, [LEFT, UP], [UP, LEFT])
])
lines.match_color(labels)
self.play(
LaggedStartMap(ShowCreation, lines),
LaggedStartMap(FadeInFromDown, labels),
lag_ratio=0.5
)
self.wait()
self.square_dimensions = VGroup(lines, labels)
def introduce_bullseye(self):
square = self.square
circle = self.circle
board = self.board
circle.save_state()
circle.replace(board[-1])
label = TextMobject("Bullseye")
label.scale(1.5)
label.next_to(square, LEFT, aligned_edge=UP)
label.set_color(RED)
arrow = Arrow(
label.get_bottom(),
circle.get_corner(DR)
)
radius = DashedLine(
square.get_center(),
square.get_left(),
stroke_width=2,
)
radius_label = TextMobject("1 ft")
radius_label.next_to(radius, DOWN, SMALL_BUFF)
self.add(circle, self.square_dimensions)
self.play(
FadeInFromLarge(circle),
FadeInFromDown(label),
ShowCreation(arrow),
LaggedStartMap(FadeOut, self.flurry_dots, run_time=1),
LaggedStartMap(FadeOut, self.darts, run_time=1),
)
self.wait()
self.add(square, board, arrow, circle)
self.play(
Restore(circle),
ApplyMethod(
arrow.scale, 0.4,
{"about_point": arrow.get_start()}
),
)
self.add(radius, self.circle_center_dot)
self.play(
ShowCreation(radius),
FadeInFrom(radius_label, RIGHT),
FadeIn(self.circle_center_dot),
)
self.play(
FadeOut(label),
Uncreate(arrow),
FadeOut(board)
)
self.wait()
s_lines, s_labels = self.square_dimensions
self.play(
FadeOut(s_lines),
FadeOut(radius),
FadeOut(radius_label),
FadeOut(self.title),
)
self.circle_dimensions = VGroup(
radius, radius_label,
)
def show_miss_example(self):
square = self.square
point = square.get_corner(UL) + 0.5 * DR
miss_word = TextMobject("Miss!")
miss_word.scale(1.5)
miss_word.next_to(point, UP, LARGE_BUFF)
dart, dot = self.show_hit_with_dart(point)
self.play(FadeInFromDown(miss_word))
self.wait()
game_over = self.show_game_over()
self.wait()
self.play(
*map(FadeOut, [dart, dot, miss_word, game_over])
)
def show_shrink_rule(self):
circle = self.circle
point = 0.5 * circle.point_from_proportion(0.2)
# First example
self.show_full_hit_process(point)
self.wait()
# Close to border
label = TextMobject("Bad shot $\\Rightarrow$ much shrinkage")
label.scale(1.5)
label.to_edge(UP)
point = 0.98 * circle.point_from_proportion(3 / 8)
circle.save_state()
self.play(FadeInFromDown(label))
self.show_full_hit_process(point)
self.wait()
self.play(Restore(circle))
# Close to center
new_label = TextMobject("Good shot $\\Rightarrow$ less shrinkage")
new_label.scale(1.5)
new_label.to_edge(UP)
point = 0.2 * circle.point_from_proportion(3 / 8)
self.play(
FadeInFromDown(new_label),
FadeOutAndShift(label, UP),
)
self.show_full_hit_process(point)
self.wait()
self.play(FadeOut(new_label))
# Play on
for x in range(3):
r1, r2 = np.random.random(size=2)
point = r1 * circle.point_from_proportion(r2)
self.show_full_hit_process(point)
point = circle.get_right() + 0.5 * UR
self.show_miss(point)
self.wait()
self.show_game_over()
class ShowScoring(HyperdartScene):
def setup(self):
super().setup()
self.add_score_counter()
def construct(self):
self.comment_on_score()
self.show_several_hits()
def comment_on_score(self):
score_label = self.score_label
comment = TextMobject("\\# Bullseyes")
# rect = SurroundingRectangle(comment)
# rect.set_stroke(width=1)
# comment.add(rect)
comment.set_color(YELLOW)
comment.next_to(score_label, DOWN, LARGE_BUFF)
comment.set_x(midpoint(
self.square.get_left(),
LEFT_SIDE,
)[0])
arrow = Arrow(
comment.get_top(),
score_label[1].get_bottom(),
buff=0.2,
)
arrow.match_color(comment)
self.play(
FadeInFromDown(comment),
GrowArrow(arrow),
)
def show_several_hits(self):
points = [UR, DL, 0.5 * UL, 0.5 * DR]
for point in points:
self.show_full_hit_process(point, pace="fast")
self.show_miss(2 * UR)
self.wait()
#
def add_score_counter(self):
score = Integer(0)
score_label = VGroup(
TextMobject("Score: "),
score
)
score_label.arrange(RIGHT, aligned_edge=DOWN)
score_label.scale(1.5)
score_label.to_corner(UL)
self.add(score_label)
self.score = score
self.score_label = score_label
def increment_score(self):
score = self.score
new_score = score.copy()
new_score.increment_value(1)
self.play(
FadeOutAndShift(score, UP),
FadeInFrom(new_score, DOWN),
run_time=1,
)
self.remove(new_score)
score.increment_value()
score.move_to(new_score)
self.add(score)
def show_hit(self, point, *args, **kwargs):
result = super().show_hit(point, *args, **kwargs)
if self.is_inside(point):
self.increment_score()
return result
def show_hit_with_dart(self, point, *args, **kwargs):
result = super().show_hit_with_dart(point, *args, **kwargs)
if self.is_inside(point):
self.increment_score()
return result
class ShowSeveralRounds(ShowScoring):
CONFIG = {
"n_rounds": 5,
}
def construct(self):
for x in range(self.n_rounds):
self.show_single_round()
self.reset_board()
def show_single_round(self, pace="fast"):
while True:
point = self.get_random_point()
if self.is_inside(point):
self.show_full_hit_process(point, pace=pace)
else:
to_fade = self.show_miss(point)
self.wait(0.5)
self.play(
ShowCreationThenFadeAround(self.score_label),
FadeOut(to_fade)
)
return
def reset_board(self):
score = self.score
new_score = score.copy()
new_score.set_value(0)
self.play(
self.circle.match_width, self.square,
FadeOutAndShift(score, UP),
FadeInFrom(new_score, DOWN),
)
score.set_value(0)
self.add(score)
self.remove(new_score)
class ShowSeveralRoundsQuickly(ShowSeveralRounds):
CONFIG = {
"n_rounds": 15,
}
def show_full_hit_process(self, point, *args, **kwargs):
lines = self.get_all_hit_lines(point)
dart = self.show_hit_with_dart(point)
self.add(lines)
self.score.increment_value(1)
to_fade = self.show_circle_shrink(lines[1], pace="fast")
to_fade.add(*lines, *dart)
self.play(FadeOut(to_fade), run_time=0.5)
def increment_score(self):
pass # Handled elsewhere
class ShowSeveralRoundsVeryQuickly(ShowSeveralRoundsQuickly):
def construct(self):
pass
class ShowUniformDistribution(HyperdartScene):
CONFIG = {
"dart_sound": "dart_high",
"n_points": 1000,
}
def construct(self):
self.add_title()
self.show_random_points()
self.exchange_titles()
self.show_random_points()
def get_square(self):
return super().get_square().to_edge(DOWN)
def add_title(self):
# square = self.square
title = TextMobject("All points in the square are equally likely")
title.scale(1.5)
title.to_edge(UP)
new_title = TextMobject("``Uniform distribution'' on the square")
new_title.scale(1.5)
new_title.to_edge(UP)
self.play(FadeInFromDown(title))
self.title = title
self.new_title = new_title
def show_random_points(self):
points = self.get_random_points(self.n_points)
dots = VGroup(*[
Dot(point, radius=0.02)
for point in points
])
dots.set_fill(opacity=0.75)
run_time = 5
self.play(LaggedStartMap(
FadeInFromLarge, dots,
run_time=run_time,
))
for x in range(1000):
self.add_dart_sound(
time_offset=-run_time * np.random.random(),
gain=-10,
gain_to_background=-5,
)
self.wait()
def exchange_titles(self):
self.play(
FadeInFromDown(self.new_title),
FadeOutAndShift(self.title, UP),
)
class ExpectedScoreEqualsQMark(Scene):
def construct(self):
equation = TextMobject(
"\\textbf{E}[Score] = ???",
tex_to_color_map={
"???": YELLOW,
}
)
aka = TextMobject("a.k.a. Long-term average")
aka.next_to(equation, DOWN)
self.play(Write(equation))
self.wait(2)
self.play(FadeInFrom(aka, UP))
self.wait()
class ScoreHistogram(Scene):
CONFIG = {
"axes_config": {
""
}
}
def construct(self):
self.add_axes()
self.add_score_label()
self.setup_histogram()
self.show_many_runs()
def add_axes(self):
axes = Axes(**self.axes_config)
def add_score_label(self):
pass
def setup_histogram(self):
pass
def show_many_runs(self):
pass
#
def add_one_run(self, animate=True):
pass
def get_random_score(self):
pass

4926
active_projects/spirals.py Normal file

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ class AnimationGroup(Animation):
self.group = Group(*remove_list_redundancies(
[anim.mobject for anim in animations]
))
self.init_run_time()
Animation.__init__(self, self.group, **kwargs)
def get_all_mobjects(self):
@ -41,7 +42,7 @@ class AnimationGroup(Animation):
def begin(self):
for anim in self.animations:
anim.begin()
self.init_run_time()
# self.init_run_time()
def finish(self):
for anim in self.animations:

View File

@ -1,5 +1,7 @@
from manimlib.animation.animation import Animation
from manimlib.animation.composition import Succession
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.mobject import Group
from manimlib.utils.bezier import integer_interpolate
from manimlib.utils.config_ops import digest_config
from manimlib.utils.rate_functions import linear
@ -7,6 +9,7 @@ from manimlib.utils.rate_functions import double_smooth
from manimlib.utils.rate_functions import smooth
import numpy as np
import itertools as it
class ShowPartial(Animation):
@ -132,4 +135,42 @@ class ShowIncreasingSubsets(Animation):
def interpolate_mobject(self, alpha):
n_submobs = len(self.all_submobs)
index = int(self.int_func(alpha * n_submobs))
self.update_submobject_list(index)
def update_submobject_list(self, index):
self.mobject.submobjects = self.all_submobs[:index]
class ShowSubmobjectsOneByOne(ShowIncreasingSubsets):
def __init__(self, group, **kwargs):
new_group = Group(*group)
super().__init__(new_group, **kwargs)
def update_submobject_list(self, index):
# N = len(self.all_submobs)
if index == 0:
self.mobject.submobjects = []
else:
self.mobject.submobjects = self.all_submobs[index - 1]
# TODO, this is broken...
class AddTextWordByWord(Succession):
CONFIG = {
# If given a value for run_time, it will
# override the time_per_char
"run_time": None,
"time_per_char": 0.06,
}
def __init__(self, text_mobject, **kwargs):
digest_config(self, kwargs)
tpc = self.time_per_char
anims = it.chain(*[
[
ShowIncreasingSubsets(word, run_time=tpc * len(word)),
Animation(word, run_time=0.005 * len(word)**1.5),
]
for word in text_mobject
])
super().__init__(*anims, **kwargs)

View File

@ -154,8 +154,7 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene):
"randomize_order": True,
"capitalize": True,
"name_y_spacing": 0.7,
# "thanks_words": "Funded by the community, with special thanks to:",
"thanks_words": "Early access, name in credits and more at 3b1b.org/support",
"thanks_words": "My thanks to all the patrons among you",
}
def construct(self):
@ -245,7 +244,7 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene):
columns.next_to(underline, DOWN, buff=2)
columns.generate_target()
columns.target.to_edge(DOWN, buff=2)
columns.target.to_edge(DOWN, buff=4)
vect = columns.target.get_center() - columns.get_center()
distance = get_norm(vect)
wait_time = 20
@ -259,11 +258,15 @@ class PatreonEndScreen(PatreonThanks, PiCreatureScene):
self.wait(wait_time)
def modify_patron_name(self, name):
if name.lower() == "RedAgent14".lower():
return "Brian Shepetofsky"
elif name.lower() == "DeathByShrimp".lower():
return "Henry Bresnahan"
modification_map = {
"RedAgent14": "Brian Shepetofsky",
"DeathByShrimp": "Henry Bresnahan",
"akostrikov": "Aleksandr Kostrikov",
"Jacob Baxter": "Will Fleshman",
}
for n1, n2 in modification_map.items():
if name.lower() == n1.lower():
return n2
return name

View File

@ -202,8 +202,6 @@ class TipableVMobject(VMobject):
return get_norm(start - end)
class Arc(TipableVMobject):
CONFIG = {
"radius": 1.0,

View File

@ -50,6 +50,15 @@ class PMobject(Mobject):
self.color = color
return self
def get_stroke_width(self):
return self.stroke_width
def set_stroke_width(self, width, family=True):
mobs = self.family_members_with_points() if family else [self]
for mob in mobs:
mob.stroke_width = width
return self
# def set_color_by_gradient(self, start_color, end_color):
def set_color_by_gradient(self, *colors):
self.rgbas = np.array(list(map(
@ -158,6 +167,12 @@ class PMobject(Mobject):
self.rgbas = interpolate(
mobject1.rgbas, mobject2.rgbas, alpha
)
self.set_stroke_width(interpolate(
mobject1.get_stroke_width(),
mobject2.get_stroke_width(),
alpha,
))
return self
def pointwise_become_partial(self, mobject, a, b):
lower_index, upper_index = [
@ -206,6 +221,14 @@ class Mobject2D(PMobject):
Mobject.__init__(self, **kwargs)
class PGroup(PMobject):
def __init__(self, *pmobs, **kwargs):
if not all([isinstance(m, PMobject) for m in pmobs]):
raise Exception("All submobjects must be of type PMobject")
super().__init__(**kwargs)
self.add(*pmobs)
class PointCloudDot(Mobject1D):
CONFIG = {
"radius": 0.075,

View File

@ -543,6 +543,8 @@ class Scene(Container):
self.file_writer.write_frame(frame)
def add_sound(self, sound_file, time_offset=0, gain=None, **kwargs):
if self.skip_animations:
return
time = self.get_time() + time_offset
self.file_writer.add_sound(sound_file, time, gain, **kwargs)

View File

@ -88,7 +88,9 @@ class NameAnimationScene(Scene):
return self.animated_name.replace(" ", "") + "Animation"
names = []
names = [
"Happy 18th Birthday\\\\Pranavi Hiremath!"
]
if __name__ == "__main__":
for name in names:

View File

@ -45,7 +45,7 @@ def stage_scenes(module_name):
animation_dir = os.path.join(
os.path.expanduser('~'),
"Dropbox (3Blue1Brown)/3Blue1Brown Team Folder/videos",
"windmill", "1440p60"
"spirals", "1440p60"
)
#
files = os.listdir(animation_dir)