3b1b-manim/active_projects/hyperdarts.py
2019-08-28 14:23:00 -07:00

355 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