mirror of
https://github.com/3b1b/manim.git
synced 2025-08-05 16:49:03 +00:00
356 lines
9.8 KiB
Python
356 lines
9.8 KiB
Python
![]() |
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,
|
||
|
},
|
||
|
"default_line_style": {
|
||
|
"stroke_width": 2,
|
||
|
"stroke_color": WHITE,
|
||
|
},
|
||
|
"default_dot_config": {
|
||
|
"fill_color": WHITE,
|
||
|
"background_stroke_width": 1,
|
||
|
"background_stroke_color": BLACK,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
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
|
||
|
dot = Dot(circle.get_center())
|
||
|
dot.set_width(0.02)
|
||
|
dot.set_color(GREY)
|
||
|
return dot
|
||
|
|
||
|
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)
|
||
|
|
||
|
# 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
|
||
|
|
||
|
# Animations
|
||
|
|
||
|
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,
|
||
|
)
|
||
|
self.wait(0.5)
|
||
|
elif pace == "fast":
|
||
|
self.add(dot)
|
||
|
return dot
|
||
|
|
||
|
def show_full_hit_process(self, point, pace="slow"):
|
||
|
assert(pace in ["slow", "fast"])
|
||
|
|
||
|
lines = self.get_all_hit_lines(point, self.circle)
|
||
|
h_line, chord, radii = lines
|
||
|
|
||
|
dot = self.show_hit(point)
|
||
|
if pace == "slow":
|
||
|
self.play(
|
||
|
ShowCreation(h_line),
|
||
|
GrowFromCenter(chord),
|
||
|
)
|
||
|
self.play(*map(ShowCreation, radii))
|
||
|
elif pace == "fast":
|
||
|
self.add(dot)
|
||
|
self.play(
|
||
|
ShowCreation(h_line),
|
||
|
GrowFromCenter(chord),
|
||
|
*map(ShowCreation, radii),
|
||
|
run_time=0.5
|
||
|
)
|
||
|
|
||
|
new_chord = self.show_circle_shrink(chord, pace=pace)
|
||
|
self.play(
|
||
|
FadeOut(lines),
|
||
|
FadeOut(new_chord),
|
||
|
FadeOut(dot),
|
||
|
run_time=(1 if pace == "slow" else 0.5)
|
||
|
)
|
||
|
|
||
|
def show_circle_shrink(self, chord, pace="slow"):
|
||
|
circle = self.circle
|
||
|
chord_copy = chord.copy()
|
||
|
new_circle = self.get_new_circle_from_chord(chord)
|
||
|
|
||
|
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 chord_copy
|
||
|
|
||
|
def show_miss(self, point):
|
||
|
square = self.square
|
||
|
miss = TextMobject("Miss!")
|
||
|
miss.next_to(point, DOWN)
|
||
|
|
||
|
dot = self.show_hit(
|
||
|
point,
|
||
|
added_anims=[
|
||
|
ApplyMethod(
|
||
|
square.set_color, YELLOW,
|
||
|
rate_func=there_and_back,
|
||
|
),
|
||
|
GrowFromCenter(miss)
|
||
|
]
|
||
|
)
|
||
|
return VGroup(dot, miss)
|
||
|
|
||
|
|
||
|
# Problem statement
|
||
|
|
||
|
class IntroduceGame(HyperdartScene):
|
||
|
CONFIG = {
|
||
|
"random_seed": 1,
|
||
|
"square_width": 5,
|
||
|
# "num_darts_in_initial_flurry": 1000,
|
||
|
"num_darts_in_initial_flurry": 100,
|
||
|
}
|
||
|
|
||
|
def construct(self):
|
||
|
self.add_real_dartboard()
|
||
|
self.show_flurry_of_points()
|
||
|
self.show_board_dimensions()
|
||
|
self.introduce_bullseye()
|
||
|
self.show_shrink_rule()
|
||
|
|
||
|
def add_real_dartboard(self):
|
||
|
pass
|
||
|
|
||
|
def show_flurry_of_points(self):
|
||
|
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
|
||
|
dots = VGroup(*[
|
||
|
Dot(point, radius=0.01)
|
||
|
for point in points
|
||
|
])
|
||
|
|
||
|
self.remove(self.circle)
|
||
|
self.play(
|
||
|
FadeInFromDown(title),
|
||
|
LaggedStartMap(FadeInFromLarge, dots)
|
||
|
)
|
||
|
self.wait()
|
||
|
|
||
|
self.flurry_dots = dots
|
||
|
|
||
|
def show_board_dimensions(self):
|
||
|
square = self.square
|
||
|
|
||
|
labels = VGroup(*[
|
||
|
TextMobject("2ft").next_to(
|
||
|
square.get_edge_center(vect), -vect, SMALL_BUFF
|
||
|
)
|
||
|
for vect in [UP, 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, buff=SMALL_BUFF)
|
||
|
for label, v1, v2 in zip(labels, [LEFT, UP], [UP, RIGHT])
|
||
|
])
|
||
|
lines.match_color(labels)
|
||
|
|
||
|
self.play(
|
||
|
LaggedStartMap(ShowCreation, lines),
|
||
|
LaggedStartMap(FadeInFromDown, labels),
|
||
|
lag_ratio=0.5
|
||
|
)
|
||
|
|
||
|
def introduce_bullseye(self):
|
||
|
pass
|
||
|
|
||
|
def show_shrink_rule(self):
|
||
|
pass
|