3b1b-manim/manimlib/animation/indication.py

426 lines
13 KiB
Python
Raw Normal View History

from __future__ import annotations
import numpy as np
from manimlib.animation.animation import Animation
from manimlib.animation.composition import AnimationGroup
from manimlib.animation.composition import Succession
from manimlib.animation.creation import ShowCreation
from manimlib.animation.creation import ShowPartial
from manimlib.animation.fading import FadeOut
2021-08-19 09:00:30 -07:00
from manimlib.animation.fading import FadeIn
2022-04-12 19:19:59 +08:00
from manimlib.animation.movement import Homotopy
from manimlib.animation.transform import Transform
2022-08-23 11:40:19 +08:00
from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
2022-04-12 19:19:59 +08:00
from manimlib.constants import ORIGIN, RIGHT, UP
from manimlib.constants import SMALL_BUFF
from manimlib.constants import DEG
2022-04-12 19:19:59 +08:00
from manimlib.constants import TAU
from manimlib.constants import GREY, YELLOW
from manimlib.mobject.geometry import Circle
from manimlib.mobject.geometry import Dot
2022-04-12 19:19:59 +08:00
from manimlib.mobject.geometry import Line
from manimlib.mobject.shape_matchers import SurroundingRectangle
2021-03-18 17:42:47 -07:00
from manimlib.mobject.shape_matchers import Underline
2022-04-12 19:19:59 +08:00
from manimlib.mobject.types.vectorized_mobject import VMobject
from manimlib.mobject.types.vectorized_mobject import VGroup
from manimlib.utils.bezier import interpolate
2021-08-19 09:00:30 -07:00
from manimlib.utils.rate_functions import smooth
from manimlib.utils.rate_functions import squish_rate_func
2022-04-12 19:19:59 +08:00
from manimlib.utils.rate_functions import there_and_back
from manimlib.utils.rate_functions import wiggle
from typing import TYPE_CHECKING
if TYPE_CHECKING:
2022-12-14 14:40:05 -08:00
from typing import Callable
2022-12-16 20:19:18 -08:00
from manimlib.typing import ManimColor
from manimlib.mobject.mobject import Mobject
2022-04-12 19:19:59 +08:00
class FocusOn(Transform):
2022-12-14 14:40:05 -08:00
def __init__(
self,
focus_point: np.ndarray | Mobject,
opacity: float = 0.2,
color: ManimColor = GREY,
run_time: float = 2,
remover: bool = True,
**kwargs
):
self.focus_point = focus_point
2022-12-14 14:40:05 -08:00
self.opacity = opacity
self.color = color
# Initialize with blank mobject, while create_target
# and create_starting_mobject handle the meat
2022-12-14 14:40:05 -08:00
super().__init__(VMobject(), run_time=run_time, remover=remover, **kwargs)
def create_target(self) -> Dot:
little_dot = Dot(radius=0)
little_dot.set_fill(self.color, opacity=self.opacity)
2022-12-14 14:40:05 -08:00
little_dot.add_updater(lambda d: d.move_to(self.focus_point))
return little_dot
def create_starting_mobject(self) -> Dot:
return Dot(
radius=FRAME_X_RADIUS + FRAME_Y_RADIUS,
stroke_width=0,
fill_color=self.color,
fill_opacity=0,
)
class Indicate(Transform):
2022-12-14 14:40:05 -08:00
def __init__(
self,
mobject: Mobject,
scale_factor: float = 1.2,
color: ManimColor = YELLOW,
rate_func: Callable[[float], float] = there_and_back,
**kwargs
):
self.scale_factor = scale_factor
self.color = color
super().__init__(mobject, rate_func=rate_func, **kwargs)
def create_target(self) -> Mobject:
target = self.mobject.copy()
target.scale(self.scale_factor)
target.set_color(self.color)
return target
class Flash(AnimationGroup):
def __init__(
self,
2022-12-14 14:40:05 -08:00
point: np.ndarray | Mobject,
color: ManimColor = YELLOW,
2022-12-14 14:40:05 -08:00
line_length: float = 0.2,
num_lines: int = 12,
flash_radius: float = 0.3,
line_stroke_width: float = 3.0,
run_time: float = 1.0,
**kwargs
):
self.point = point
self.color = color
2022-12-14 14:40:05 -08:00
self.line_length = line_length
self.num_lines = num_lines
self.flash_radius = flash_radius
self.line_stroke_width = line_stroke_width
self.lines = self.create_lines()
animations = self.create_line_anims()
super().__init__(
*animations,
group=self.lines,
2022-12-14 14:40:05 -08:00
run_time=run_time,
**kwargs,
)
def create_lines(self) -> VGroup:
lines = VGroup()
for angle in np.arange(0, TAU, TAU / self.num_lines):
line = Line(ORIGIN, self.line_length * RIGHT)
line.shift((self.flash_radius - self.line_length) * RIGHT)
line.rotate(angle, about_point=ORIGIN)
lines.add(line)
2020-03-07 20:53:48 -08:00
lines.set_stroke(
color=self.color,
width=self.line_stroke_width
)
lines.add_updater(lambda l: l.move_to(self.point))
return lines
def create_line_anims(self) -> list[Animation]:
return [
ShowCreationThenDestruction(line)
for line in self.lines
]
2022-12-14 14:40:05 -08:00
class CircleIndicate(Transform):
def __init__(
self,
mobject: Mobject,
scale_factor: float = 1.2,
rate_func: Callable[[float], float] = there_and_back,
stroke_color: ManimColor = YELLOW,
stroke_width: float = 3.0,
remover: bool = True,
**kwargs
):
circle = Circle(stroke_color=stroke_color, stroke_width=stroke_width)
circle.surround(mobject)
pre_circle = circle.copy().set_stroke(width=0)
pre_circle.scale(1 / scale_factor)
super().__init__(
pre_circle, circle,
rate_func=rate_func,
remover=remover,
**kwargs
)
class ShowPassingFlash(ShowPartial):
2022-12-14 14:40:05 -08:00
def __init__(
self,
mobject: Mobject,
time_width: float = 0.1,
remover: bool = True,
**kwargs
):
self.time_width = time_width
super().__init__(
mobject,
remover=remover,
**kwargs
)
def get_bounds(self, alpha: float) -> tuple[float, float]:
tw = self.time_width
upper = interpolate(0, 1 + tw, alpha)
lower = upper - tw
upper = min(upper, 1)
lower = max(lower, 0)
return (lower, upper)
def finish(self) -> None:
super().finish()
for submob, start in self.get_all_families_zipped():
submob.pointwise_become_partial(start, 0, 1)
2021-02-25 08:45:03 -08:00
class VShowPassingFlash(Animation):
2022-12-14 14:40:05 -08:00
def __init__(
self,
vmobject: VMobject,
time_width: float = 0.3,
2023-01-11 19:27:23 -08:00
taper_width: float = 0.05,
2022-12-14 14:40:05 -08:00
remover: bool = True,
**kwargs
):
self.time_width = time_width
self.taper_width = taper_width
super().__init__(vmobject, remover=remover, **kwargs)
self.mobject = vmobject
def taper_kernel(self, x):
if x < self.taper_width:
return x
elif x > 1 - self.taper_width:
return 1.0 - x
return 1.0
2021-02-25 08:45:03 -08:00
def begin(self) -> None:
2021-03-18 17:42:47 -07:00
# Compute an array of stroke widths for each submobject
# which tapers out at either end
self.submob_to_widths = dict()
2021-03-18 17:42:47 -07:00
for sm in self.mobject.get_family():
widths = sm.get_stroke_widths()
self.submob_to_widths[hash(sm)] = np.array([
width * self.taper_kernel(x)
for width, x in zip(widths, np.linspace(0, 1, len(widths)))
])
2021-02-25 08:45:03 -08:00
super().begin()
def interpolate_submobject(
self,
submobject: VMobject,
starting_sumobject: None,
alpha: float
) -> None:
widths = self.submob_to_widths[hash(submobject)]
2021-02-25 08:45:03 -08:00
# Create a gaussian such that 3 sigmas out on either side
2021-03-18 17:42:47 -07:00
# will equals time_width
tw = self.time_width
sigma = tw / 6
mu = interpolate(-tw / 2, 1 + tw / 2, alpha)
xs = np.linspace(0, 1, len(widths))
zs = (xs - mu) / sigma
gaussian = np.exp(-0.5 * zs * zs)
gaussian[abs(xs - mu) > 3 * sigma] = 0
if len(widths * gaussian) !=0:
submobject.set_stroke(width=widths * gaussian)
2021-02-25 08:45:03 -08:00
def finish(self) -> None:
2021-02-25 08:45:03 -08:00
super().finish()
for submob, start in self.get_all_families_zipped():
submob.match_style(start)
2021-03-18 17:42:47 -07:00
class FlashAround(VShowPassingFlash):
2022-12-14 14:40:05 -08:00
def __init__(
self,
mobject: Mobject,
time_width: float = 1.0,
taper_width: float = 0.0,
2022-12-14 14:40:05 -08:00
stroke_width: float = 4.0,
color: ManimColor = YELLOW,
buff: float = SMALL_BUFF,
n_inserted_curves: int = 100,
2022-12-14 14:40:05 -08:00
**kwargs
):
path = self.get_path(mobject, buff)
2023-01-27 14:48:31 -08:00
if mobject.is_fixed_in_frame():
path.fix_in_frame()
2022-12-14 14:40:05 -08:00
path.insert_n_curves(n_inserted_curves)
2021-03-18 17:42:47 -07:00
path.set_points(path.get_points_without_null_curves())
2022-12-14 14:40:05 -08:00
path.set_stroke(color, stroke_width)
super().__init__(path, time_width=time_width, taper_width=taper_width, **kwargs)
2021-03-18 17:42:47 -07:00
2022-12-14 14:40:05 -08:00
def get_path(self, mobject: Mobject, buff: float) -> SurroundingRectangle:
return SurroundingRectangle(mobject, buff=buff)
2021-03-18 17:42:47 -07:00
class FlashUnder(FlashAround):
2022-12-14 14:40:05 -08:00
def get_path(self, mobject: Mobject, buff: float) -> Underline:
return Underline(mobject, buff=buff, stretch_factor=1.0)
2021-03-18 17:42:47 -07:00
class ShowCreationThenDestruction(ShowPassingFlash):
2022-12-14 14:40:05 -08:00
def __init__(self, vmobject: VMobject, time_width: float = 2.0, **kwargs):
super().__init__(vmobject, time_width=time_width, **kwargs)
2019-01-16 11:07:36 -08:00
class ShowCreationThenFadeOut(Succession):
2022-12-14 14:40:05 -08:00
def __init__(self, mobject: Mobject, remover: bool = True, **kwargs):
super().__init__(
ShowCreation(mobject),
FadeOut(mobject),
2022-12-14 14:40:05 -08:00
remover=remover,
2019-01-16 11:07:36 -08:00
**kwargs
)
class AnimationOnSurroundingRectangle(AnimationGroup):
2022-12-14 14:40:05 -08:00
RectAnimationType: type = Animation
2022-12-14 14:40:05 -08:00
def __init__(
self,
mobject: Mobject,
stroke_width: float = 2.0,
stroke_color: ManimColor = YELLOW,
buff: float = SMALL_BUFF,
**kwargs
):
rect = SurroundingRectangle(
mobject,
stroke_width=stroke_width,
stroke_color=stroke_color,
buff=buff,
)
2022-12-14 14:40:05 -08:00
rect.add_updater(lambda r: r.move_to(mobject))
super().__init__(self.RectAnimationType(rect, **kwargs))
class ShowPassingFlashAround(AnimationOnSurroundingRectangle):
2022-12-14 14:40:05 -08:00
RectAnimationType = ShowPassingFlash
class ShowCreationThenDestructionAround(AnimationOnSurroundingRectangle):
2022-12-14 14:40:05 -08:00
RectAnimationType = ShowCreationThenDestruction
class ShowCreationThenFadeAround(AnimationOnSurroundingRectangle):
2022-12-14 14:40:05 -08:00
RectAnimationType = ShowCreationThenFadeOut
class ApplyWave(Homotopy):
2022-12-14 14:40:05 -08:00
def __init__(
self,
mobject: Mobject,
direction: np.ndarray = UP,
amplitude: float = 0.2,
run_time: float = 1.0,
**kwargs
):
left_x = mobject.get_left()[0]
right_x = mobject.get_right()[0]
2022-12-14 14:40:05 -08:00
vect = amplitude * direction
def homotopy(x, y, z, t):
alpha = (x - left_x) / (right_x - left_x)
power = np.exp(2.0 * (alpha - 0.5))
nudge = there_and_back(t**power)
return np.array([x, y, z]) + nudge * vect
super().__init__(homotopy, mobject, **kwargs)
class WiggleOutThenIn(Animation):
2022-12-14 14:40:05 -08:00
def __init__(
self,
mobject: Mobject,
scale_value: float = 1.1,
rotation_angle: float = 0.01 * TAU,
n_wiggles: int = 6,
scale_about_point: np.ndarray | None = None,
rotate_about_point: np.ndarray | None = None,
run_time: float = 2,
**kwargs
):
self.scale_value = scale_value
self.rotation_angle = rotation_angle
self.n_wiggles = n_wiggles
self.scale_about_point = scale_about_point
self.rotate_about_point = rotate_about_point
super().__init__(mobject, run_time=run_time, **kwargs)
def get_scale_about_point(self) -> np.ndarray:
2022-12-14 14:40:05 -08:00
return self.scale_about_point or self.mobject.get_center()
def get_rotate_about_point(self) -> np.ndarray:
2022-12-14 14:40:05 -08:00
return self.rotate_about_point or self.mobject.get_center()
def interpolate_submobject(
self,
submobject: Mobject,
starting_sumobject: Mobject,
alpha: float
) -> None:
2021-01-12 07:27:32 -10:00
submobject.match_points(starting_sumobject)
submobject.scale(
interpolate(1, self.scale_value, there_and_back(alpha)),
about_point=self.get_scale_about_point()
)
submobject.rotate(
wiggle(alpha, self.n_wiggles) * self.rotation_angle,
about_point=self.get_rotate_about_point()
)
class TurnInsideOut(Transform):
def __init__(self, mobject: Mobject, path_arc: float = 90 * DEG, **kwargs):
2022-12-14 14:40:05 -08:00
super().__init__(mobject, path_arc=path_arc, **kwargs)
def create_target(self) -> Mobject:
2022-12-14 14:40:05 -08:00
result = self.mobject.copy().reverse_points()
if isinstance(result, VMobject):
result.refresh_triangulation()
return result
2021-08-19 09:00:30 -07:00
class FlashyFadeIn(AnimationGroup):
2022-12-14 14:40:05 -08:00
def __init__(self,
vmobject: VMobject,
stroke_width: float = 2.0,
fade_lag: float = 0.0,
time_width: float = 1.0,
**kwargs
):
2021-08-19 09:00:30 -07:00
outline = vmobject.copy()
outline.set_fill(opacity=0)
outline.set_stroke(width=stroke_width, opacity=1)
rate_func = kwargs.get("rate_func", smooth)
super().__init__(
2022-12-14 14:40:05 -08:00
FadeIn(vmobject, rate_func=squish_rate_func(rate_func, fade_lag, 1)),
VShowPassingFlash(outline, time_width=time_width),
2021-08-19 09:00:30 -07:00
**kwargs
)