123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- from __future__ import annotations
- import math
- from os import remove
- 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
- from manimlib.animation.fading import FadeIn
- from manimlib.animation.movement import Homotopy
- from manimlib.animation.transform import Transform
- from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
- from manimlib.constants import ORIGIN, RIGHT, UP
- from manimlib.constants import SMALL_BUFF
- from manimlib.constants import DEGREES
- from manimlib.constants import TAU
- from manimlib.constants import GREY, YELLOW
- from manimlib.mobject.geometry import Circle
- from manimlib.mobject.geometry import Dot
- from manimlib.mobject.geometry import Line
- from manimlib.mobject.shape_matchers import SurroundingRectangle
- from manimlib.mobject.shape_matchers import Underline
- from manimlib.mobject.types.vectorized_mobject import VMobject
- from manimlib.mobject.types.vectorized_mobject import VGroup
- from manimlib.utils.bezier import interpolate
- from manimlib.utils.rate_functions import smooth
- from manimlib.utils.rate_functions import squish_rate_func
- from manimlib.utils.rate_functions import there_and_back
- from manimlib.utils.rate_functions import wiggle
- from typing import TYPE_CHECKING
- if TYPE_CHECKING:
- from typing import Callable
- from manimlib.typing import ManimColor
- from manimlib.mobject.mobject import Mobject
- class FocusOn(Transform):
- 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
- self.opacity = opacity
- self.color = color
- # Initialize with blank mobject, while create_target
- # and create_starting_mobject handle the meat
- 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)
- 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):
- 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,
- point: np.ndarray | Mobject,
- color: ManimColor = YELLOW,
- 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
- 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,
- 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)
- 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
- ]
- 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):
- 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)
- class VShowPassingFlash(Animation):
- def __init__(
- self,
- vmobject: VMobject,
- time_width: float = 0.3,
- taper_width: float = 0.05,
- 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
- def begin(self) -> None:
- # Compute an array of stroke widths for each submobject
- # which tapers out at either end
- self.submob_to_widths = dict()
- 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)))
- ])
- super().begin()
- def interpolate_submobject(
- self,
- submobject: VMobject,
- starting_sumobject: None,
- alpha: float
- ) -> None:
- widths = self.submob_to_widths[hash(submobject)]
- # Create a gaussian such that 3 sigmas out on either side
- # 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)
- def finish(self) -> None:
- super().finish()
- for submob, start in self.get_all_families_zipped():
- submob.match_style(start)
- class FlashAround(VShowPassingFlash):
- def __init__(
- self,
- mobject: Mobject,
- time_width: float = 1.0,
- taper_width: float = 0.0,
- stroke_width: float = 4.0,
- color: ManimColor = YELLOW,
- buff: float = SMALL_BUFF,
- n_inserted_curves: int = 100,
- **kwargs
- ):
- path = self.get_path(mobject, buff)
- if mobject.is_fixed_in_frame():
- path.fix_in_frame()
- path.insert_n_curves(n_inserted_curves)
- path.set_points(path.get_points_without_null_curves())
- path.set_stroke(color, stroke_width)
- super().__init__(path, time_width=time_width, taper_width=taper_width, **kwargs)
- def get_path(self, mobject: Mobject, buff: float) -> SurroundingRectangle:
- return SurroundingRectangle(mobject, buff=buff)
- class FlashUnder(FlashAround):
- def get_path(self, mobject: Mobject, buff: float) -> Underline:
- return Underline(mobject, buff=buff, stretch_factor=1.0)
- class ShowCreationThenDestruction(ShowPassingFlash):
- def __init__(self, vmobject: VMobject, time_width: float = 2.0, **kwargs):
- super().__init__(vmobject, time_width=time_width, **kwargs)
- class ShowCreationThenFadeOut(Succession):
- def __init__(self, mobject: Mobject, remover: bool = True, **kwargs):
- super().__init__(
- ShowCreation(mobject),
- FadeOut(mobject),
- remover=remover,
- **kwargs
- )
- class AnimationOnSurroundingRectangle(AnimationGroup):
- RectAnimationType: type = Animation
- 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,
- )
- rect.add_updater(lambda r: r.move_to(mobject))
- super().__init__(self.RectAnimationType(rect, **kwargs))
- class ShowPassingFlashAround(AnimationOnSurroundingRectangle):
- RectAnimationType = ShowPassingFlash
- class ShowCreationThenDestructionAround(AnimationOnSurroundingRectangle):
- RectAnimationType = ShowCreationThenDestruction
- class ShowCreationThenFadeAround(AnimationOnSurroundingRectangle):
- RectAnimationType = ShowCreationThenFadeOut
- class ApplyWave(Homotopy):
- 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]
- 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):
- 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:
- return self.scale_about_point or self.mobject.get_center()
- def get_rotate_about_point(self) -> np.ndarray:
- return self.rotate_about_point or self.mobject.get_center()
- def interpolate_submobject(
- self,
- submobject: Mobject,
- starting_sumobject: Mobject,
- alpha: float
- ) -> None:
- 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 * DEGREES, **kwargs):
- super().__init__(mobject, path_arc=path_arc, **kwargs)
- def create_target(self) -> Mobject:
- result = self.mobject.copy().reverse_points()
- if isinstance(result, VMobject):
- result.refresh_triangulation()
- return result
- class FlashyFadeIn(AnimationGroup):
- def __init__(self,
- vmobject: VMobject,
- stroke_width: float = 2.0,
- fade_lag: float = 0.0,
- time_width: float = 1.0,
- **kwargs
- ):
- 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__(
- FadeIn(vmobject, rate_func=squish_rate_func(rate_func, fade_lag, 1)),
- VShowPassingFlash(outline, time_width=time_width),
- **kwargs
- )
|