changing.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. from __future__ import annotations
  2. import numpy as np
  3. from manimlib.constants import BLUE_B, BLUE_D, BLUE_E, GREY_BROWN, WHITE
  4. from manimlib.mobject.mobject import Mobject
  5. from manimlib.mobject.types.vectorized_mobject import VGroup
  6. from manimlib.mobject.types.vectorized_mobject import VMobject
  7. from manimlib.utils.rate_functions import smooth
  8. from typing import TYPE_CHECKING
  9. if TYPE_CHECKING:
  10. from typing import Callable, List, Iterable
  11. from manimlib.typing import ManimColor, Vect3, Self
  12. class AnimatedBoundary(VGroup):
  13. def __init__(
  14. self,
  15. vmobject: VMobject,
  16. colors: List[ManimColor] = [BLUE_D, BLUE_B, BLUE_E, GREY_BROWN],
  17. max_stroke_width: float = 3.0,
  18. cycle_rate: float = 0.5,
  19. back_and_forth: bool = True,
  20. draw_rate_func: Callable[[float], float] = smooth,
  21. fade_rate_func: Callable[[float], float] = smooth,
  22. **kwargs
  23. ):
  24. super().__init__(**kwargs)
  25. self.vmobject: VMobject = vmobject
  26. self.colors = colors
  27. self.max_stroke_width = max_stroke_width
  28. self.cycle_rate = cycle_rate
  29. self.back_and_forth = back_and_forth
  30. self.draw_rate_func = draw_rate_func
  31. self.fade_rate_func = fade_rate_func
  32. self.boundary_copies: list[VMobject] = [
  33. vmobject.copy().set_style(
  34. stroke_width=0,
  35. fill_opacity=0
  36. )
  37. for x in range(2)
  38. ]
  39. self.add(*self.boundary_copies)
  40. self.total_time: float = 0
  41. self.add_updater(
  42. lambda m, dt: self.update_boundary_copies(dt)
  43. )
  44. def update_boundary_copies(self, dt: float) -> Self:
  45. # Not actual time, but something which passes at
  46. # an altered rate to make the implementation below
  47. # cleaner
  48. time = self.total_time * self.cycle_rate
  49. growing, fading = self.boundary_copies
  50. colors = self.colors
  51. msw = self.max_stroke_width
  52. vmobject = self.vmobject
  53. index = int(time % len(colors))
  54. alpha = time % 1
  55. draw_alpha = self.draw_rate_func(alpha)
  56. fade_alpha = self.fade_rate_func(alpha)
  57. if self.back_and_forth and int(time) % 2 == 1:
  58. bounds = (1 - draw_alpha, 1)
  59. else:
  60. bounds = (0, draw_alpha)
  61. self.full_family_become_partial(growing, vmobject, *bounds)
  62. growing.set_stroke(colors[index], width=msw)
  63. if time >= 1:
  64. self.full_family_become_partial(fading, vmobject, 0, 1)
  65. fading.set_stroke(
  66. color=colors[index - 1],
  67. width=(1 - fade_alpha) * msw
  68. )
  69. self.total_time += dt
  70. return self
  71. def full_family_become_partial(
  72. self,
  73. mob1: VMobject,
  74. mob2: VMobject,
  75. a: float,
  76. b: float
  77. ) -> Self:
  78. family1 = mob1.family_members_with_points()
  79. family2 = mob2.family_members_with_points()
  80. for sm1, sm2 in zip(family1, family2):
  81. sm1.pointwise_become_partial(sm2, a, b)
  82. return self
  83. class TracedPath(VMobject):
  84. def __init__(
  85. self,
  86. traced_point_func: Callable[[], Vect3],
  87. time_traced: float = np.inf,
  88. time_per_anchor: float = 1.0 / 15,
  89. stroke_width: float | Iterable[float] = 2.0,
  90. stroke_color: ManimColor = WHITE,
  91. **kwargs
  92. ):
  93. super().__init__(**kwargs)
  94. self.traced_point_func = traced_point_func
  95. self.time_traced = time_traced
  96. self.time_per_anchor = time_per_anchor
  97. self.time: float = 0
  98. self.traced_points: list[np.ndarray] = []
  99. self.add_updater(lambda m, dt: m.update_path(dt))
  100. self.set_stroke(stroke_color, stroke_width)
  101. def update_path(self, dt: float) -> Self:
  102. if dt == 0:
  103. return self
  104. point = self.traced_point_func().copy()
  105. self.traced_points.append(point)
  106. if self.time_traced < np.inf:
  107. n_relevant_points = int(self.time_traced / dt + 0.5)
  108. n_tps = len(self.traced_points)
  109. if n_tps < n_relevant_points:
  110. points = self.traced_points + [point] * (n_relevant_points - n_tps)
  111. else:
  112. points = self.traced_points[n_tps - n_relevant_points:]
  113. # Every now and then refresh the list
  114. if n_tps > 10 * n_relevant_points:
  115. self.traced_points = self.traced_points[-n_relevant_points:]
  116. else:
  117. points = self.traced_points
  118. if points:
  119. self.set_points_smoothly(points)
  120. self.time += dt
  121. return self
  122. class TracingTail(TracedPath):
  123. def __init__(
  124. self,
  125. mobject_or_func: Mobject | Callable[[], np.ndarray],
  126. time_traced: float = 1.0,
  127. stroke_width: float | Iterable[float] = (0, 3),
  128. stroke_opacity: float | Iterable[float] = (0, 1),
  129. stroke_color: ManimColor = WHITE,
  130. **kwargs
  131. ):
  132. if isinstance(mobject_or_func, Mobject):
  133. func = mobject_or_func.get_center
  134. else:
  135. func = mobject_or_func
  136. super().__init__(
  137. func,
  138. time_traced=time_traced,
  139. stroke_width=stroke_width,
  140. stroke_opacity=stroke_opacity,
  141. stroke_color=stroke_color,
  142. **kwargs
  143. )
  144. self.add_updater(lambda m: m.set_stroke(width=stroke_width, opacity=stroke_opacity))