indication.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. from __future__ import annotations
  2. import math
  3. from os import remove
  4. import numpy as np
  5. from manimlib.animation.animation import Animation
  6. from manimlib.animation.composition import AnimationGroup
  7. from manimlib.animation.composition import Succession
  8. from manimlib.animation.creation import ShowCreation
  9. from manimlib.animation.creation import ShowPartial
  10. from manimlib.animation.fading import FadeOut
  11. from manimlib.animation.fading import FadeIn
  12. from manimlib.animation.movement import Homotopy
  13. from manimlib.animation.transform import Transform
  14. from manimlib.constants import FRAME_X_RADIUS, FRAME_Y_RADIUS
  15. from manimlib.constants import ORIGIN, RIGHT, UP
  16. from manimlib.constants import SMALL_BUFF
  17. from manimlib.constants import DEGREES
  18. from manimlib.constants import TAU
  19. from manimlib.constants import GREY, YELLOW
  20. from manimlib.mobject.geometry import Circle
  21. from manimlib.mobject.geometry import Dot
  22. from manimlib.mobject.geometry import Line
  23. from manimlib.mobject.shape_matchers import SurroundingRectangle
  24. from manimlib.mobject.shape_matchers import Underline
  25. from manimlib.mobject.types.vectorized_mobject import VMobject
  26. from manimlib.mobject.types.vectorized_mobject import VGroup
  27. from manimlib.utils.bezier import interpolate
  28. from manimlib.utils.rate_functions import smooth
  29. from manimlib.utils.rate_functions import squish_rate_func
  30. from manimlib.utils.rate_functions import there_and_back
  31. from manimlib.utils.rate_functions import wiggle
  32. from typing import TYPE_CHECKING
  33. if TYPE_CHECKING:
  34. from typing import Callable
  35. from manimlib.typing import ManimColor
  36. from manimlib.mobject.mobject import Mobject
  37. class FocusOn(Transform):
  38. def __init__(
  39. self,
  40. focus_point: np.ndarray | Mobject,
  41. opacity: float = 0.2,
  42. color: ManimColor = GREY,
  43. run_time: float = 2,
  44. remover: bool = True,
  45. **kwargs
  46. ):
  47. self.focus_point = focus_point
  48. self.opacity = opacity
  49. self.color = color
  50. # Initialize with blank mobject, while create_target
  51. # and create_starting_mobject handle the meat
  52. super().__init__(VMobject(), run_time=run_time, remover=remover, **kwargs)
  53. def create_target(self) -> Dot:
  54. little_dot = Dot(radius=0)
  55. little_dot.set_fill(self.color, opacity=self.opacity)
  56. little_dot.add_updater(lambda d: d.move_to(self.focus_point))
  57. return little_dot
  58. def create_starting_mobject(self) -> Dot:
  59. return Dot(
  60. radius=FRAME_X_RADIUS + FRAME_Y_RADIUS,
  61. stroke_width=0,
  62. fill_color=self.color,
  63. fill_opacity=0,
  64. )
  65. class Indicate(Transform):
  66. def __init__(
  67. self,
  68. mobject: Mobject,
  69. scale_factor: float = 1.2,
  70. color: ManimColor = YELLOW,
  71. rate_func: Callable[[float], float] = there_and_back,
  72. **kwargs
  73. ):
  74. self.scale_factor = scale_factor
  75. self.color = color
  76. super().__init__(mobject, rate_func=rate_func, **kwargs)
  77. def create_target(self) -> Mobject:
  78. target = self.mobject.copy()
  79. target.scale(self.scale_factor)
  80. target.set_color(self.color)
  81. return target
  82. class Flash(AnimationGroup):
  83. def __init__(
  84. self,
  85. point: np.ndarray | Mobject,
  86. color: ManimColor = YELLOW,
  87. line_length: float = 0.2,
  88. num_lines: int = 12,
  89. flash_radius: float = 0.3,
  90. line_stroke_width: float = 3.0,
  91. run_time: float = 1.0,
  92. **kwargs
  93. ):
  94. self.point = point
  95. self.color = color
  96. self.line_length = line_length
  97. self.num_lines = num_lines
  98. self.flash_radius = flash_radius
  99. self.line_stroke_width = line_stroke_width
  100. self.lines = self.create_lines()
  101. animations = self.create_line_anims()
  102. super().__init__(
  103. *animations,
  104. group=self.lines,
  105. run_time=run_time,
  106. **kwargs,
  107. )
  108. def create_lines(self) -> VGroup:
  109. lines = VGroup()
  110. for angle in np.arange(0, TAU, TAU / self.num_lines):
  111. line = Line(ORIGIN, self.line_length * RIGHT)
  112. line.shift((self.flash_radius - self.line_length) * RIGHT)
  113. line.rotate(angle, about_point=ORIGIN)
  114. lines.add(line)
  115. lines.set_stroke(
  116. color=self.color,
  117. width=self.line_stroke_width
  118. )
  119. lines.add_updater(lambda l: l.move_to(self.point))
  120. return lines
  121. def create_line_anims(self) -> list[Animation]:
  122. return [
  123. ShowCreationThenDestruction(line)
  124. for line in self.lines
  125. ]
  126. class CircleIndicate(Transform):
  127. def __init__(
  128. self,
  129. mobject: Mobject,
  130. scale_factor: float = 1.2,
  131. rate_func: Callable[[float], float] = there_and_back,
  132. stroke_color: ManimColor = YELLOW,
  133. stroke_width: float = 3.0,
  134. remover: bool = True,
  135. **kwargs
  136. ):
  137. circle = Circle(stroke_color=stroke_color, stroke_width=stroke_width)
  138. circle.surround(mobject)
  139. pre_circle = circle.copy().set_stroke(width=0)
  140. pre_circle.scale(1 / scale_factor)
  141. super().__init__(
  142. pre_circle, circle,
  143. rate_func=rate_func,
  144. remover=remover,
  145. **kwargs
  146. )
  147. class ShowPassingFlash(ShowPartial):
  148. def __init__(
  149. self,
  150. mobject: Mobject,
  151. time_width: float = 0.1,
  152. remover: bool = True,
  153. **kwargs
  154. ):
  155. self.time_width = time_width
  156. super().__init__(
  157. mobject,
  158. remover=remover,
  159. **kwargs
  160. )
  161. def get_bounds(self, alpha: float) -> tuple[float, float]:
  162. tw = self.time_width
  163. upper = interpolate(0, 1 + tw, alpha)
  164. lower = upper - tw
  165. upper = min(upper, 1)
  166. lower = max(lower, 0)
  167. return (lower, upper)
  168. def finish(self) -> None:
  169. super().finish()
  170. for submob, start in self.get_all_families_zipped():
  171. submob.pointwise_become_partial(start, 0, 1)
  172. class VShowPassingFlash(Animation):
  173. def __init__(
  174. self,
  175. vmobject: VMobject,
  176. time_width: float = 0.3,
  177. taper_width: float = 0.05,
  178. remover: bool = True,
  179. **kwargs
  180. ):
  181. self.time_width = time_width
  182. self.taper_width = taper_width
  183. super().__init__(vmobject, remover=remover, **kwargs)
  184. self.mobject = vmobject
  185. def taper_kernel(self, x):
  186. if x < self.taper_width:
  187. return x
  188. elif x > 1 - self.taper_width:
  189. return 1.0 - x
  190. return 1.0
  191. def begin(self) -> None:
  192. # Compute an array of stroke widths for each submobject
  193. # which tapers out at either end
  194. self.submob_to_widths = dict()
  195. for sm in self.mobject.get_family():
  196. widths = sm.get_stroke_widths()
  197. self.submob_to_widths[hash(sm)] = np.array([
  198. width * self.taper_kernel(x)
  199. for width, x in zip(widths, np.linspace(0, 1, len(widths)))
  200. ])
  201. super().begin()
  202. def interpolate_submobject(
  203. self,
  204. submobject: VMobject,
  205. starting_sumobject: None,
  206. alpha: float
  207. ) -> None:
  208. widths = self.submob_to_widths[hash(submobject)]
  209. # Create a gaussian such that 3 sigmas out on either side
  210. # will equals time_width
  211. tw = self.time_width
  212. sigma = tw / 6
  213. mu = interpolate(-tw / 2, 1 + tw / 2, alpha)
  214. xs = np.linspace(0, 1, len(widths))
  215. zs = (xs - mu) / sigma
  216. gaussian = np.exp(-0.5 * zs * zs)
  217. gaussian[abs(xs - mu) > 3 * sigma] = 0
  218. if len(widths * gaussian) !=0:
  219. submobject.set_stroke(width=widths * gaussian)
  220. def finish(self) -> None:
  221. super().finish()
  222. for submob, start in self.get_all_families_zipped():
  223. submob.match_style(start)
  224. class FlashAround(VShowPassingFlash):
  225. def __init__(
  226. self,
  227. mobject: Mobject,
  228. time_width: float = 1.0,
  229. taper_width: float = 0.0,
  230. stroke_width: float = 4.0,
  231. color: ManimColor = YELLOW,
  232. buff: float = SMALL_BUFF,
  233. n_inserted_curves: int = 100,
  234. **kwargs
  235. ):
  236. path = self.get_path(mobject, buff)
  237. if mobject.is_fixed_in_frame():
  238. path.fix_in_frame()
  239. path.insert_n_curves(n_inserted_curves)
  240. path.set_points(path.get_points_without_null_curves())
  241. path.set_stroke(color, stroke_width)
  242. super().__init__(path, time_width=time_width, taper_width=taper_width, **kwargs)
  243. def get_path(self, mobject: Mobject, buff: float) -> SurroundingRectangle:
  244. return SurroundingRectangle(mobject, buff=buff)
  245. class FlashUnder(FlashAround):
  246. def get_path(self, mobject: Mobject, buff: float) -> Underline:
  247. return Underline(mobject, buff=buff, stretch_factor=1.0)
  248. class ShowCreationThenDestruction(ShowPassingFlash):
  249. def __init__(self, vmobject: VMobject, time_width: float = 2.0, **kwargs):
  250. super().__init__(vmobject, time_width=time_width, **kwargs)
  251. class ShowCreationThenFadeOut(Succession):
  252. def __init__(self, mobject: Mobject, remover: bool = True, **kwargs):
  253. super().__init__(
  254. ShowCreation(mobject),
  255. FadeOut(mobject),
  256. remover=remover,
  257. **kwargs
  258. )
  259. class AnimationOnSurroundingRectangle(AnimationGroup):
  260. RectAnimationType: type = Animation
  261. def __init__(
  262. self,
  263. mobject: Mobject,
  264. stroke_width: float = 2.0,
  265. stroke_color: ManimColor = YELLOW,
  266. buff: float = SMALL_BUFF,
  267. **kwargs
  268. ):
  269. rect = SurroundingRectangle(
  270. mobject,
  271. stroke_width=stroke_width,
  272. stroke_color=stroke_color,
  273. buff=buff,
  274. )
  275. rect.add_updater(lambda r: r.move_to(mobject))
  276. super().__init__(self.RectAnimationType(rect, **kwargs))
  277. class ShowPassingFlashAround(AnimationOnSurroundingRectangle):
  278. RectAnimationType = ShowPassingFlash
  279. class ShowCreationThenDestructionAround(AnimationOnSurroundingRectangle):
  280. RectAnimationType = ShowCreationThenDestruction
  281. class ShowCreationThenFadeAround(AnimationOnSurroundingRectangle):
  282. RectAnimationType = ShowCreationThenFadeOut
  283. class ApplyWave(Homotopy):
  284. def __init__(
  285. self,
  286. mobject: Mobject,
  287. direction: np.ndarray = UP,
  288. amplitude: float = 0.2,
  289. run_time: float = 1.0,
  290. **kwargs
  291. ):
  292. left_x = mobject.get_left()[0]
  293. right_x = mobject.get_right()[0]
  294. vect = amplitude * direction
  295. def homotopy(x, y, z, t):
  296. alpha = (x - left_x) / (right_x - left_x)
  297. power = np.exp(2.0 * (alpha - 0.5))
  298. nudge = there_and_back(t**power)
  299. return np.array([x, y, z]) + nudge * vect
  300. super().__init__(homotopy, mobject, **kwargs)
  301. class WiggleOutThenIn(Animation):
  302. def __init__(
  303. self,
  304. mobject: Mobject,
  305. scale_value: float = 1.1,
  306. rotation_angle: float = 0.01 * TAU,
  307. n_wiggles: int = 6,
  308. scale_about_point: np.ndarray | None = None,
  309. rotate_about_point: np.ndarray | None = None,
  310. run_time: float = 2,
  311. **kwargs
  312. ):
  313. self.scale_value = scale_value
  314. self.rotation_angle = rotation_angle
  315. self.n_wiggles = n_wiggles
  316. self.scale_about_point = scale_about_point
  317. self.rotate_about_point = rotate_about_point
  318. super().__init__(mobject, run_time=run_time, **kwargs)
  319. def get_scale_about_point(self) -> np.ndarray:
  320. return self.scale_about_point or self.mobject.get_center()
  321. def get_rotate_about_point(self) -> np.ndarray:
  322. return self.rotate_about_point or self.mobject.get_center()
  323. def interpolate_submobject(
  324. self,
  325. submobject: Mobject,
  326. starting_sumobject: Mobject,
  327. alpha: float
  328. ) -> None:
  329. submobject.match_points(starting_sumobject)
  330. submobject.scale(
  331. interpolate(1, self.scale_value, there_and_back(alpha)),
  332. about_point=self.get_scale_about_point()
  333. )
  334. submobject.rotate(
  335. wiggle(alpha, self.n_wiggles) * self.rotation_angle,
  336. about_point=self.get_rotate_about_point()
  337. )
  338. class TurnInsideOut(Transform):
  339. def __init__(self, mobject: Mobject, path_arc: float = 90 * DEGREES, **kwargs):
  340. super().__init__(mobject, path_arc=path_arc, **kwargs)
  341. def create_target(self) -> Mobject:
  342. result = self.mobject.copy().reverse_points()
  343. if isinstance(result, VMobject):
  344. result.refresh_triangulation()
  345. return result
  346. class FlashyFadeIn(AnimationGroup):
  347. def __init__(self,
  348. vmobject: VMobject,
  349. stroke_width: float = 2.0,
  350. fade_lag: float = 0.0,
  351. time_width: float = 1.0,
  352. **kwargs
  353. ):
  354. outline = vmobject.copy()
  355. outline.set_fill(opacity=0)
  356. outline.set_stroke(width=stroke_width, opacity=1)
  357. rate_func = kwargs.get("rate_func", smooth)
  358. super().__init__(
  359. FadeIn(vmobject, rate_func=squish_rate_func(rate_func, fade_lag, 1)),
  360. VShowPassingFlash(outline, time_width=time_width),
  361. **kwargs
  362. )