mirror of
https://github.com/3b1b/manim.git
synced 2025-07-29 13:03:31 +08:00
920
active_projects/hyperdarts.py
Normal file
920
active_projects/hyperdarts.py
Normal 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
4926
active_projects/spirals.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -202,8 +202,6 @@ class TipableVMobject(VMobject):
|
||||
return get_norm(start - end)
|
||||
|
||||
|
||||
|
||||
|
||||
class Arc(TipableVMobject):
|
||||
CONFIG = {
|
||||
"radius": 1.0,
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
Reference in New Issue
Block a user