Skip to content

Commit fa1b86f

Browse files
AntonBallmaierpre-commit-ci[bot]behacklnaveen521kk
authored
Enhance manim banner animations (#1308)
* test * revert test changes * Enhance create animation - Better Code readability - fix jumping triangle bug - Small fade in for spiraling shapes - Ease out rate function instead of smooth - allow run_time paramter * Add docs for run_time parameter * Simplify expand animation code * Change animation to uncover letters * Rework expand animation * add run_time parameter to expand * Make expansion centered * add direction to expand * change direction default * Finishing touches - Slide back correctly aligned - documentation * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix bug in docs * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix bug related to memory usage issue * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * fix bug (now really) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update manim/mobject/logo.py Co-authored-by: Benjamin Hackl <[email protected]> * rename shape to shapes * remove unnecessary comment * fix remaining occurances of "shape" Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Benjamin Hackl <[email protected]> Co-authored-by: Naveen M K <[email protected]>
1 parent 65fa84c commit fa1b86f

File tree

1 file changed

+127
-75
lines changed

1 file changed

+127
-75
lines changed

manim/mobject/logo.py

Lines changed: 127 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,14 @@
22

33
__all__ = ["ManimBanner"]
44

5-
import numpy as np
6-
75
from ..animation.composition import AnimationGroup, Succession
86
from ..animation.fading import FadeIn
9-
from ..animation.transform import ApplyMethod
7+
from ..animation.update import UpdateFromAlphaFunc
108
from ..constants import DOWN, LEFT, ORIGIN, RIGHT, TAU, UP
119
from ..mobject.geometry import Circle, Square, Triangle
1210
from ..mobject.svg.tex_mobject import MathTex, Tex
1311
from ..mobject.types.vectorized_mobject import VGroup
14-
from ..mobject.value_tracker import ValueTracker
15-
from ..utils.space_ops import normalize
12+
from ..utils.rate_functions import ease_in_out_cubic, ease_out_sine, smooth
1613
from ..utils.tex_templates import TexFontTemplates
1714

1815

@@ -30,27 +27,26 @@ class ManimBanner(VGroup):
3027
3128
Examples
3229
--------
30+
.. manim:: DarkThemeBanner
3331
34-
.. manim:: BannerDarkBackground
35-
36-
class BannerDarkBackground(Scene):
32+
class DarkThemeBanner(Scene):
3733
def construct(self):
38-
banner = ManimBanner().scale(0.5).to_corner(DR)
39-
self.play(FadeIn(banner))
34+
banner = ManimBanner()
35+
self.play(banner.create())
4036
self.play(banner.expand())
41-
self.play(FadeOut(banner))
37+
self.wait()
38+
self.play(Unwrite(banner))
4239
43-
.. manim:: BannerLightBackground
40+
.. manim:: LightThemeBanner
4441
45-
class BannerLightBackground(Scene):
42+
class LightThemeBanner(Scene):
4643
def construct(self):
4744
self.camera.background_color = "#ece6e2"
48-
banner_large = ManimBanner(dark_theme=False).scale(0.7)
49-
banner_small = ManimBanner(dark_theme=False).scale(0.35)
50-
banner_small.next_to(banner_large, DOWN)
51-
self.play(banner_large.create(), banner_small.create())
52-
self.play(banner_large.expand(), banner_small.expand())
53-
self.play(FadeOut(banner_large), FadeOut(banner_small))
45+
banner = ManimBanner(dark_theme=False)
46+
self.play(banner.create())
47+
self.play(banner.expand())
48+
self.wait()
49+
self.play(Unwrite(banner))
5450
5551
"""
5652

@@ -71,7 +67,8 @@ def __init__(self, dark_theme: bool = True):
7167
self.circle = Circle(color=logo_green, fill_opacity=1).shift(LEFT)
7268
self.square = Square(color=logo_blue, fill_opacity=1).shift(UP)
7369
self.triangle = Triangle(color=logo_red, fill_opacity=1).shift(RIGHT)
74-
self.add(self.triangle, self.square, self.circle, self.M)
70+
self.shapes = VGroup(self.triangle, self.square, self.circle)
71+
self.add(self.shapes, self.M)
7572
self.move_to(ORIGIN)
7673

7774
anim = VGroup()
@@ -90,7 +87,6 @@ def __init__(self, dark_theme: bool = True):
9087
# Note: "anim" is only shown in the expanded state
9188
# and thus not yet added to the submobjects of self.
9289
self.anim = anim
93-
self.anim.set_opacity(0)
9490

9591
def scale(self, scale_factor: float, **kwargs) -> "ManimBanner":
9692
"""Scale the banner by the specified scale factor.
@@ -111,54 +107,48 @@ def scale(self, scale_factor: float, **kwargs) -> "ManimBanner":
111107
self.anim.scale(scale_factor, **kwargs)
112108
return super().scale(scale_factor, **kwargs)
113109

114-
def create(self):
110+
def create(self, run_time: float = 2) -> AnimationGroup:
115111
"""The creation animation for Manim's logo.
116112
113+
Parameters
114+
----------
115+
run_time
116+
The run time of the animation.
117+
117118
Returns
118119
-------
119120
:class:`~.AnimationGroup`
120121
An animation to be used in a :meth:`.Scene.play` call.
121122
"""
122-
shape_center = VGroup(self.circle, self.square, self.triangle).get_center()
123-
124-
spiral_run_time = 2.1
123+
shape_center = self.shapes.get_center()
125124
expansion_factor = 8 * self.scale_factor
126125

127-
tracker = ValueTracker(0)
128-
129-
for mob in [self.circle, self.square, self.triangle]:
130-
mob.final_position = mob.get_center()
131-
mob.initial_position = (
132-
mob.final_position
133-
+ (mob.final_position - shape_center) * expansion_factor
126+
for shape in self.shapes:
127+
shape.final_position = shape.get_center()
128+
shape.initial_position = (
129+
shape.final_position
130+
+ (shape.final_position - shape_center) * expansion_factor
134131
)
135-
mob.initial_to_final_distance = np.linalg.norm(
136-
mob.final_position - mob.initial_position
137-
)
138-
mob.move_to(mob.initial_position)
139-
mob.current_time = 0
140-
mob.starting_mobject = mob.copy()
141-
142-
def updater(mob, dt):
143-
mob.become(mob.starting_mobject)
144-
direction = shape_center - mob.get_center()
145-
mob.shift(
146-
normalize(direction, fall_back=direction)
147-
* mob.initial_to_final_distance
148-
* tracker.get_value()
149-
)
150-
mob.rotate(TAU * tracker.get_value(), about_point=shape_center)
151-
mob.rotate(-TAU * tracker.get_value())
132+
shape.move_to(shape.initial_position)
133+
shape.save_state()
152134

153-
mob.add_updater(updater)
154-
155-
spin_animation = ApplyMethod(tracker.set_value, 1, run_time=spiral_run_time)
135+
def spiral_updater(shapes, alpha):
136+
for shape in shapes:
137+
shape.restore()
138+
shape.shift((shape.final_position - shape.initial_position) * alpha)
139+
shape.rotate(TAU * alpha, about_point=shape_center)
140+
shape.rotate(-TAU * alpha, about_point=shape.get_center_of_mass())
141+
shape.set_opacity(min(1, alpha * 3))
156142

157143
return AnimationGroup(
158-
FadeIn(self, run_time=spiral_run_time / 2), spin_animation
144+
UpdateFromAlphaFunc(
145+
self.shapes, spiral_updater, run_time=run_time, rate_func=ease_out_sine
146+
),
147+
FadeIn(self.M, run_time=run_time / 2),
148+
lag_ratio=0.1,
159149
)
160150

161-
def expand(self) -> Succession:
151+
def expand(self, run_time: float = 1.5, direction="center") -> Succession:
162152
"""An animation that expands Manim's logo into its banner.
163153
164154
The returned animation transforms the banner from its initial
@@ -174,34 +164,96 @@ def expand(self) -> Succession:
174164
it is added as a submobject so subsequent animations
175165
to the banner object apply to the text "anim" as well.
176166
167+
Parameters
168+
----------
169+
run_time
170+
The run time of the animation.
171+
direction
172+
The direction in which the logo is expanded.
173+
177174
Returns
178175
-------
179176
:class:`~.Succession`
180177
An animation to be used in a :meth:`.Scene.play` call.
181178
179+
Examples
180+
--------
181+
.. manim:: ExpandDirections
182+
183+
class ExpandDirections(Scene):
184+
def construct(self):
185+
banners = [ManimBanner().scale(0.5).shift(UP*x) for x in [-2, 0, 2]]
186+
self.play(
187+
banners[0].expand(direction="right"),
188+
banners[1].expand(direction="center"),
189+
banners[2].expand(direction="left"),
190+
)
191+
182192
"""
193+
if direction not in ["left", "right", "center"]:
194+
raise ValueError("direction must be 'left', 'right' or 'center'.")
195+
183196
m_shape_offset = 6.25 * self.scale_factor
197+
shape_sliding_overshoot = self.scale_factor * 0.8
184198
m_anim_buff = 0.06
185-
self.add(self.anim)
186-
self.anim.next_to(self.M, buff=m_anim_buff).shift(
187-
m_shape_offset * LEFT
188-
).align_to(self.M, DOWN)
189-
move_left = AnimationGroup(
190-
ApplyMethod(self.triangle.shift, m_shape_offset * LEFT),
191-
ApplyMethod(self.square.shift, m_shape_offset * LEFT),
192-
ApplyMethod(self.circle.shift, m_shape_offset * LEFT),
193-
ApplyMethod(self.M.shift, m_shape_offset * LEFT),
194-
)
195-
move_right = AnimationGroup(
196-
ApplyMethod(self.triangle.shift, m_shape_offset * RIGHT),
197-
ApplyMethod(self.square.shift, m_shape_offset * RIGHT),
198-
ApplyMethod(self.circle.shift, m_shape_offset * RIGHT),
199-
ApplyMethod(self.M.shift, 0 * LEFT),
200-
AnimationGroup(
201-
*[ApplyMethod(obj.set_opacity, 1) for obj in self.anim], lag_ratio=0.15
199+
self.anim.next_to(self.M, buff=m_anim_buff).align_to(self.M, DOWN)
200+
self.anim.set_opacity(0)
201+
self.shapes.save_state()
202+
m_clone = self.anim[-1].copy()
203+
self.add(m_clone)
204+
m_clone.move_to(self.shapes)
205+
206+
self.M.save_state()
207+
left_group = VGroup(self.M, self.anim, m_clone)
208+
209+
def shift(vector):
210+
self.shapes.restore()
211+
left_group.align_to(self.M.saved_state, LEFT)
212+
if direction == "right":
213+
self.shapes.shift(vector)
214+
elif direction == "center":
215+
self.shapes.shift(vector / 2)
216+
left_group.shift(-vector / 2)
217+
elif direction == "left":
218+
left_group.shift(-vector)
219+
220+
def slide_and_uncover(mob, alpha):
221+
shift(alpha * (m_shape_offset + shape_sliding_overshoot) * RIGHT)
222+
223+
# Add letters when they are covered
224+
for letter in mob.anim:
225+
if mob.square.get_center()[0] > letter.get_center()[0]:
226+
letter.set_opacity(1)
227+
self.add(letter)
228+
229+
# Finish animation
230+
if alpha == 1:
231+
self.remove(*[self.anim])
232+
self.add_to_back(self.anim)
233+
mob.shapes.set_z_index(0)
234+
mob.shapes.save_state()
235+
mob.M.save_state()
236+
237+
def slide_back(mob, alpha):
238+
if alpha == 0:
239+
m_clone.set_opacity(1)
240+
m_clone.move_to(mob.anim[-1])
241+
mob.anim.set_opacity(1)
242+
243+
shift(alpha * shape_sliding_overshoot * LEFT)
244+
245+
if alpha == 1:
246+
mob.remove(m_clone)
247+
mob.add_to_back(mob.shapes)
248+
249+
return Succession(
250+
UpdateFromAlphaFunc(
251+
self,
252+
slide_and_uncover,
253+
run_time=run_time * 2 / 3,
254+
rate_func=ease_in_out_cubic,
255+
),
256+
UpdateFromAlphaFunc(
257+
self, slide_back, run_time=run_time * 1 / 3, rate_func=smooth
202258
),
203-
# It would be nice to have the last AnimationGroup replaced by
204-
# FadeIn(self.anim, lag_ratio=1)
205-
# Currently not working though.
206259
)
207-
return Succession(move_left, move_right)

0 commit comments

Comments
 (0)